refactor(security): Ports & Adapters 架构重构|解除反向依赖|抽离 Security Starter 准备

- 新增 2 个端口接口(UserAuthenticationPort / PermissionPort)+ 1 个适配器(PermissionAdapter)
- SecurityUserDetails 用 roles(Set<String>) 替代 authorities,消除 Jackson 序列化问题
- 删除 UserSession,Redis / JWT 直存 SecurityUserDetails
- SecurityUserDetailsService / PermissionService 注入 Port 替代 system Service
This commit is contained in:
Ray.Hao
2026-07-05 16:53:46 +08:00
parent b17bd6bf7c
commit 2441efa9ba
46 changed files with 2917 additions and 938 deletions

View File

@@ -0,0 +1,131 @@
package com.youlai.boot.auth.security.config;
import cn.binarywang.wx.miniapp.api.WxMaService;
import cn.hutool.core.util.ArrayUtil;
import com.youlai.boot.framework.captcha.service.CaptchaService;
import com.youlai.boot.framework.security.config.SecurityProperties;
import com.youlai.boot.framework.security.filter.TokenAuthenticationFilter;
import com.youlai.boot.framework.security.port.UserAuthenticationPort;
import com.youlai.boot.framework.security.service.SecurityUserDetailsService;
import com.youlai.boot.framework.security.token.TokenManager;
import com.youlai.boot.auth.security.filter.CaptchaValidationFilter;
import com.youlai.boot.auth.security.handler.JsonAccessDeniedHandler;
import com.youlai.boot.auth.security.handler.JsonAuthenticationEntryPoint;
import com.youlai.boot.auth.security.provider.SmsAuthenticationProvider;
import com.youlai.boot.auth.security.provider.WxMaAuthenticationProvider;
import com.youlai.boot.system.service.UserSocialService;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.access.intercept.AuthorizationFilter;
/**
* Spring Security 配置类。
* <p>
* 归使用方auth 模块安全规则放行路径、CORS、Provider 装配、响应格式)
* 因项目而异,不应由框架层强制装配。
*
* @author Ray.Hao
* @since 4.3.1
*/
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
@RequiredArgsConstructor
public class SecurityConfig {
private final RedisTemplate<String, Object> redisTemplate;
private final PasswordEncoder passwordEncoder;
private final TokenManager tokenManager;
private final SecurityUserDetailsService userDetailsService;
private final CaptchaService captchaService;
private final SecurityProperties securityProperties;
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http
.authorizeHttpRequests(requestMatcherRegistry -> {
String[] ignoreUrls = securityProperties.getIgnoreUrls();
if (ArrayUtil.isNotEmpty(ignoreUrls)) {
requestMatcherRegistry.requestMatchers(ignoreUrls).permitAll();
}
requestMatcherRegistry.anyRequest().authenticated();
}
)
.exceptionHandling(configurer ->
configurer
.authenticationEntryPoint(new JsonAuthenticationEntryPoint())
.accessDeniedHandler(new JsonAccessDeniedHandler())
)
.sessionManagement(configurer ->
configurer.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
)
.csrf(AbstractHttpConfigurer::disable)
.formLogin(AbstractHttpConfigurer::disable)
.httpBasic(AbstractHttpConfigurer::disable)
.headers(headers -> headers.frameOptions(HeadersConfigurer.FrameOptionsConfig::disable))
// 验证码校验(使用方过滤器,直接写 JSON 响应)
.addFilterBefore(new CaptchaValidationFilter(captchaService), UsernamePasswordAuthenticationFilter.class)
// Token 认证Starter 过滤器,抛 AuthenticationException 交给 ExceptionTranslationFilter 处理)
.addFilterBefore(new TokenAuthenticationFilter(tokenManager), AuthorizationFilter.class)
.build();
}
@Bean
public WebSecurityCustomizer webSecurityCustomizer() {
return (web) -> {
String[] unsecuredUrls = securityProperties.getUnsecuredUrls();
if (ArrayUtil.isNotEmpty(unsecuredUrls)) {
web.ignoring().requestMatchers(unsecuredUrls);
}
};
}
@Bean
public DaoAuthenticationProvider daoAuthenticationProvider() {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider(userDetailsService);
provider.setPasswordEncoder(passwordEncoder);
return provider;
}
@Bean
public SmsAuthenticationProvider smsAuthenticationProvider(UserAuthenticationPort userAuthPort) {
return new SmsAuthenticationProvider(userAuthPort, redisTemplate);
}
@Bean
public WxMaAuthenticationProvider wechatMiniAuthenticationProvider(
WxMaService wxMaService,
UserAuthenticationPort userAuthenticationPort,
UserSocialService userSocialService
) {
return new WxMaAuthenticationProvider(wxMaService, userAuthenticationPort, userSocialService);
}
@Bean
public AuthenticationManager authenticationManager(
DaoAuthenticationProvider daoAuthenticationProvider,
SmsAuthenticationProvider smsAuthenticationProvider,
WxMaAuthenticationProvider wxMaAuthenticationProvider
) {
return new ProviderManager(
daoAuthenticationProvider,
smsAuthenticationProvider,
wxMaAuthenticationProvider
);
}
}

View File

@@ -0,0 +1,26 @@
package com.youlai.boot.auth.security.exception;
import org.springframework.security.core.AuthenticationException;
/**
* 需要绑定手机号异常(微信小程序登录未绑定手机号时抛出)。
*/
public class MobileNotBoundException extends AuthenticationException {
private final String openid;
private final String sessionKey;
public MobileNotBoundException(String openid, String sessionKey) {
super("需要绑定手机号");
this.openid = openid;
this.sessionKey = sessionKey;
}
public String getOpenid() {
return openid;
}
public String getSessionKey() {
return sessionKey;
}
}

View File

@@ -0,0 +1,16 @@
package com.youlai.boot.auth.security.exception;
import org.springframework.security.core.AuthenticationException;
/**
* 短信验证码异常
*
* @author Ray.Hao
* @since 2025/3/1
*/
public class SmsCaptchaException extends AuthenticationException {
public SmsCaptchaException(String msg) {
super(msg);
}
}

View File

@@ -0,0 +1,137 @@
package com.youlai.boot.auth.security.filter;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.youlai.boot.common.constant.SecurityConstants;
import com.youlai.boot.common.result.ResultCode;
import com.youlai.boot.framework.web.util.ResponseWriter;
import com.youlai.boot.framework.captcha.exception.CaptchaException;
import com.youlai.boot.framework.captcha.service.CaptchaService;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletInputStream;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequestWrapper;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.StreamUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.util.ContentCachingRequestWrapper;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
/**
* 图形验证码校验过滤器。
* <p>
* 归使用方,因为验证码规则(哪些接口需要验证码、验证码类型)因项目而异。
*/
public class CaptchaValidationFilter extends OncePerRequestFilter {
private static final RequestMatcher LOGIN_PATH_REQUEST_MATCHER = PathPatternRequestMatcher.withDefaults()
.matcher(HttpMethod.POST, SecurityConstants.LOGIN_PATH);
public static final String CAPTCHA_CODE_PARAM_NAME = "captchaCode";
public static final String CAPTCHA_ID_PARAM_NAME = "captchaId";
private final CaptchaService captchaService;
public CaptchaValidationFilter(CaptchaService captchaService) {
this.captchaService = captchaService;
}
@Override
public void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
if (!LOGIN_PATH_REQUEST_MATCHER.matches(request)) {
chain.doFilter(request, response);
return;
}
String contentType = request.getContentType();
if (contentType == null || !contentType.contains(MediaType.APPLICATION_JSON_VALUE)) {
ResponseWriter.writeError(response, ResultCode.USER_VERIFICATION_CODE_ERROR);
return;
}
ContentCachingRequestWrapper requestWrapper = new ContentCachingRequestWrapper(request, -1);
byte[] bodyBytes = StreamUtils.copyToByteArray(requestWrapper.getInputStream());
String body = new String(bodyBytes, StandardCharsets.UTF_8);
String captchaCode = null;
String captchaId = null;
if (StrUtil.isNotBlank(body)) {
JSONObject jsonObject = JSONUtil.parseObj(body);
captchaCode = jsonObject.getStr(CAPTCHA_CODE_PARAM_NAME);
captchaId = jsonObject.getStr(CAPTCHA_ID_PARAM_NAME);
}
try {
captchaService.validate(captchaId, captchaCode);
HttpServletRequest repeatableRequest = new RepeatableReadRequestWrapper(requestWrapper, bodyBytes);
chain.doFilter(repeatableRequest, response);
} catch (CaptchaException e) {
ResponseWriter.writeError(response, e.getResultCode());
}
}
private static class RepeatableReadRequestWrapper extends HttpServletRequestWrapper {
private final byte[] cachedBody;
RepeatableReadRequestWrapper(HttpServletRequest request, byte[] cachedBody) {
super(request);
this.cachedBody = cachedBody != null ? cachedBody : new byte[0];
}
@Override
public ServletInputStream getInputStream() {
ByteArrayInputStream bais = new ByteArrayInputStream(cachedBody);
return new ServletInputStream() {
@Override
public int read() {
return bais.read();
}
@Override
public boolean isFinished() {
return bais.available() == 0;
}
@Override
public boolean isReady() {
return true;
}
@Override
public void setReadListener(jakarta.servlet.ReadListener readListener) {
// no-op
}
};
}
@Override
public BufferedReader getReader() {
return new BufferedReader(new InputStreamReader(getInputStream(), StandardCharsets.UTF_8));
}
@Override
public int getContentLength() {
return cachedBody.length;
}
@Override
public long getContentLengthLong() {
return cachedBody.length;
}
}
}

View File

@@ -0,0 +1,26 @@
package com.youlai.boot.auth.security.handler;
import com.youlai.boot.common.result.ResultCode;
import com.youlai.boot.framework.web.util.ResponseWriter;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
/**
* 无权限访问处理器。
* <p>
* 归使用方,因为 JSON 响应格式因项目而异。
*
* @author Ray.Hao
* @since 2.0.0
*/
public class JsonAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response,
AccessDeniedException accessDeniedException) {
ResponseWriter.writeError(response, ResultCode.ACCESS_PERMISSION_EXCEPTION);
}
}

View File

@@ -0,0 +1,37 @@
package com.youlai.boot.auth.security.handler;
import com.youlai.boot.common.result.ResultCode;
import com.youlai.boot.framework.web.util.ResponseWriter;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* 统一处理 Spring Security 认证失败响应。
* <p>
* 归使用方,因为 JSON 响应格式Result 结构、错误码)因项目而异。
*
* @author Ray.Hao
* @since 2.0.0
*/
public class JsonAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException, ServletException {
if (authException instanceof BadCredentialsException) {
ResponseWriter.writeError(response, ResultCode.USER_PASSWORD_ERROR);
} else if (authException instanceof InsufficientAuthenticationException) {
ResponseWriter.writeError(response, ResultCode.ACCESS_TOKEN_INVALID);
} else {
ResponseWriter.writeError(response, ResultCode.USER_LOGIN_EXCEPTION, authException.getMessage());
}
}
}

View File

@@ -0,0 +1,53 @@
package com.youlai.boot.auth.security.model;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import java.io.Serial;
import java.util.Collection;
/**
* 短信验证码认证 Token。
* <p>
* 未认证principal=手机号credentials=验证码已认证principal=SecurityUserDetailscredentials=null。
*
* @author Ray.Hao
* @since 2.20.0
*/
public class SmsAuthenticationToken extends AbstractAuthenticationToken {
@Serial
private static final long serialVersionUID = 621L;
private final Object principal;
private final Object credentials;
public SmsAuthenticationToken(String mobile, String verifyCode) {
super(AuthorityUtils.NO_AUTHORITIES);
this.principal = mobile;
this.credentials = verifyCode;
setAuthenticated(false);
}
public SmsAuthenticationToken(Object principal, Collection<? extends GrantedAuthority> authorities) {
super(authorities);
this.principal = principal;
this.credentials = null;
super.setAuthenticated(true);
}
public static SmsAuthenticationToken authenticated(Object principal, Collection<? extends GrantedAuthority> authorities) {
return new SmsAuthenticationToken(principal, authorities);
}
@Override
public Object getCredentials() {
return this.credentials;
}
@Override
public Object getPrincipal() {
return this.principal;
}
}

View File

@@ -0,0 +1,53 @@
package com.youlai.boot.auth.security.model;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import java.io.Serial;
import java.util.Collection;
/**
* 微信小程序认证 Token。
* <p>
* 未认证principal=微信code已认证principal=SecurityUserDetails。
*
* @author Ray.Hao
* @since 4.0.0
*/
public class WxMaAuthenticationToken extends AbstractAuthenticationToken {
@Serial
private static final long serialVersionUID = 622L;
private final Object principal;
private final Object credentials;
public WxMaAuthenticationToken(String code) {
super(AuthorityUtils.NO_AUTHORITIES);
this.principal = code;
this.credentials = null;
setAuthenticated(false);
}
public WxMaAuthenticationToken(Object principal, Collection<? extends GrantedAuthority> authorities) {
super(authorities);
this.principal = principal;
this.credentials = null;
super.setAuthenticated(true);
}
public static WxMaAuthenticationToken authenticated(Object principal, Collection<? extends GrantedAuthority> authorities) {
return new WxMaAuthenticationToken(principal, authorities);
}
@Override
public Object getCredentials() {
return this.credentials;
}
@Override
public Object getPrincipal() {
return this.principal;
}
}

View File

@@ -0,0 +1,95 @@
package com.youlai.boot.auth.security.provider;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.youlai.boot.common.constant.RedisConstants;
import com.youlai.boot.framework.security.model.SecurityUser;
import com.youlai.boot.framework.security.model.SecurityUserDetails;
import com.youlai.boot.framework.security.port.UserAuthenticationPort;
import com.youlai.boot.auth.security.exception.SmsCaptchaException;
import com.youlai.boot.auth.security.model.SmsAuthenticationToken;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
/**
* 短信验证码认证 Provider
* <p>
* 认证流程:
* <ol>
* <li>根据手机号查询用户信息</li>
* <li>校验用户状态</li>
* <li>校验短信验证码(与 Redis 缓存比对)</li>
* <li>验证成功后删除验证码</li>
* <li>返回已认证的 Authentication</li>
* </ol>
*
* @author Ray.Hao
* @since 2.17.0
*/
@Slf4j
public class SmsAuthenticationProvider implements AuthenticationProvider {
private final UserAuthenticationPort userAuthPort;
private final RedisTemplate<String, Object> redisTemplate;
public SmsAuthenticationProvider(UserAuthenticationPort userAuthPort, RedisTemplate<String, Object> redisTemplate) {
this.userAuthPort = userAuthPort;
this.redisTemplate = redisTemplate;
}
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String mobile = (String) authentication.getPrincipal();
String inputVerifyCode = (String) authentication.getCredentials();
if (StrUtil.isBlank(mobile)) {
log.warn("短信验证码登录失败:手机号为空");
throw new SmsCaptchaException("手机号不能为空");
}
if (StrUtil.isBlank(inputVerifyCode)) {
log.warn("短信验证码登录失败:验证码为空,手机号={}", mobile);
throw new SmsCaptchaException("验证码不能为空");
}
SecurityUser securityUser = userAuthPort.getAuthInfoByMobile(mobile);
if (securityUser == null) {
log.warn("短信验证码登录失败:用户不存在,手机号={}", mobile);
throw new UsernameNotFoundException("用户不存在");
}
if (ObjectUtil.notEqual(securityUser.getStatus(), 1)) {
log.warn("短信验证码登录失败:用户已禁用,用户名={}", securityUser.getUsername());
throw new DisabledException("用户已被禁用");
}
String cacheKey = StrUtil.format(RedisConstants.Captcha.SMS_LOGIN_CODE, mobile);
String cachedVerifyCode = (String) redisTemplate.opsForValue().get(cacheKey);
if (cachedVerifyCode == null) {
log.warn("短信验证码登录失败:验证码已过期,手机号={}", mobile);
throw new SmsCaptchaException("验证码已过期,请重新获取");
}
if (!StrUtil.equals(inputVerifyCode, cachedVerifyCode)) {
log.warn("短信验证码登录失败:验证码错误,手机号={}", mobile);
throw new SmsCaptchaException("验证码错误");
}
redisTemplate.delete(cacheKey);
SecurityUserDetails userDetails = new SecurityUserDetails(securityUser);
log.info("短信验证码登录成功:用户名={},手机号={}", securityUser.getUsername(), mobile);
return SmsAuthenticationToken.authenticated(userDetails, userDetails.getAuthorities());
}
@Override
public boolean supports(Class<?> authentication) {
return SmsAuthenticationToken.class.isAssignableFrom(authentication);
}
}

View File

@@ -0,0 +1,85 @@
package com.youlai.boot.auth.security.provider;
import cn.binarywang.wx.miniapp.api.WxMaService;
import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult;
import cn.hutool.core.util.ObjectUtil;
import com.youlai.boot.common.enums.SocialPlatformEnum;
import com.youlai.boot.framework.security.model.SecurityUser;
import com.youlai.boot.framework.security.model.SecurityUserDetails;
import com.youlai.boot.framework.security.port.UserAuthenticationPort;
import com.youlai.boot.auth.security.exception.MobileNotBoundException;
import com.youlai.boot.auth.security.model.WxMaAuthenticationToken;
import com.youlai.boot.system.model.entity.UserSocial;
import com.youlai.boot.system.service.UserSocialService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.error.WxErrorException;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
/**
* 微信小程序认证 Provider
*/
@Slf4j
@RequiredArgsConstructor
public class WxMaAuthenticationProvider implements AuthenticationProvider {
private final WxMaService wxMaService;
private final UserAuthenticationPort userAuthPort;
private final UserSocialService userSocialService;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String code = (String) authentication.getPrincipal();
if (code == null || code.isEmpty()) {
log.warn("微信小程序登录失败code为空");
throw new IllegalArgumentException("code不能为空");
}
try {
WxMaJscode2SessionResult session = wxMaService.jsCode2SessionInfo(code);
String openid = session.getOpenid();
String sessionKey = session.getSessionKey();
log.info("微信小程序登录openid={}", openid);
UserSocial userSocial = userSocialService.getByPlatformAndOpenid(SocialPlatformEnum.WECHAT_MINI, openid);
if (userSocial == null) {
log.info("微信小程序登录用户未绑定手机号openid={}", openid);
throw new MobileNotBoundException(openid, sessionKey);
}
SecurityUser securityUser = userAuthPort.getAuthInfoByOpenid(SocialPlatformEnum.WECHAT_MINI, openid);
if (securityUser == null) {
log.warn("微信小程序登录失败用户不存在openid={}", openid);
throw new UsernameNotFoundException("用户不存在");
}
if (ObjectUtil.notEqual(securityUser.getStatus(), 1)) {
log.warn("微信小程序登录失败用户已禁用username={}", securityUser.getUsername());
throw new DisabledException("用户已被禁用");
}
userSocialService.updateSessionKey(userSocial.getId(), sessionKey);
SecurityUserDetails userDetails = new SecurityUserDetails(securityUser);
log.info("微信小程序登录成功username={}, openid={}", securityUser.getUsername(), openid);
return WxMaAuthenticationToken.authenticated(userDetails, userDetails.getAuthorities());
} catch (WxErrorException e) {
log.error("微信小程序登录失败调用微信接口异常code={}", code, e);
throw new IllegalArgumentException("微信登录失败:" + e.getMessage());
}
}
@Override
public boolean supports(Class<?> authentication) {
return WxMaAuthenticationToken.class.isAssignableFrom(authentication);
}
}

View File

@@ -6,8 +6,8 @@ import com.youlai.boot.common.constant.RedisConstants;
import com.youlai.boot.framework.captcha.model.CaptchaInfo;
import com.youlai.boot.framework.captcha.service.CaptchaService;
import com.youlai.boot.framework.security.model.AuthenticationToken;
import com.youlai.boot.framework.security.model.SmsAuthenticationToken;
import com.youlai.boot.framework.security.token.TokenManager;
import com.youlai.boot.auth.security.model.SmsAuthenticationToken;
import com.youlai.boot.framework.security.util.SecurityUtils;
import com.youlai.boot.framework.integration.sms.enums.SmsTypeEnum;
import com.youlai.boot.framework.integration.sms.service.SmsService;
@@ -58,9 +58,9 @@ public class AuthServiceImpl implements AuthService {
// 2. 执行认证(认证中)
// 说明:这里的认证流程由 Spring Security 提供的 AuthenticationManager 执行。
// 默认情况下会委托给 DaoAuthenticationProvider
// 1) retrieveUser(...):内部通过 UserDetailsService.loadUserByUsername(...) 获取用户信息(本项目为 SysUserDetailsService 实现)
// 1) retrieveUser(...):内部通过 UserDetailsService.loadUserByUsername(...) 获取用户信息(本项目为 SecurityUserDetailsService 实现)
// 2) additionalAuthenticationChecks(...):对比请求密码与用户存储密码(由 PasswordEncoder 完成匹配)
// 认证通过后返回已认证的 Authenticationprincipal 为 SysUserDetailsauthorities 为角色/权限集合)。
// 认证通过后返回已认证的 Authenticationprincipal 为 SecurityUserDetailsauthorities 为角色/权限集合)。
Authentication authentication = authenticationManager.authenticate(authenticationToken);
// 3. 认证成功后生成 JWT 令牌,并存入 Security 上下文,供登录日志 AOP 使用(已认证)

View File

@@ -8,12 +8,12 @@ import cn.hutool.core.util.StrUtil;
import com.youlai.boot.auth.model.vo.WxMaLoginVO;
import com.youlai.boot.auth.service.WxMaAuthService;
import com.youlai.boot.common.constant.RedisConstants;
import com.youlai.boot.framework.security.exception.NeedBindMobileException;
import com.youlai.boot.framework.security.model.AuthenticationToken;
import com.youlai.boot.framework.security.model.SysUserDetails;
import com.youlai.boot.framework.security.model.WxMaAuthenticationToken;
import com.youlai.boot.framework.security.model.SecurityUserDetails;
import com.youlai.boot.framework.security.token.TokenManager;
import com.youlai.boot.system.enums.SocialPlatformEnum;
import com.youlai.boot.auth.security.exception.MobileNotBoundException;
import com.youlai.boot.auth.security.model.WxMaAuthenticationToken;
import com.youlai.boot.common.enums.SocialPlatformEnum;
import com.youlai.boot.system.model.entity.SysUser;
import com.youlai.boot.system.service.UserSocialService;
import com.youlai.boot.system.service.UserService;
@@ -69,7 +69,7 @@ public class WxMaAuthServiceImpl implements WxMaAuthService {
.tokenType(authToken.getTokenType())
.expiresIn(authToken.getExpiresIn())
.build();
} catch (NeedBindMobileException e) {
} catch (MobileNotBoundException e) {
return WxMaLoginVO.builder()
.isNewUser(true)
.needBindMobile(true)
@@ -234,7 +234,7 @@ public class WxMaAuthServiceImpl implements WxMaAuthService {
* 生成认证令牌
*/
private AuthenticationToken generateAuthToken(String mobile) {
SysUserDetails userDetails = new SysUserDetails(userService.getAuthInfoByMobile(mobile));
SecurityUserDetails userDetails = new SecurityUserDetails(userService.getAuthInfoByMobile(mobile));
Authentication authentication = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities()
);