From d9e25874ed4a19bf5cd56c0f9d88c3d5d57662f6 Mon Sep 17 00:00:00 2001 From: "Ray.Hao" <1490493387@qq.com> Date: Mon, 13 Jan 2025 08:26:50 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E8=AE=A4=E8=AF=81=E5=92=8C?= =?UTF-8?q?=E7=9F=AD=E4=BF=A1=E5=8F=91=E9=80=81=E9=87=8D=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../boot/common/constant/RedisConstants.java | 7 +- .../youlai/boot/config/SecurityConfig.java | 4 +- .../config/property/AliyunSmsProperties.java | 4 +- .../sms/SmsAuthenticationProvider.java | 81 +++++++++++++++++++ .../extension/sms/SmsAuthenticationToken.java | 68 ++++++++++++++++ .../filter/JwtAuthenticationFilter.java | 6 +- .../security/manager/JwtTokenManager.java} | 17 ++-- .../security/manager/RedisTokenManager.java} | 11 ++- .../security/manager/TokenManager.java} | 10 +-- .../security/model/AuthToken.java} | 5 +- .../auth/controller/AuthController.java | 37 +++++++-- .../boot/shared/auth/service/AuthService.java | 15 +++- .../auth/service/impl/AuthServiceImpl.java | 47 ++++++++--- .../file/service/impl/MinioFileService.java | 10 +-- .../shared/sms/controller/SmsController.java | 1 + .../boot/shared/sms/enums/SmsTypeEnum.java | 22 +++++ .../boot/shared/sms/service/SmsService.java | 10 +-- .../sms/service/impl/AliyunSmsService.java | 11 +-- .../system/service/impl/UserServiceImpl.java | 30 ++++--- src/main/resources/application-dev.yml | 24 +++--- 20 files changed, 329 insertions(+), 91 deletions(-) create mode 100644 src/main/java/com/youlai/boot/core/security/extension/sms/SmsAuthenticationProvider.java create mode 100644 src/main/java/com/youlai/boot/core/security/extension/sms/SmsAuthenticationToken.java rename src/main/java/com/youlai/boot/{shared/auth/service/impl/JwtTokenService.java => core/security/manager/JwtTokenManager.java} (93%) rename src/main/java/com/youlai/boot/{shared/auth/service/impl/RedisTokenService.java => core/security/manager/RedisTokenManager.java} (73%) rename src/main/java/com/youlai/boot/{shared/auth/service/TokenService.java => core/security/manager/TokenManager.java} (79%) rename src/main/java/com/youlai/boot/{shared/auth/model/AuthTokenResponse.java => core/security/model/AuthToken.java} (88%) create mode 100644 src/main/java/com/youlai/boot/shared/sms/enums/SmsTypeEnum.java diff --git a/src/main/java/com/youlai/boot/common/constant/RedisConstants.java b/src/main/java/com/youlai/boot/common/constant/RedisConstants.java index 48ca4765..bf4fc986 100644 --- a/src/main/java/com/youlai/boot/common/constant/RedisConstants.java +++ b/src/main/java/com/youlai/boot/common/constant/RedisConstants.java @@ -31,8 +31,13 @@ public interface RedisConstants { /** * 手机验证码缓存前缀 */ + String SMS_LOGIN_VERIFY_CODE_PREFIX = "sms_login:mobile:"; - String MOBILE_VERIFICATION_CODE_PREFIX = "VERIFICATION_CODE:MOBILE:"; + /** + * 重置密码验证码缓存前缀 + */ + + String SMS_RESET_PASSWORD_VERIFY_CODE_PREFIX = "sms_reset_password:mobile:"; /** diff --git a/src/main/java/com/youlai/boot/config/SecurityConfig.java b/src/main/java/com/youlai/boot/config/SecurityConfig.java index 6b32835e..63d94cd7 100644 --- a/src/main/java/com/youlai/boot/config/SecurityConfig.java +++ b/src/main/java/com/youlai/boot/config/SecurityConfig.java @@ -11,7 +11,7 @@ import com.youlai.boot.core.security.extension.WechatAuthenticationProvider; import com.youlai.boot.core.security.filter.CaptchaValidationFilter; import com.youlai.boot.core.security.filter.JwtAuthenticationFilter; import com.youlai.boot.core.security.service.SysUserDetailsService; -import com.youlai.boot.shared.auth.service.impl.JwtTokenService; +import com.youlai.boot.core.security.manager.JwtTokenManager; import com.youlai.boot.system.service.ConfigService; import com.youlai.boot.system.service.UserService; import lombok.RequiredArgsConstructor; @@ -47,7 +47,7 @@ public class SecurityConfig { private final RedisTemplate redisTemplate; private final PasswordEncoder passwordEncoder; - private final JwtTokenService jwtTokenService; + private final JwtTokenManager jwtTokenService; private final WxMaService wxMaService; private final UserService userService; private final SysUserDetailsService userDetailsService; diff --git a/src/main/java/com/youlai/boot/config/property/AliyunSmsProperties.java b/src/main/java/com/youlai/boot/config/property/AliyunSmsProperties.java index 05cf7b66..138d9479 100644 --- a/src/main/java/com/youlai/boot/config/property/AliyunSmsProperties.java +++ b/src/main/java/com/youlai/boot/config/property/AliyunSmsProperties.java @@ -43,8 +43,8 @@ public class AliyunSmsProperties { private String signName; /** - * 模板编码 + * 短信模板集合 */ - private Map templateCodes; + private Map templates; } diff --git a/src/main/java/com/youlai/boot/core/security/extension/sms/SmsAuthenticationProvider.java b/src/main/java/com/youlai/boot/core/security/extension/sms/SmsAuthenticationProvider.java new file mode 100644 index 00000000..bf728e77 --- /dev/null +++ b/src/main/java/com/youlai/boot/core/security/extension/sms/SmsAuthenticationProvider.java @@ -0,0 +1,81 @@ +package com.youlai.boot.core.security.extension.sms; + +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import com.youlai.boot.common.constant.RedisConstants; +import com.youlai.boot.core.security.extension.WechatAuthenticationToken; +import com.youlai.boot.core.security.model.SysUserDetails; +import com.youlai.boot.system.model.dto.UserAuthInfo; +import com.youlai.boot.system.service.UserService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.security.authentication.AuthenticationProvider; +import org.springframework.security.authentication.CredentialsExpiredException; +import org.springframework.security.authentication.DisabledException; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; + + +/** + * 短信验证码认证 Provider + * + * @author Ray.Hao + * @since 2.17.0 + */ +@Slf4j +public class SmsAuthenticationProvider implements AuthenticationProvider { + + private final UserService userService; + + private final StringRedisTemplate redisTemplate; + + + public SmsAuthenticationProvider(UserService userService, StringRedisTemplate redisTemplate) { + this.userService = userService; + this.redisTemplate = redisTemplate; + } + + /** + * 短信验证码认证逻辑,参考 Spring Security 认证密码校验流程 + * + * @param authentication 认证对象 + * @return 认证后的 Authentication 对象 + * @throws AuthenticationException 认证异常 + * @see org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider#authenticate(Authentication) + */ + @Override + public Authentication authenticate(Authentication authentication) throws AuthenticationException { + String mobile = (String) authentication.getPrincipal(); + String verifyCode = (String) authentication.getCredentials(); + + // 根据手机号获取用户信息 + UserAuthInfo userAuthInfo = userService.getUserAuthInfoByMobile(mobile); + + // 检查用户状态是否有效 + if (ObjectUtil.notEqual(userAuthInfo.getStatus(), 1)) { + throw new DisabledException("用户已被禁用"); + } + + // 校验发送短信验证码的手机号是否与当前登录用户一致 + + String cachedVerifyCode= redisTemplate.opsForValue().get(RedisConstants.SMS_LOGIN_VERIFY_CODE_PREFIX + mobile); + + if ( !StrUtil.equals(verifyCode, cachedVerifyCode)) { + throw new CredentialsExpiredException("验证码错误"); + } + + // 构建认证后的用户详情信息 + SysUserDetails userDetails = new SysUserDetails(userAuthInfo); + + // 创建已认证的 WeChatAuthenticationToken + return SmsAuthenticationToken.authenticated( + userDetails, + userDetails.getAuthorities() + ); + } + + @Override + public boolean supports(Class authentication) { + return WechatAuthenticationToken.class.isAssignableFrom(authentication); + } +} diff --git a/src/main/java/com/youlai/boot/core/security/extension/sms/SmsAuthenticationToken.java b/src/main/java/com/youlai/boot/core/security/extension/sms/SmsAuthenticationToken.java new file mode 100644 index 00000000..64b0b7f2 --- /dev/null +++ b/src/main/java/com/youlai/boot/core/security/extension/sms/SmsAuthenticationToken.java @@ -0,0 +1,68 @@ +package com.youlai.boot.core.security.extension.sms; + +import org.springframework.security.authentication.AbstractAuthenticationToken; +import org.springframework.security.core.GrantedAuthority; + +import java.io.Serial; +import java.util.Collection; + +/** + * 短信验证码认证 Token + * + * @author Ray.Hao + * @since 2.20.0 + */ +public class SmsAuthenticationToken extends AbstractAuthenticationToken { + @Serial + private static final long serialVersionUID = 621L; + private final Object principal; + private Object credentials; + + /** + * 短信验证码认证 Token (未认证) + * + * @param principal 微信用户信息 + */ + public SmsAuthenticationToken(Object principal) { + // 没有授权信息时,设置为 null + super(null); + this.principal = principal; + // 默认未认证 + this.setAuthenticated(false); + } + + /** + * 短信验证码认证 Token (已认证) + * + * @param principal 用户信息 + * @param authorities 授权信息 + */ + public SmsAuthenticationToken(Object principal, Collection authorities) { + super(authorities); + this.principal = principal; + // 认证通过 + super.setAuthenticated(true); + } + + + /** + * 认证通过 + * + * @param principal 用户信息 + * @param authorities 授权信息 + * @return + */ + public static SmsAuthenticationToken authenticated(Object principal, Collection authorities) { + return new SmsAuthenticationToken(principal, authorities); + } + + @Override + public Object getCredentials() { + return this.credentials ; + } + + @Override + public Object getPrincipal() { + return this.principal; + } +} diff --git a/src/main/java/com/youlai/boot/core/security/filter/JwtAuthenticationFilter.java b/src/main/java/com/youlai/boot/core/security/filter/JwtAuthenticationFilter.java index ac14f3bc..3b2da4b9 100644 --- a/src/main/java/com/youlai/boot/core/security/filter/JwtAuthenticationFilter.java +++ b/src/main/java/com/youlai/boot/core/security/filter/JwtAuthenticationFilter.java @@ -4,7 +4,7 @@ import cn.hutool.core.util.StrUtil; import com.youlai.boot.common.constant.SecurityConstants; import com.youlai.boot.common.result.ResultCode; import com.youlai.boot.common.util.ResponseUtils; -import com.youlai.boot.shared.auth.service.impl.JwtTokenService; +import com.youlai.boot.core.security.manager.JwtTokenManager; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; @@ -24,10 +24,10 @@ import java.io.IOException; */ public class JwtAuthenticationFilter extends OncePerRequestFilter { - private final JwtTokenService jwtTokenService; + private final JwtTokenManager jwtTokenService; - public JwtAuthenticationFilter(JwtTokenService jwtTokenService) { + public JwtAuthenticationFilter(JwtTokenManager jwtTokenService) { this.jwtTokenService = jwtTokenService; } diff --git a/src/main/java/com/youlai/boot/shared/auth/service/impl/JwtTokenService.java b/src/main/java/com/youlai/boot/core/security/manager/JwtTokenManager.java similarity index 93% rename from src/main/java/com/youlai/boot/shared/auth/service/impl/JwtTokenService.java rename to src/main/java/com/youlai/boot/core/security/manager/JwtTokenManager.java index b27e27f4..f347624c 100644 --- a/src/main/java/com/youlai/boot/shared/auth/service/impl/JwtTokenService.java +++ b/src/main/java/com/youlai/boot/core/security/manager/JwtTokenManager.java @@ -1,4 +1,4 @@ -package com.youlai.boot.shared.auth.service.impl; +package com.youlai.boot.core.security.manager; import cn.hutool.core.convert.Convert; import cn.hutool.core.date.DateUtil; @@ -13,8 +13,7 @@ import com.youlai.boot.common.exception.BusinessException; import com.youlai.boot.common.result.ResultCode; import com.youlai.boot.config.property.SecurityProperties; import com.youlai.boot.core.security.model.SysUserDetails; -import com.youlai.boot.shared.auth.model.AuthTokenResponse; -import com.youlai.boot.shared.auth.service.TokenService; +import com.youlai.boot.core.security.model.AuthToken; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; @@ -38,14 +37,14 @@ import java.util.stream.Collectors; */ @ConditionalOnProperty(value = "security.session.type", havingValue = "jwt") @Service -public class JwtTokenService implements TokenService { +public class JwtTokenManager implements TokenManager { private final SecurityProperties securityProperties; private final RedisTemplate redisTemplate; private final byte[] secretKey; - public JwtTokenService(SecurityProperties securityProperties, RedisTemplate redisTemplate) { + public JwtTokenManager(SecurityProperties securityProperties, RedisTemplate redisTemplate) { this.securityProperties = securityProperties; this.redisTemplate = redisTemplate; this.secretKey = securityProperties.getJwt().getKey().getBytes(); @@ -58,14 +57,14 @@ public class JwtTokenService implements TokenService { * @return 令牌响应对象 */ @Override - public AuthTokenResponse generateToken(Authentication authentication) { + public AuthToken generateToken(Authentication authentication) { int accessTokenTimeToLive = securityProperties.getJwt().getAccessTokenTimeToLive(); int refreshTokenTimeToLive = securityProperties.getJwt().getRefreshTokenTimeToLive(); String accessToken = generateToken(authentication, accessTokenTimeToLive); String refreshToken = generateToken(authentication, refreshTokenTimeToLive); - return AuthTokenResponse.builder() + return AuthToken.builder() .accessToken(accessToken) .refreshToken(refreshToken) .tokenType("Bearer") @@ -164,7 +163,7 @@ public class JwtTokenService implements TokenService { */ @Override - public AuthTokenResponse refreshToken(String refreshToken) { + public AuthToken refreshToken(String refreshToken) { boolean isValid = validateToken(refreshToken); if (!isValid) { @@ -175,7 +174,7 @@ public class JwtTokenService implements TokenService { int accessTokenExpiration = securityProperties.getJwt().getRefreshTokenTimeToLive(); String newAccessToken = generateToken(authentication, accessTokenExpiration); - return AuthTokenResponse.builder() + return AuthToken.builder() .accessToken(newAccessToken) .refreshToken(refreshToken) .tokenType("Bearer") diff --git a/src/main/java/com/youlai/boot/shared/auth/service/impl/RedisTokenService.java b/src/main/java/com/youlai/boot/core/security/manager/RedisTokenManager.java similarity index 73% rename from src/main/java/com/youlai/boot/shared/auth/service/impl/RedisTokenService.java rename to src/main/java/com/youlai/boot/core/security/manager/RedisTokenManager.java index 78603b4d..b154e549 100644 --- a/src/main/java/com/youlai/boot/shared/auth/service/impl/RedisTokenService.java +++ b/src/main/java/com/youlai/boot/core/security/manager/RedisTokenManager.java @@ -1,7 +1,6 @@ -package com.youlai.boot.shared.auth.service.impl; +package com.youlai.boot.core.security.manager; -import com.youlai.boot.shared.auth.model.AuthTokenResponse; -import com.youlai.boot.shared.auth.service.TokenService; +import com.youlai.boot.core.security.model.AuthToken; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.security.core.Authentication; import org.springframework.stereotype.Service; @@ -14,7 +13,7 @@ import org.springframework.stereotype.Service; */ @ConditionalOnProperty(value = "security.session.type", havingValue = "redis-token") @Service -public class RedisTokenService implements TokenService { +public class RedisTokenManager implements TokenManager { /** * 生成令牌 @@ -23,7 +22,7 @@ public class RedisTokenService implements TokenService { * @return */ @Override - public AuthTokenResponse generateToken(Authentication authentication) { + public AuthToken generateToken(Authentication authentication) { return null; } @@ -56,7 +55,7 @@ public class RedisTokenService implements TokenService { * @return */ @Override - public AuthTokenResponse refreshToken(String token) { + public AuthToken refreshToken(String token) { return null; } } diff --git a/src/main/java/com/youlai/boot/shared/auth/service/TokenService.java b/src/main/java/com/youlai/boot/core/security/manager/TokenManager.java similarity index 79% rename from src/main/java/com/youlai/boot/shared/auth/service/TokenService.java rename to src/main/java/com/youlai/boot/core/security/manager/TokenManager.java index 674539f7..1997053d 100644 --- a/src/main/java/com/youlai/boot/shared/auth/service/TokenService.java +++ b/src/main/java/com/youlai/boot/core/security/manager/TokenManager.java @@ -1,7 +1,7 @@ -package com.youlai.boot.shared.auth.service; +package com.youlai.boot.core.security.manager; -import com.youlai.boot.shared.auth.model.AuthTokenResponse; +import com.youlai.boot.core.security.model.AuthToken; import org.springframework.security.core.Authentication; /** @@ -10,7 +10,7 @@ import org.springframework.security.core.Authentication; * @author Ray * @since 2.16.0 */ -public interface TokenService { +public interface TokenManager { /** * 生成认证 Token @@ -18,7 +18,7 @@ public interface TokenService { * @param authentication 用户认证信息 * @return 认证 Token 响应 */ - AuthTokenResponse generateToken(Authentication authentication); + AuthToken generateToken(Authentication authentication); /** * 解析 Token 获取认证信息 @@ -44,7 +44,7 @@ public interface TokenService { * @param token 刷新令牌 * @return 认证 Token 响应 */ - AuthTokenResponse refreshToken(String token); + AuthToken refreshToken(String token); /** * 将 Token 加入黑名单 diff --git a/src/main/java/com/youlai/boot/shared/auth/model/AuthTokenResponse.java b/src/main/java/com/youlai/boot/core/security/model/AuthToken.java similarity index 88% rename from src/main/java/com/youlai/boot/shared/auth/model/AuthTokenResponse.java rename to src/main/java/com/youlai/boot/core/security/model/AuthToken.java index 719623c5..7905713b 100644 --- a/src/main/java/com/youlai/boot/shared/auth/model/AuthTokenResponse.java +++ b/src/main/java/com/youlai/boot/core/security/model/AuthToken.java @@ -1,4 +1,4 @@ -package com.youlai.boot.shared.auth.model; +package com.youlai.boot.core.security.model; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Builder; @@ -13,7 +13,7 @@ import lombok.Data; @Schema(description = "认证令牌响应对象") @Data @Builder -public class AuthTokenResponse { +public class AuthToken { @Schema(description = "令牌类型", example = "Bearer") private String tokenType; @@ -21,7 +21,6 @@ public class AuthTokenResponse { @Schema(description = "访问令牌") private String accessToken; - @Schema(description = "刷新令牌") private String refreshToken; diff --git a/src/main/java/com/youlai/boot/shared/auth/controller/AuthController.java b/src/main/java/com/youlai/boot/shared/auth/controller/AuthController.java index a6758aea..5c2f3e34 100644 --- a/src/main/java/com/youlai/boot/shared/auth/controller/AuthController.java +++ b/src/main/java/com/youlai/boot/shared/auth/controller/AuthController.java @@ -5,7 +5,7 @@ import com.youlai.boot.common.result.Result; import com.youlai.boot.shared.auth.model.RefreshTokenRequest; import com.youlai.boot.shared.auth.service.AuthService; import com.youlai.boot.shared.auth.model.CaptchaResponse; -import com.youlai.boot.shared.auth.model.AuthTokenResponse; +import com.youlai.boot.core.security.model.AuthToken; import com.youlai.boot.common.annotation.Log; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; @@ -32,12 +32,12 @@ public class AuthController { @Operation(summary = "登录") @PostMapping("/login") @Log(value = "登录", module = LogModuleEnum.LOGIN) - public Result login( + public Result login( @Parameter(description = "用户名", example = "admin") @RequestParam String username, @Parameter(description = "密码", example = "123456") @RequestParam String password ) { - AuthTokenResponse authTokenResponse = authService.login(username, password); - return Result.success(authTokenResponse); + AuthToken authToken = authService.login(username, password); + return Result.success(authToken); } @Operation(summary = "注销") @@ -58,17 +58,38 @@ public class AuthController { @Operation(summary = "刷新token") @PostMapping("/refresh-token") public Result refreshToken(@RequestBody RefreshTokenRequest request) { - AuthTokenResponse authTokenResponse = authService.refreshToken(request); - return Result.success(authTokenResponse); + AuthToken authToken = authService.refreshToken(request); + return Result.success(authToken); } @Operation(summary = "微信登录") @PostMapping("/wechat-login") @Log(value = "微信登录", module = LogModuleEnum.LOGIN) - public Result wechatLogin( + public Result wechatLogin( @Parameter(description = "微信授权码", example = "code") @RequestParam String code ) { - AuthTokenResponse loginResult = authService.wechatLogin(code); + AuthToken loginResult = authService.wechatLogin(code); return Result.success(loginResult); } + + + @Operation(summary = "短信验证码登录") + @PostMapping("/sms-login") + @Log(value = "短信验证码登录", module = LogModuleEnum.LOGIN) + public Result smsLogin( + @Parameter(description = "手机号", example = "18888888888") @RequestParam String mobile, + @Parameter(description = "验证码", example = "123456") @RequestParam String code + ) { + AuthToken loginResult = authService.smsLogin(mobile, code); + return Result.success(loginResult); + } + + @Operation(summary = "短信验证码登录发送短信") + @PostMapping("/sms-login/verify-code") + public Result sendLoginVerifyCode( + @Parameter(description = "手机号", example = "18888888888") @RequestParam String mobile + ) { + authService.sendLoginVerifyCode(mobile); + return Result.success(); + } } diff --git a/src/main/java/com/youlai/boot/shared/auth/service/AuthService.java b/src/main/java/com/youlai/boot/shared/auth/service/AuthService.java index 1a4215a4..13d66d9b 100644 --- a/src/main/java/com/youlai/boot/shared/auth/service/AuthService.java +++ b/src/main/java/com/youlai/boot/shared/auth/service/AuthService.java @@ -1,7 +1,7 @@ package com.youlai.boot.shared.auth.service; import com.youlai.boot.shared.auth.model.CaptchaResponse; -import com.youlai.boot.shared.auth.model.AuthTokenResponse; +import com.youlai.boot.core.security.model.AuthToken; import com.youlai.boot.shared.auth.model.RefreshTokenRequest; /** @@ -19,7 +19,7 @@ public interface AuthService { * @param password 密码 * @return 登录结果 */ - AuthTokenResponse login(String username, String password); + AuthToken login(String username, String password); /** * 登出 @@ -39,7 +39,7 @@ public interface AuthService { * @param request 刷新令牌请求参数 * @return 登录结果 */ - AuthTokenResponse refreshToken(RefreshTokenRequest request); + AuthToken refreshToken(RefreshTokenRequest request); /** * 微信小程序登录 @@ -47,5 +47,12 @@ public interface AuthService { * @param code 微信登录code * @return 登录结果 */ - AuthTokenResponse wechatLogin(String code); + AuthToken wechatLogin(String code); + + /** + * 发送短信验证码 + * + * @param mobile 手机号 + */ + void sendLoginVerifyCode(String mobile); } diff --git a/src/main/java/com/youlai/boot/shared/auth/service/impl/AuthServiceImpl.java b/src/main/java/com/youlai/boot/shared/auth/service/impl/AuthServiceImpl.java index 78bb6a74..668aa571 100644 --- a/src/main/java/com/youlai/boot/shared/auth/service/impl/AuthServiceImpl.java +++ b/src/main/java/com/youlai/boot/shared/auth/service/impl/AuthServiceImpl.java @@ -12,11 +12,13 @@ import com.youlai.boot.config.property.CaptchaProperties; import com.youlai.boot.core.security.extension.WechatAuthenticationToken; import com.youlai.boot.core.security.util.SecurityUtils; import com.youlai.boot.shared.auth.enums.CaptchaTypeEnum; -import com.youlai.boot.shared.auth.model.AuthTokenResponse; +import com.youlai.boot.core.security.model.AuthToken; import com.youlai.boot.shared.auth.model.CaptchaResponse; import com.youlai.boot.shared.auth.model.RefreshTokenRequest; import com.youlai.boot.shared.auth.service.AuthService; -import com.youlai.boot.shared.auth.service.TokenService; +import com.youlai.boot.core.security.manager.TokenManager; +import com.youlai.boot.shared.sms.enums.SmsTypeEnum; +import com.youlai.boot.shared.sms.service.SmsService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.data.redis.core.RedisTemplate; @@ -45,7 +47,9 @@ public class AuthServiceImpl implements AuthService { private final CodeGenerator codeGenerator; private final Font captchaFont; private final CaptchaProperties captchaProperties; - private final TokenService tokenService; + private final TokenManager tokenManager; + + private final SmsService smsService; /** * 用户名密码登录 @@ -55,7 +59,7 @@ public class AuthServiceImpl implements AuthService { * @return 访问令牌 */ @Override - public AuthTokenResponse login(String username, String password) { + public AuthToken login(String username, String password) { // 1. 创建用于密码认证的令牌(未认证) UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username.trim(), password); @@ -64,7 +68,8 @@ public class AuthServiceImpl implements AuthService { Authentication authentication = authenticationManager.authenticate(authenticationToken); // 3. 认证成功后生成 JWT 令牌,并存入 Security 上下文,供登录日志 AOP 使用(已认证) - AuthTokenResponse authTokenResponse = tokenService.generateToken(authentication); + AuthToken authTokenResponse = + tokenManager.generateToken(authentication); SecurityContextHolder.getContext().setAuthentication(authentication); return authTokenResponse; } @@ -76,7 +81,7 @@ public class AuthServiceImpl implements AuthService { * @return 访问令牌 */ @Override - public AuthTokenResponse wechatLogin(String code) { + public AuthToken wechatLogin(String code) { // 1. 创建用户微信认证的令牌(未认证) WechatAuthenticationToken authenticationToken = new WechatAuthenticationToken(code); @@ -84,10 +89,30 @@ public class AuthServiceImpl implements AuthService { Authentication authentication = authenticationManager.authenticate(authenticationToken); // 3. 认证成功后生成 JWT 令牌,并存入 Security 上下文,供登录日志 AOP 使用(已认证) - AuthTokenResponse authTokenResponse = tokenService.generateToken(authentication); + AuthToken authTokenResponse = tokenManager.generateToken(authentication); SecurityContextHolder.getContext().setAuthentication(authentication); return authTokenResponse; + } + + + /** + * 发送短信验证码 + * + * @param mobile 手机号 + */ + @Override + public void sendLoginVerifyCode(String mobile) { + + // 随机生成4位验证码 + String verifyCode = String.valueOf((int) ((Math.random() * 9 + 1) * 1000)); + // 发送短信验证码 + smsService.sendSms(mobile, SmsTypeEnum.LOGIN, verifyCode); + + + + + } /** @@ -99,7 +124,7 @@ public class AuthServiceImpl implements AuthService { if (StrUtil.isNotBlank(token) && token.startsWith(SecurityConstants.JWT_TOKEN_PREFIX)) { token = token.substring(SecurityConstants.JWT_TOKEN_PREFIX.length()); // 将JWT令牌加入黑名单 - tokenService.blacklistToken(token); + tokenManager.blacklistToken(token); // 清除Security上下文 SecurityContextHolder.clearContext(); } @@ -156,18 +181,18 @@ public class AuthServiceImpl implements AuthService { * @return 新的访问令牌 */ @Override - public AuthTokenResponse refreshToken(RefreshTokenRequest request) { + public AuthToken refreshToken(RefreshTokenRequest request) { // 验证刷新令牌 String refreshToken = request.getRefreshToken(); - boolean isValidate = tokenService.validateToken(refreshToken); + boolean isValidate = tokenManager.validateToken(refreshToken); if (!isValidate) { throw new BusinessException(ResultCode.REFRESH_TOKEN_INVALID); } - return tokenService.refreshToken(refreshToken); + return tokenManager.refreshToken(refreshToken); } diff --git a/src/main/java/com/youlai/boot/shared/file/service/impl/MinioFileService.java b/src/main/java/com/youlai/boot/shared/file/service/impl/MinioFileService.java index e5ea9f2f..db5fce55 100644 --- a/src/main/java/com/youlai/boot/shared/file/service/impl/MinioFileService.java +++ b/src/main/java/com/youlai/boot/shared/file/service/impl/MinioFileService.java @@ -128,8 +128,8 @@ public class MinioFileService implements FileService { /** * 删除文件 * - * @param filePath 文件路径 - * https://oss.youlai.tech/default/20221120/test.jpg + * @param filePath 文件路径 http://localhost:9000/default/20221120/test.jpg + * * @return */ @Override @@ -151,10 +151,8 @@ public class MinioFileService implements FileService { minioClient.removeObject(removeObjectArgs); return true; - } catch (ErrorResponseException | InsufficientDataException | InternalException | InvalidKeyException | - InvalidResponseException | IOException | NoSuchAlgorithmException | ServerException | - XmlParserException e) { - throw new RuntimeException(e); + } catch (Exception e) { + throw new RuntimeException("文件删除失败", e); } } diff --git a/src/main/java/com/youlai/boot/shared/sms/controller/SmsController.java b/src/main/java/com/youlai/boot/shared/sms/controller/SmsController.java index bb85859d..710601bc 100644 --- a/src/main/java/com/youlai/boot/shared/sms/controller/SmsController.java +++ b/src/main/java/com/youlai/boot/shared/sms/controller/SmsController.java @@ -6,6 +6,7 @@ package com.youlai.boot.shared.sms.controller; * @author Ray * @since 2.10.0 */ + public class SmsController { diff --git a/src/main/java/com/youlai/boot/shared/sms/enums/SmsTypeEnum.java b/src/main/java/com/youlai/boot/shared/sms/enums/SmsTypeEnum.java new file mode 100644 index 00000000..a2aaa1cb --- /dev/null +++ b/src/main/java/com/youlai/boot/shared/sms/enums/SmsTypeEnum.java @@ -0,0 +1,22 @@ +package com.youlai.boot.shared.sms.enums; + +import com.youlai.boot.common.base.IBaseEnum; +import lombok.Getter; + +/** + * 短信类型枚举 + */ +@Getter +public enum SmsTypeEnum implements IBaseEnum { + REGISTER("register", "注册短信验证码"), + LOGIN("login", "登录短信验证码"), + RESET_PASSWORD("reset-password", "重置密码短信验证码"); + + private final String value; + private final String label; + + SmsTypeEnum(String value, String label) { + this.value = value; + this.label = label; + } +} diff --git a/src/main/java/com/youlai/boot/shared/sms/service/SmsService.java b/src/main/java/com/youlai/boot/shared/sms/service/SmsService.java index 85583d07..06d2061b 100644 --- a/src/main/java/com/youlai/boot/shared/sms/service/SmsService.java +++ b/src/main/java/com/youlai/boot/shared/sms/service/SmsService.java @@ -1,11 +1,11 @@ package com.youlai.boot.shared.sms.service; +import com.youlai.boot.shared.sms.enums.SmsTypeEnum; + /** * 短信服务接口层 - *

- * SMS = Short Message Service 短信服务 * - * @author Ray + * @author Ray.Hao * @since 2024/8/17 */ public interface SmsService { @@ -14,9 +14,9 @@ public interface SmsService { * 发送短信 * * @param mobile 手机号 13388886666 - * @param templateCode 短信模板 SMS_194640010 + * @param smsType 短信模板 SMS_194640010 * @param templateParam 模板参数 "[{"code":"123456"}]" * @return boolean 是否发送成功 */ - boolean sendSms(String mobile, String templateCode, String templateParam); + boolean sendSms(String mobile, SmsTypeEnum smsType, String templateParam); } diff --git a/src/main/java/com/youlai/boot/shared/sms/service/impl/AliyunSmsService.java b/src/main/java/com/youlai/boot/shared/sms/service/impl/AliyunSmsService.java index a831bdf3..84915efe 100644 --- a/src/main/java/com/youlai/boot/shared/sms/service/impl/AliyunSmsService.java +++ b/src/main/java/com/youlai/boot/shared/sms/service/impl/AliyunSmsService.java @@ -9,6 +9,7 @@ import com.aliyuncs.exceptions.ServerException; import com.aliyuncs.http.MethodType; import com.aliyuncs.profile.DefaultProfile; import com.youlai.boot.config.property.AliyunSmsProperties; +import com.youlai.boot.shared.sms.enums.SmsTypeEnum; import com.youlai.boot.shared.sms.service.SmsService; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -29,13 +30,15 @@ public class AliyunSmsService implements SmsService { * 发送短信验证码 * * @param mobile 手机号 13388886666 - * @param templateCode 短信模板 SMS_194640010 - * @param templateParam 模板参数 "[{"code":"123456"}]" + * @param smsType 短信模板 SMS_194640010 + * @param templateParam 模板参数 [{"code":"123456"}] * * @return boolean 是否发送成功 */ @Override - public boolean sendSms(String mobile,String templateCode,String templateParam) { + public boolean sendSms(String mobile, SmsTypeEnum smsType, String templateParam) { + + String templateCode = aliyunSmsProperties.getTemplates().get(smsType.getValue()); DefaultProfile profile = DefaultProfile.getProfile(aliyunSmsProperties.getRegionId(), aliyunSmsProperties.getAccessKeyId(), aliyunSmsProperties.getAccessKeySecret()); @@ -65,8 +68,6 @@ public class AliyunSmsService implements SmsService { try { CommonResponse response = client.getCommonResponse(request); return response.getHttpResponse().isSuccess(); - } catch (ServerException e) { - e.printStackTrace(); } catch (ClientException e) { e.printStackTrace(); } diff --git a/src/main/java/com/youlai/boot/system/service/impl/UserServiceImpl.java b/src/main/java/com/youlai/boot/system/service/impl/UserServiceImpl.java index c2abd1e9..0d413931 100644 --- a/src/main/java/com/youlai/boot/system/service/impl/UserServiceImpl.java +++ b/src/main/java/com/youlai/boot/system/service/impl/UserServiceImpl.java @@ -10,7 +10,7 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.youlai.boot.common.constant.RedisConstants; import com.youlai.boot.common.constant.SystemConstants; -import com.youlai.boot.shared.auth.service.TokenService; +import com.youlai.boot.core.security.manager.TokenManager; import com.youlai.boot.system.enums.ContactType; import com.youlai.boot.common.model.Option; import com.youlai.boot.shared.mail.service.MailService; @@ -31,7 +31,6 @@ import com.youlai.boot.system.model.dto.UserExportDTO; import com.youlai.boot.system.model.vo.UserInfoVO; import com.youlai.boot.system.model.vo.UserPageVO; import com.youlai.boot.core.security.service.PermissionService; -import com.youlai.boot.system.service.RoleMenuService; import com.youlai.boot.system.service.RoleService; import com.youlai.boot.system.service.UserRoleService; import com.youlai.boot.system.service.UserService; @@ -62,8 +61,6 @@ public class UserServiceImpl extends ServiceImpl implements Us private final UserRoleService userRoleService; - private final RoleMenuService roleMenuService; - private final RoleService roleService; private final PermissionService permissionService; @@ -76,7 +73,7 @@ public class UserServiceImpl extends ServiceImpl implements Us private final StringRedisTemplate redisTemplate; - private final TokenService tokenService; + private final TokenManager tokenManager; private final UserConverter userConverter; @@ -228,6 +225,21 @@ public class UserServiceImpl extends ServiceImpl implements Us return userAuthInfo; } + + /** + * 根据手机号获取用户认证信息 + * + * @param mobile 手机号 + * @return {@link UserAuthInfo} + */ + @Override + public UserAuthInfo getUserAuthInfoByMobile(String mobile) { + UserAuthInfo userAuthInfo = this.baseMapper.getUserAuthInfoByMobile(mobile); + + return null; + } + + /** * 根据微信 OpenID 注册或绑定用户 *

@@ -365,7 +377,7 @@ public class UserServiceImpl extends ServiceImpl implements Us if (result) { // 加入黑名单,重新登录 String accessToken = SecurityUtils.getTokenFromRequest(); - tokenService.blacklistToken(accessToken); + tokenManager.blacklistToken(accessToken); } return result; } @@ -457,7 +469,7 @@ public class UserServiceImpl extends ServiceImpl implements Us * 修改当前用户邮箱 * * @param form 表单数据 - * @return + * @return true|false */ @Override public boolean bindEmail(EmailBindingForm form) { @@ -473,9 +485,9 @@ public class UserServiceImpl extends ServiceImpl implements Us String email = form.getEmail(); String redisCacheKey = RedisConstants.EMAIL_VERIFICATION_CODE_PREFIX + email; - String cachedVerificationCode = redisTemplate.opsForValue().get(redisCacheKey); + String cachedVerifyCode = redisTemplate.opsForValue().get(redisCacheKey); - if (cachedVerificationCode == null || !inputVerificationCode.equals(cachedVerificationCode)) { + if (!inputVerificationCode.equals(cachedVerifyCode)) { throw new BusinessException("验证码错误"); } diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml index bd6b123e..e6ec9972 100644 --- a/src/main/resources/application-dev.yml +++ b/src/main/resources/application-dev.yml @@ -10,8 +10,8 @@ spring: datasource: type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.cj.jdbc.Driver - url: jdbc:mysql://www.youlai.tech:3306/youlai_boot?zeroDateTimeBehavior=convertToNull&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&autoReconnect=true&allowMultiQueries=true - username: youlai + url: jdbc:mysql://localhost:3306/youlai_boot?zeroDateTimeBehavior=convertToNull&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&autoReconnect=true&allowMultiQueries=true + username: root password: 123456 data: redis: @@ -103,25 +103,25 @@ security: # 文件存储配置 oss: - # OSS 类型 (目前支持aliyun、minio) + # OSS 类型 (目前支持aliyun、minio、local) type: minio # MinIO 对象存储服务 minio: - # 服务Endpoint + # MinIO 服务地址 endpoint: http://localhost:9000 # 访问凭据 access-key: minioadmin # 凭据密钥 secret-key: minioadmin # 存储桶名称 - bucket-name: public - # (可选)自定义域名,如果配置了域名,生成的文件URL是域名格式,未配置则URL则是IP格式 (eg: https://oss.youlai.tech) + bucket-name: youlai + # (可选) 自定义域名:配置后,文件 URL 会使用该域名格式 custom-domain: # 阿里云OSS对象存储服务 aliyun: # 服务Endpoint endpoint: oss-cn-hangzhou.aliyuncs.com - # 访问凭据 + # 访问凭据` access-key-id: your-access-key-id # 凭据密钥 access-key-secret: your-access-key-secret @@ -140,13 +140,13 @@ sms: domain: dysmsapi.aliyuncs.com regionId: cn-shanghai signName: 有来技术 - templateCodes: - # 注册(预留) + templates: + # 注册短信验证码模板 register: SMS_22xxx771 - # 登录(预留) + # 登录短信验证码模板 login: SMS_22xxx772 - # 修改密码 - changePassword: SMS_22xxx773 + # 修改密码短信验证码模板 + reset-password: SMS_22xxx773 # springdoc配置: https://springdoc.org/properties.html springdoc: