Commit de57dff1 by zhangxingmin

微服务核心组件

parent 345cdf96
......@@ -53,18 +53,6 @@
<artifactId>jjwt-jackson</artifactId>
</dependency>
<!-- yd-user-service 模块 -->
<dependency>
<groupId>com.yd</groupId>
<artifactId>yd-user-service</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.yd</groupId>
<artifactId>yd-common</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-core</artifactId>
......@@ -92,12 +80,27 @@
<artifactId>p6spy</artifactId>
</dependency>
<!-- 用户API模块 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
<version>3.1.1</version> <!-- 与您的Spring Cloud版本匹配 -->
</dependency>
<!-- yd-user-service 模块 -->
<dependency>
<groupId>com.yd</groupId>
<artifactId>yd-user-api</artifactId>
<artifactId>yd-user-service</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.yd</groupId>
<artifactId>yd-common</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.yd</groupId>
<artifactId>yd-framework</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</project>
......@@ -2,7 +2,7 @@ package com.yd.auth.core.config;
import com.yd.auth.core.security.JwtAuthenticationEntryPoint;
import com.yd.auth.core.security.JwtAuthenticationFilter;
import com.yd.auth.core.service.AuthUserDetailsService;
import com.yd.auth.core.service.impl.AuthUserDetailsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
......@@ -62,7 +62,8 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
"/auth/login",
"/auth/register",
"/swagger-ui/**",
"/v3/api-docs/**"
"/v3/api-docs/**",
"/scrm/test"
).permitAll()
.anyRequest().authenticated()
.and()
......
package com.yd.auth.core.controller;
import com.yd.auth.core.dto.LoginRequest;
import com.yd.auth.core.dto.LoginResponse;
import com.yd.auth.core.request.LoginRequest;
import com.yd.auth.core.response.LoginResponse;
import com.yd.auth.core.service.AuthService;
import com.yd.common.result.Result;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.validation.Valid;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
/**
* 认证信息
*/
@RestController
@RequestMapping("/auth")
@Validated
public class AuthController {
private final AuthService authService;
@Autowired
public AuthController(AuthService authService) {
this.authService = authService;
}
private AuthService authService;
/**
* 登录(spring security登录认证)
* @param loginRequest
* @return
*/
@PostMapping("/login")
public Result<LoginResponse> login(@Valid @RequestBody LoginRequest loginRequest) {
public Result<LoginResponse> login(@RequestBody LoginRequest loginRequest) {
LoginResponse response = authService.login(loginRequest);
return Result.success(response);
}
// @PostMapping("/logout")
// public Result<Void> logout() {
// // 实际实现需要前端传递token
// // 这里简化处理
// return R.success("登出成功");
// }
}
package com.yd.auth.core.dto;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
@Data
public class AuthPermissionDto {
private Long id;
private String name;
private String description;
private String url;
private String method;
}
package com.yd.auth.core.dto;
import lombok.Data;
@Data
public class AuthRoleDto {
private Long id;
private String name;
private String description;
}
package com.yd.auth.core.dto;
import lombok.Data;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
* 用户认证信息数据传输对象
* 实现Spring Security的UserDetails接口,用于封装用户认证信息
*/
@Data
public class AuthUserDto implements UserDetails {
/**
* 系统用户主键id(数据库唯一标识)
*/
private Long id;
/**
* 用户唯一标识(业务ID),用于业务层面的用户识别
*/
private String userUid;
/**
* 登录账号(实现UserDetails接口必需)
*/
private String username;
/**
* 加密后的密码(实现UserDetails接口必需)
*/
private String password;
// ============== Spring Security账户状态属性 ==============
/**
* 账户是否启用(默认true启用)
*/
private Boolean enabled = true;
/**
* 账户是否未过期(默认true未过期)
*/
private Boolean accountNonExpired = true;
/**
* 凭证(密码)是否未过期(默认true未过期)
*/
private Boolean credentialsNonExpired = true;
/**
* 账户是否未锁定(默认true未锁定)
*/
private Boolean accountNonLocked = true;
/**
* 用户关联的角色列表
*/
private List<AuthRoleDto> roles;
/**
* 获取用户权限集合(实现UserDetails接口必需)
* Spring Security通过此方法获取用户的权限/角色信息
*
* @return 权限集合,每个权限以GrantedAuthority对象表示
*/
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
// 创建权限集合
List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
// 检查角色列表是否有效
if (roles != null && !roles.isEmpty()) {
// 遍历所有角色
for (AuthRoleDto role : roles) {
// 将角色名转换为Spring Security认可的权限标识(添加ROLE_前缀)
authorities.add(new SimpleGrantedAuthority("ROLE_" + role.getName()));
}
}
return authorities;
}
/**
* 检查账户是否未过期
* @return true=未过期,false=已过期
*/
@Override
public boolean isAccountNonExpired() {
return accountNonExpired;
}
/**
* 检查账户是否未锁定
* @return true=未锁定,false=已锁定
*/
@Override
public boolean isAccountNonLocked() {
return accountNonLocked;
}
/**
* 检查凭证(密码)是否未过期
* @return true=未过期,false=已过期
*/
@Override
public boolean isCredentialsNonExpired() {
return credentialsNonExpired;
}
/**
* 检查账户是否启用
* @return true=启用,false=禁用
*/
@Override
public boolean isEnabled() {
return enabled;
}
}
package com.yd.auth.core.dto;
public class LoginRequest {
private String username;
private String password;
// Getters and Setters
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
package com.yd.auth.core.dto;
public class LoginResponse {
private String token;
private String tokenType = "Bearer";
private long expiresIn;
// Getters and Setters
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
public String getTokenType() {
return tokenType;
}
public void setTokenType(String tokenType) {
this.tokenType = tokenType;
}
public long getExpiresIn() {
return expiresIn;
}
public void setExpiresIn(long expiresIn) {
this.expiresIn = expiresIn;
}
}
package com.yd.auth.core.exception;
import com.yd.auth.core.security.JwtTokenProvider;
import com.yd.common.result.Result;
import org.springframework.http.HttpStatus;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(BadCredentialsException.class)
@ResponseStatus(HttpStatus.UNAUTHORIZED)
public Result<?> handleBadCredentialsException(BadCredentialsException e) {
return Result.fail(401, "用户名或密码错误");
}
@ExceptionHandler(JwtTokenProvider.JwtAuthenticationException.class)
@ResponseStatus(HttpStatus.UNAUTHORIZED)
public Result<?> handleJwtAuthenticationException(JwtTokenProvider.JwtAuthenticationException e) {
return Result.fail(401, e.getMessage());
}
}
//package com.yd.auth.core.exception;
//
//import com.yd.auth.core.security.JwtTokenProvider;
//import com.yd.common.result.Result;
//import org.springframework.http.HttpStatus;
//import org.springframework.security.authentication.BadCredentialsException;
//import org.springframework.web.bind.annotation.ExceptionHandler;
//import org.springframework.web.bind.annotation.ResponseStatus;
//import org.springframework.web.bind.annotation.RestControllerAdvice;
//
//@RestControllerAdvice
//public class GlobalExceptionHandler {
//
// @ExceptionHandler(BadCredentialsException.class)
// @ResponseStatus(HttpStatus.UNAUTHORIZED)
// public Result<?> handleBadCredentialsException(BadCredentialsException e) {
// return Result.fail(401, "用户名或密码错误");
// }
//
// @ExceptionHandler(JwtTokenProvider.JwtAuthenticationException.class)
// @ResponseStatus(HttpStatus.UNAUTHORIZED)
// public Result<?> handleJwtAuthenticationException(JwtTokenProvider.JwtAuthenticationException e) {
// return Result.fail(401, e.getMessage());
// }
//}
package com.yd.auth.core.request;
import lombok.Data;
import javax.validation.constraints.NotBlank;
@Data
public class LoginRequest {
/**
* 用户登录账号
*/
@NotBlank(message = "用户登录账号不能为空")
private String username;
/**
* 明文密码
*/
@NotBlank(message = "密码不能为空")
private String password;
}
package com.yd.auth.core.response;
import lombok.Data;
@Data
public class LoginResponse {
private String token;
private String tokenType = "Bearer";
private long expiresIn;
}
package com.yd.auth.core.security;
import com.yd.auth.core.security.JwtAuthenticationToken;
import org.springframework.security.authentication.ReactiveAuthenticationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;
import java.util.ArrayList;
import java.util.List;
@Component
public class DummyAuthenticationManager implements ReactiveAuthenticationManager {
@Override
public Mono<Authentication> authenticate(Authentication authentication) {
// 检查是否为 JwtAuthenticationToken 类型
if (authentication instanceof JwtAuthenticationToken) {
String token = (String) authentication.getCredentials();
try {
// 验证令牌有效性(实际项目中应调用 JwtUtils 验证)
if (isValidToken(token)) {
// 从令牌中提取用户名和权限信息
String username = extractUsernameFromToken(token);
List<GrantedAuthority> authorities = extractAuthoritiesFromToken(token);
// 创建已认证的令牌
JwtAuthenticationToken authenticatedToken =
new JwtAuthenticationToken(username, authorities);
authenticatedToken.setDetails(authentication.getDetails());
// return Mono.just(authenticatedToken);
return null;
}
} catch (Exception e) {
return Mono.error(e);
}
}
// 非 JWT 认证请求,返回空
return Mono.empty();
}
// 实际项目中应调用 JwtUtils 验证令牌
private boolean isValidToken(String token) {
// 示例实现,实际应调用 JwtUtils.validateToken(token)
return token != null && !token.isEmpty();
}
// 实际项目中应从令牌中解析用户名
private String extractUsernameFromToken(String token) {
// 示例实现,实际应调用 JwtUtils.getUsernameFromToken(token)
return "test-user";
}
// 实际项目中应从令牌中解析权限
private List<GrantedAuthority> extractAuthoritiesFromToken(String token) {
// 示例实现,实际应调用 JwtUtils.getRolesFromToken(token)
List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
authorities.add(new SimpleGrantedAuthority("ROLE_USER"));
return authorities;
}
}
......@@ -15,6 +15,12 @@ public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
public void commence(HttpServletRequest request,
HttpServletResponse response,
AuthenticationException authException) throws IOException {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "未经授权的访问");
// 设置响应类型为JSON
response.setContentType("application/json;charset=UTF-8");
// 直接设置401状态码
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
// 返回JSON格式的错误信息
response.getWriter().write("{\"code\":401,\"message\":\"未经授权的访问\"}");
}
}
package com.yd.auth.core.security;
import com.yd.auth.core.dto.AuthUserDto;
import com.yd.user.service.model.SysUser;
import com.yd.user.service.service.ISysUserService;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Objects;
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
@Autowired
private ISysUserService iSysUserService;
private final JwtTokenProvider tokenProvider;
private final UserDetailsService userDetailsService;
public JwtAuthenticationFilter(JwtTokenProvider tokenProvider,
UserDetailsService userDetailsService) {
public JwtAuthenticationFilter(JwtTokenProvider tokenProvider) {
this.tokenProvider = tokenProvider;
this.userDetailsService = userDetailsService;
}
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
// 1. 从HTTP请求中解析JWT令牌(通常从Authorization头获取)
String token = resolveToken(request);
// 2. 检查令牌是否存在且有效
if (token != null && tokenProvider.validateToken(token)) {
String username = tokenProvider.getUsernameFromToken(token);
// 从数据库加载用户信息
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
// 3. 从JWT令牌中提取用户唯一标识(业务ID)
String userUid = tokenProvider.getUserUidFromToken(token);
// 4. 根据用户唯一标识查询用户详细信息(包括权限信息)
UserDetails userDetails = queryUserDetails(userUid);
// 5. 创建Spring Security认证对象
// - userDetails: 包含用户身份和权限信息
// - null: 凭证(密码),JWT认证中不需要
// - userDetails.getAuthorities(): 用户的权限集合
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
// 6. 将认证对象设置到SecurityContext中,表示用户已认证,这里设置后续接口能直接拿到登录用户信息
SecurityContextHolder.getContext().setAuthentication(authentication);
}
// 7. 继续执行后续过滤器链(无论是否认证都要继续处理请求)
filterChain.doFilter(request, response);
}
/**
* 查询用户信息返回spring security认证对象UserDetails
* @param userUid
* @return spring security认证对象UserDetails
*/
public UserDetails queryUserDetails(String userUid) {
AuthUserDto authUserDto = null;
SysUser sysUser = iSysUserService.queryOne(userUid);
if (Objects.isNull(sysUser)) {
throw new UsernameNotFoundException("用户不存在:");
}
authUserDto = new AuthUserDto();
BeanUtils.copyProperties(sysUser,authUserDto);
// 查询用户角色 TODO
// authUserDto.setRoles(roles);
return authUserDto;
}
private String resolveToken(HttpServletRequest request) {
String bearerToken = request.getHeader("Authorization");
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
......
package com.yd.auth.core.security;
import com.yd.auth.core.utils.JwtUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
@Component
public class JwtAuthenticationProvider implements AuthenticationProvider {
@Autowired
private JwtUtils jwtUtils;
@Autowired
private UserDetailsService userDetailsService;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String token = (String) authentication.getCredentials();
try {
// 验证JWT令牌
if (!jwtUtils.validateToken(token)) {
throw new BadCredentialsException("无效的令牌");
}
// 从令牌中获取用户名
String username = jwtUtils.getUsernameFromToken(token);
// 加载用户详情
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
// 从令牌中获取角色
List<String> roles = jwtUtils.getRolesFromToken(token);
List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
for (String role : roles) {
authorities.add(new SimpleGrantedAuthority(role));
}
// 创建已认证的令牌
JwtAuthenticationToken authenticatedToken =
new JwtAuthenticationToken(userDetails, authorities);
authenticatedToken.setDetails(authentication.getDetails());
return authenticatedToken;
} catch (Exception e) {
throw new BadCredentialsException("认证失败", e);
}
}
@Override
public boolean supports(Class<?> authentication) {
return JwtAuthenticationToken.class.isAssignableFrom(authentication);
}
}
package com.yd.auth.core.security;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.SpringSecurityCoreVersion;
import java.util.Collection;
public class JwtAuthenticationToken extends AbstractAuthenticationToken {
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
private final Object principal;
private String credentials;
/**
* 用于未认证的令牌
*/
public JwtAuthenticationToken(String token) {
super(null);
this.principal = token;
this.credentials = token;
setAuthenticated(false);
}
/**
* 用于已认证的令牌
*/
public JwtAuthenticationToken(Object principal, Collection<? extends GrantedAuthority> authorities) {
super(authorities);
this.principal = principal;
super.setAuthenticated(true); // must use super, as we override
}
@Override
public Object getCredentials() {
return this.credentials;
}
@Override
public Object getPrincipal() {
return this.principal;
}
@Override
public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
if (isAuthenticated) {
throw new IllegalArgumentException(
"Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead");
}
super.setAuthenticated(false);
}
@Override
public void eraseCredentials() {
super.eraseCredentials();
credentials = null;
}
}
......@@ -8,7 +8,6 @@ import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import javax.crypto.SecretKey;
import java.util.Collection;
import java.util.Date;
......@@ -17,10 +16,10 @@ import java.util.stream.Collectors;
@Component
public class JwtTokenProvider {
@Value("${jwt.secret}")
@Value("${jwt.secret:1}")
private String jwtSecret;
@Value("${jwt.expiration}")
@Value("${jwt.expiration:1}")
private int jwtExpiration;
public String generateToken(Authentication authentication) {
......@@ -41,6 +40,15 @@ public class JwtTokenProvider {
.collect(Collectors.toList());
}
public String getUserUidFromToken(String token) {
return Jwts.parserBuilder()
.setSigningKey(getSigningKey())
.build()
.parseClaimsJws(token)
.getBody()
.getSubject();
}
public String getUsernameFromToken(String token) {
return Jwts.parserBuilder()
.setSigningKey(getSigningKey())
......
package com.yd.auth.core.service;
import com.yd.auth.core.dto.LoginRequest;
import com.yd.auth.core.dto.LoginResponse;
import org.springframework.security.core.Authentication;
import com.yd.auth.core.request.LoginRequest;
import com.yd.auth.core.response.LoginResponse;
public interface AuthService {
LoginResponse login(LoginRequest loginRequest);
void logout(String token);
Authentication getAuthentication(String token);
}
package com.yd.auth.core.service;
import com.yd.auth.core.model.AuthUser;
import com.yd.user.api.dto.UserDTO;
import com.yd.user.api.feign.UserFeignClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import java.util.stream.Collectors;
@Service
public class AuthUserDetailsService implements UserDetailsService {
private final UserFeignClient userFeignClient;
@Autowired
public AuthUserDetailsService(UserFeignClient userFeignClient) {
this.userFeignClient = userFeignClient;
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 通过Feign调用yd-user服务获取用户信息
UserDTO user = userFeignClient.findByUsername(username);
if (user == null) {
throw new UsernameNotFoundException("用户不存在:" + username);
}
// 转换为Spring Security用户对象
return new AuthUser(
user.getId(),
user.getUsername(),
user.getPassword(),
user.getRoles().stream()
.map(role -> new SimpleGrantedAuthority(role)) // 修正类名
.collect(Collectors.toList())
);
}
}
package com.yd.auth.core.service;
import com.yd.user.service.entity.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
public interface UserService extends UserDetailsService {
/**
* 根据用户名加载用户详情
*/
@Override
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
/**
* 根据用户名查找用户
*/
User findByUsername(String username);
/**
* 注册新用户
*/
User register(User user);
}
package com.yd.auth.core.service.impl;
import com.yd.auth.core.dto.LoginRequest;
import com.yd.auth.core.dto.LoginResponse;
import com.yd.auth.core.request.LoginRequest;
import com.yd.auth.core.response.LoginResponse;
import com.yd.auth.core.security.JwtTokenProvider;
import com.yd.auth.core.service.AuthService;
import org.springframework.beans.factory.annotation.Autowired;
......@@ -24,43 +24,45 @@ public class AuthServiceImpl implements AuthService {
this.jwtTokenProvider = jwtTokenProvider;
}
/**
* 用户登录认证方法(基于Spring Security的认证流程)
*
* @param loginRequest 登录请求对象(包含用户名和密码)
* @return 登录响应对象(包含JWT令牌和过期时间)
*/
@Override
public LoginResponse login(LoginRequest loginRequest) {
// 1. 创建认证对象
// 1. 创建未认证的Authentication对象
// - 第一个参数:用户名(作为身份主体)
// - 第二个参数:原始密码(作为凭证)
Authentication authentication = new UsernamePasswordAuthenticationToken(
loginRequest.getUsername(),
loginRequest.getPassword()
);
// 2. 进行认证
// 2. 执行Spring Security认证流程(核心步骤)
// - 触发AuthUserDetailsService.loadUserByUsername()方法
// - 自动进行密码比对(使用配置的PasswordEncoder)
// - 成功返回已认证的Authentication对象,失败抛出AuthenticationException
Authentication authenticated = authenticationManager.authenticate(authentication);
// 3. 设置安全上下文
// 3. 将认证信息设置到SecurityContext
// - 使当前请求线程具有认证上下文
// - 后续操作可通过SecurityContextHolder获取当前用户
SecurityContextHolder.getContext().setAuthentication(authenticated);
// 4. 生成令牌
// 4. 生成JWT令牌
// - 基于认证对象中的用户信息生成令牌
// - 令牌中包含用户标识和权限信息
String token = jwtTokenProvider.generateToken(authenticated);
// 5. 创建响应对象
// 5. 构建登录响应对象
LoginResponse response = new LoginResponse();
response.setToken(token);
response.setExpiresIn(jwtTokenProvider.getJwtExpiration());
response.setToken(token); // 设置生成的JWT令牌
response.setExpiresIn(jwtTokenProvider.getJwtExpiration()); // 设置令牌过期时间
return response;
}
@Override
public void logout(String token) {
// 实现令牌失效逻辑(可选)
// 实际应用中可能需要将令牌加入黑名单
SecurityContextHolder.clearContext();
}
@Override
public Authentication getAuthentication(String token) {
if (jwtTokenProvider.validateToken(token)) {
return jwtTokenProvider.getAuthentication(token);
}
return null;
}
}
package com.yd.auth.core.service.impl;
import com.yd.auth.core.dto.AuthUserDto;
import com.yd.user.service.model.SysUser;
import com.yd.user.service.service.ISysUserService;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import java.util.Objects;
/**
* authenticationManager.authenticate(authentication) -->进行spring security 登录认证
* --> spring security 登录认证会走到这里去获取用户表的数据进行用户名和密码对比返回最后认证是否成功的结果
*/
@Service
public class AuthUserDetailsService implements UserDetailsService {
@Autowired
private ISysUserService iSysUserService;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//Spring Security用户认证对象
AuthUserDto authUserDto = null;
// 通过Feign调用yd-user-service服务获取用户信息
SysUser sysUser = iSysUserService.queryOneByName(username);
if (Objects.isNull(sysUser)) {
throw new UsernameNotFoundException("用户不存在");
}
authUserDto = new AuthUserDto();
BeanUtils.copyProperties(sysUser,authUserDto);
// 查询用户角色 TODO
// authUserDto.setRoles(roles);
return authUserDto;
}
}
package com.yd.auth.core.utils;
import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import javax.crypto.SecretKey;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
@Component
public class JwtUtils {
@Value("${jwt.secret}")
private String secret;
@Value("${jwt.expiration}")
private Long expiration;
@Value("${jwt.refresh-expiration}")
private Long refreshExpiration;
/**
* 生成JWT令牌
*/
public String generateToken(UserDetails userDetails) {
Map<String, Object> claims = new HashMap<String, Object>();
// 添加用户角色
claims.put("roles", userDetails.getAuthorities().stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.toList()));
return createToken(claims, userDetails.getUsername(), expiration);
}
/**
* 生成刷新令牌
*/
public String generateRefreshToken(UserDetails userDetails) {
Map<String, Object> claims = new HashMap<>();
return createToken(claims, userDetails.getUsername(), refreshExpiration);
}
/**
* 创建JWT令牌
*/
private String createToken(Map<String, Object> claims, String subject, Long expirationTime) {
SecretKey key = Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8));
return Jwts.builder()
.setClaims(claims)
.setSubject(subject)
.setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + expirationTime * 1000))
.signWith(key, SignatureAlgorithm.HS256)
.compact();
}
/**
* 解析JWT令牌
*/
public Claims parseToken(String token) {
SecretKey key = Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8));
try {
return Jwts.parserBuilder()
.setSigningKey(key)
.build()
.parseClaimsJws(token)
.getBody();
} catch (ExpiredJwtException e) {
throw new RuntimeException("令牌已过期", e);
} catch (UnsupportedJwtException e) {
throw new RuntimeException("不支持的令牌", e);
} catch (MalformedJwtException e) {
throw new RuntimeException("无效的令牌", e);
} catch (SignatureException e) {
throw new RuntimeException("签名验证失败", e);
} catch (IllegalArgumentException e) {
throw new RuntimeException("令牌参数错误", e);
}
}
/**
* 从令牌中获取用户名
*/
public String getUsernameFromToken(String token) {
return parseToken(token).getSubject();
}
/**
* 从令牌中获取过期时间
*/
public Date getExpirationDateFromToken(String token) {
return parseToken(token).getExpiration();
}
/**
* 检查令牌是否过期
*/
public Boolean isTokenExpired(String token) {
final Date expiration = getExpirationDateFromToken(token);
return expiration.before(new Date());
}
/**
* 验证令牌
*/
public Boolean validateToken(String token, UserDetails userDetails) {
final String username = getUsernameFromToken(token);
return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
}
public Boolean validateToken(String token) {
try {
// 解析token
Claims claims = parseToken(token);
// 检查是否过期
return !isTokenExpired(token);
} catch (Exception e) {
return false;
}
}
/**
* 从令牌中获取角色
*/
public List<String> getRolesFromToken(String token) {
Claims claims = parseToken(token);
return claims.get("roles", List.class);
}
/**
* 从令牌中获取指定的声明
*/
public <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver) {
final Claims claims = parseToken(token);
return claimsResolver.apply(claims);
}
}
......@@ -14,11 +14,6 @@
<description>公共组件模块</description>
<dependencies>
<!-- SpringCloud Openfeign -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!-- 测试 -->
<dependency>
<groupId>org.springframework.boot</groupId>
......@@ -37,5 +32,13 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
</dependencies>
</project>
package com.yd.common.dto;
import lombok.Data;
@Data
public class PageDto {
private Integer pageNo = 1;
private Integer pageSize = 10;
}
package com.yd.common.enums;
/**
* 公共枚举
*/
public enum CommonEnum {
//业务唯一标识id的类型
UID_TYPE_TENANT("tenant","租户"),
UID_TYPE_PROJECT("project","项目"),
UID_TYPE_USER("user","用户"),
UID_TYPE_ROLE("role","角色"),
UID_TYPE_MENU("menu","菜单"),
;
//编码
private String code;
//名称
private String name;
//构造函数
CommonEnum(String code, String name) {
this.code = code;
this.name = name;
}
public String getCode() {
return code;
}
public String getName() {
return name;
}
}
package com.yd.common.enums;
/**
* 返回码
*
* @author zxm
*/
public enum ResultCode {
SUCCESS(200,"操作成功","") ,
FAIL(500,"操作失败","") ,
NULL_ERROR(201,"数据不存在","") ,
TENANT_NAME_EXISTS(202,"租户名称已存在","") ,
PARAM_CHECK_ERROR(4001,"参数校验异常",""),
;
//返回码
private final int code;
//操作响应信息
private final String message;
//响应信息的详细描述
private final String description;
//构造函数
ResultCode(int code, String message, String description) {
this.code = code;
this.message = message;
this.description = description;
}
//get方法
public int getCode() {
return code;
}
public String getMessage() {
return message;
}
public String getDescription() {
return description;
}
}
......@@ -17,7 +17,17 @@ public class Result<T> implements Serializable {
private Result() {}
/**
* 成功返回
* 成功返回(不带上数据)
*/
public static <T> Result<T> success() {
Result<T> result = new Result<T>();
result.setCode(200);
result.setMsg("操作成功");
return result;
}
/**
* 成功返回(带上数据)
*/
public static <T> Result<T> success(T data) {
Result<T> result = new Result<T>();
......
package com.yd.common.utils;
import com.yd.common.enums.CommonEnum;
import java.security.SecureRandom;
import java.util.Random;
public class RandomStringGenerator {
// 包含所有数字和字母(大小写)的字符池
private static final String CHAR_POOL = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
private static final SecureRandom secureRandom = new SecureRandom();
private static final Random random = new Random();
/**
* 生成随机字符串
* @param length 字符串长度
* @param useSecureRandom 是否使用安全随机生成器
* @return 随机字符串
*/
public static String generate(int length, boolean useSecureRandom) {
StringBuilder sb = new StringBuilder(length);
Random rand = useSecureRandom ? secureRandom : random;
for (int i = 0; i < length; i++) {
// 从字符池中随机选取字符
int randomIndex = rand.nextInt(CHAR_POOL.length());
sb.append(CHAR_POOL.charAt(randomIndex));
}
return sb.toString();
}
/**
* 生成(业务标识类型+32位随机字符串)业务唯一标识id
* @param uidType 业务标识类型
* @return 业务标识类型+32位随机字符串
*/
public static String generateUid32(String uidType) {
return uidType + "_" + generate(32,true);
}
/**
* 生成(业务标识类型+16位随机字符串)业务唯一标识id
* @param uidType 业务标识类型
* @return 业务标识类型+16位随机字符串
*/
public static String generateUid16(String uidType) {
return uidType + "_" + generate(16,true);
}
public static void main(String[] args) {
// 生成16位随机字符串(使用安全随机)
String random16 = generate(16, true);
System.out.println("16位: " + random16);
// 生成32位随机字符串(使用普通随机)
String random32 = generate(32, false);
System.out.println("32位: " + random32);
System.out.println(generateUid16(CommonEnum.UID_TYPE_USER.getCode()));
}
}
......@@ -12,18 +12,6 @@
<artifactId>yd-feign</artifactId>
<name>yd-feign</name>
<description>Feign组件模块</description>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>8</source>
<target>8</target>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
......@@ -31,10 +19,6 @@
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>com.yd</groupId>
<artifactId>yd-common</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<optional>true</optional>
......
package com.yd.feign.client;
import com.yd.common.entity.UserDetails;
import com.yd.common.result.Result;
import com.yd.feign.client.fallback.UserFeignFallbackFactory;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
/**
* 用户服务Feign客户端
*/
@FeignClient(name = "yd-user-service", fallbackFactory = UserFeignFallbackFactory.class)
public interface UserFeignClient {
/**
* 根据用户名获取用户信息
*/
@GetMapping("/user/getByUsername")
Result<UserDetails> getByUsername(@RequestParam("username") String username);
}
package com.yd.feign.client.fallback;
import com.yd.common.entity.UserDetails;
import com.yd.common.exception.BusinessException;
import com.yd.common.result.Result;
import com.yd.feign.client.UserFeignClient;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.openfeign.FallbackFactory;
import org.springframework.stereotype.Component;
/**
* 用户服务Feign降级处理
*/
@Slf4j
@Component
public class UserFeignFallbackFactory implements FallbackFactory<UserFeignClient> {
public UserFeignClient create(final Throwable cause) {
return new UserFeignClient() {
public Result<UserDetails> getByUsername(String username) {
log.error("调用用户服务失败: {}", cause.getMessage());
throw new BusinessException("获取用户信息失败,请稍后重试");
}
};
}
}
package com.yd.framework.handler;
import com.yd.common.enums.ResultCode;
import com.yd.common.exception.BusinessException;
import com.yd.common.result.Result;
import lombok.extern.slf4j.Slf4j;
......@@ -9,6 +10,9 @@ import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import javax.validation.ConstraintViolationException;
import java.util.stream.Collectors;
//import com.yd.common.model.R;
//import org.springframework.http.HttpStatus;
//import org.springframework.security.authentication.BadCredentialsException;
......@@ -33,6 +37,7 @@ public class GlobalExceptionHandler {
}
/**
* @Validated校验器触发的异常 @RequestBody对象校验
* 处理参数校验异常
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
......@@ -40,8 +45,21 @@ public class GlobalExceptionHandler {
BindingResult bindingResult = e.getBindingResult();
FieldError fieldError = bindingResult.getFieldError();
String message = fieldError != null ? fieldError.getDefaultMessage() : "参数校验失败";
log.error("参数校验异常: {}", message);
return Result.fail(400, message);
log.error("RequestBody参数校验异常: {}", message);
return Result.fail(ResultCode.PARAM_CHECK_ERROR.getCode(), message);
}
/**
* @Validated校验器触发的异常 RequestParam 参数校验
* @param e
* @return
*/
@ExceptionHandler(ConstraintViolationException.class)
public Result<?> constraintViolationExceptionHandler(ConstraintViolationException e){
String message = e.getConstraintViolations().stream().collect(Collectors.toList()).get(0).getMessage();
log.error("RequestParam参数校验异常:{}",e);
return Result.fail(ResultCode.PARAM_CHECK_ERROR.getCode(),message);
}
/**
......@@ -53,6 +71,9 @@ public class GlobalExceptionHandler {
return Result.fail("系统异常,请联系管理员");
}
// @ExceptionHandler(BadCredentialsException.class)
// @ResponseStatus(HttpStatus.UNAUTHORIZED)
// public R<?> handleBadCredentialsException(BadCredentialsException e) {
......
package com.yd.gateway.config;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 网关路由配置
*/
@Configuration
public class GatewayConfig {
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
// 认证服务路由
.route("yd-auth-service", r -> r.path("/auth/**")
.uri("lb://yd-auth-service"))
// 用户服务路由
.route("yd-user-service", r -> r.path("/user/**")
.uri("lb://yd-user-service"))
// SCRM服务路由
.route("yd-scrm-service", r -> r.path("/scrm/**")
.uri("lb://yd-scrm-service"))
.build();
}
}
package com.yd.gateway.filter;
import com.yd.auth.core.security.DummyAuthenticationManager;
import com.yd.auth.core.utils.JwtUtils;
import com.yd.common.exception.BusinessException;
import com.yd.common.result.Result;
import io.jsonwebtoken.Claims;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.web.server.WebFilterExchange;
import org.springframework.security.web.server.authentication.AuthenticationWebFilter;
import org.springframework.security.web.server.authentication.ServerAuthenticationConverter;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import com.alibaba.fastjson.JSON;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
/**
* 网关认证过滤器
*/
@Component
public class AuthenticationFilter extends AuthenticationWebFilter {
private final JwtUtils jwtUtils;
public AuthenticationFilter(JwtUtils jwtUtils) {
super(new DummyAuthenticationManager());
this.jwtUtils = jwtUtils;
setServerAuthenticationConverter(jwtAuthenticationConverter());
}
/**
* JWT认证转换器
*/
private ServerAuthenticationConverter jwtAuthenticationConverter() {
return exchange -> {
ServerHttpRequest request = exchange.getRequest();
String token = getTokenFromRequest(request);
if (token == null) {
return Mono.error(new BusinessException("未提供令牌"));
}
try {
// 验证令牌
if (!jwtUtils.validateToken(token)) {
return Mono.error(new BusinessException("无效的令牌"));
}
// 解析令牌
Claims claims = jwtUtils.parseToken(token);
String username = claims.getSubject();
// 获取角色信息
List<String> roles = (List<String>) claims.get("roles");
List<SimpleGrantedAuthority> authorities = new ArrayList<>();
if (roles != null && !roles.isEmpty()) {
authorities = roles.stream()
.map(role -> new SimpleGrantedAuthority("ROLE_" + role))
.collect(Collectors.toList());
}
// 设置认证信息
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(username, null, authorities);
return Mono.just(authentication);
} catch (Exception e) {
return Mono.error(new BusinessException(e.getMessage()));
}
};
}
/**
* 从请求头中获取令牌
*/
private String getTokenFromRequest(ServerHttpRequest request) {
String bearerToken = request.getHeaders().getFirst(HttpHeaders.AUTHORIZATION);
if (bearerToken != null && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7);
}
return null;
}
/**
* 处理认证失败
*/
protected Mono<Void> onAuthenticationFailure(WebFilterExchange webFilterExchange, AuthenticationException exception) {
ServerWebExchange exchange = webFilterExchange.getExchange();
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
exchange.getResponse().getHeaders().setContentType(MediaType.APPLICATION_JSON);
Result<?> result = Result.fail(401, exception.getMessage());
byte[] bytes = JSON.toJSONString(result).getBytes(StandardCharsets.UTF_8);
DataBuffer buffer = exchange.getResponse().bufferFactory().wrap(bytes);
return exchange.getResponse().writeWith(Mono.just(buffer));
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment