From 4350a2c3426145e20035b495e4afe29de1b5a4c2 Mon Sep 17 00:00:00 2001 From: hongawen <83944980@qq.com> Date: Thu, 15 Jun 2023 16:01:50 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BB=A3=E7=A0=81=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../config/AuthorizationServerConfig.java | 12 +- .../njcn/auth/config/WebSecurityConfig.java | 36 +- .../njcn/auth/controller/AuthController.java | 12 +- .../CaptchaTokenGranter.java | 13 +- .../PreAuthenticatedUserDetailsService.java | 5 +- .../security/granter/SmsTokenGranter.java | 92 +++++ .../AbstractSmsAuthenticationProvider.java | 337 ++++++++++++++++++ .../Sm4AuthenticationProvider.java | 2 +- .../provider/SmsAuthenticationProvider.java | 73 ++++ .../token/SmsCodeAuthenticationToken.java | 62 ++++ .../service/CustomUserDetailsService.java | 28 ++ .../auth/service/UserDetailsServiceImpl.java | 16 +- pqs-auth/src/test/java/AuthTest.java | 6 +- .../pojo/constant/SecurityConstants.java | 2 + .../njcn/redis/pojo/enums/RedisKeyEnum.java | 5 + .../com/njcn/user/api/UserFeignClient.java | 11 + .../UserFeignClientFallbackFactory.java | 6 + .../com/njcn/user/enums/UserResponseEnum.java | 1 + .../com/njcn/user/pojo/po/AuthClient.java | 4 +- .../njcn/user/controller/UserController.java | 18 + .../com/njcn/user/service/IUserService.java | 10 + .../user/service/impl/UserServiceImpl.java | 11 + 22 files changed, 720 insertions(+), 42 deletions(-) rename pqs-auth/src/main/java/com/njcn/auth/security/{extension/captcha => granter}/CaptchaTokenGranter.java (90%) rename pqs-auth/src/main/java/com/njcn/auth/security/{extension/refresh => granter}/PreAuthenticatedUserDetailsService.java (91%) create mode 100644 pqs-auth/src/main/java/com/njcn/auth/security/granter/SmsTokenGranter.java create mode 100644 pqs-auth/src/main/java/com/njcn/auth/security/provider/AbstractSmsAuthenticationProvider.java rename pqs-auth/src/main/java/com/njcn/auth/security/{sm4 => provider}/Sm4AuthenticationProvider.java (98%) create mode 100644 pqs-auth/src/main/java/com/njcn/auth/security/provider/SmsAuthenticationProvider.java create mode 100644 pqs-auth/src/main/java/com/njcn/auth/security/token/SmsCodeAuthenticationToken.java create mode 100644 pqs-auth/src/main/java/com/njcn/auth/service/CustomUserDetailsService.java diff --git a/pqs-auth/src/main/java/com/njcn/auth/config/AuthorizationServerConfig.java b/pqs-auth/src/main/java/com/njcn/auth/config/AuthorizationServerConfig.java index 3e69c8402..502beadb2 100644 --- a/pqs-auth/src/main/java/com/njcn/auth/config/AuthorizationServerConfig.java +++ b/pqs-auth/src/main/java/com/njcn/auth/config/AuthorizationServerConfig.java @@ -5,8 +5,9 @@ import cn.hutool.core.util.StrUtil; import com.njcn.auth.filter.CustomClientCredentialsTokenEndpointFilter; import com.njcn.auth.pojo.bo.BusinessUser; import com.njcn.auth.security.clientdetails.ClientDetailsServiceImpl; -import com.njcn.auth.security.extension.captcha.CaptchaTokenGranter; -import com.njcn.auth.security.extension.refresh.PreAuthenticatedUserDetailsService; +import com.njcn.auth.security.granter.CaptchaTokenGranter; +import com.njcn.auth.security.granter.PreAuthenticatedUserDetailsService; +import com.njcn.auth.security.granter.SmsTokenGranter; import com.njcn.auth.service.UserDetailsServiceImpl; import com.njcn.common.pojo.constant.SecurityConstants; import com.njcn.common.pojo.enums.auth.ClientEnum; @@ -89,10 +90,11 @@ public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdap granterList.add(new CaptchaTokenGranter(endpoints.getTokenServices(), endpoints.getClientDetailsService(), endpoints.getOAuth2RequestFactory(), authenticationManager, redisUtil )); - + // 添加短信授权模式授权者 + granterList.add(new SmsTokenGranter(endpoints.getTokenServices(), endpoints.getClientDetailsService(), + endpoints.getOAuth2RequestFactory(), authenticationManager, redisUtil + )); //todo... 后续可以扩展更多授权模式,比如:微信小程序、移动app - - CompositeTokenGranter compositeTokenGranter = new CompositeTokenGranter(granterList); endpoints.authenticationManager(authenticationManager) .accessTokenConverter(jwtAccessTokenConverter()) diff --git a/pqs-auth/src/main/java/com/njcn/auth/config/WebSecurityConfig.java b/pqs-auth/src/main/java/com/njcn/auth/config/WebSecurityConfig.java index edeb8f34a..b04da8287 100644 --- a/pqs-auth/src/main/java/com/njcn/auth/config/WebSecurityConfig.java +++ b/pqs-auth/src/main/java/com/njcn/auth/config/WebSecurityConfig.java @@ -1,6 +1,7 @@ package com.njcn.auth.config; -import com.njcn.auth.security.sm4.Sm4AuthenticationProvider; +import com.njcn.auth.security.provider.Sm4AuthenticationProvider; +import com.njcn.auth.security.provider.SmsAuthenticationProvider; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.context.annotation.Bean; @@ -29,6 +30,8 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter { private final Sm4AuthenticationProvider sm4AuthenticationProvider; + private final SmsAuthenticationProvider smsAuthenticationProvider; + @Override protected void configure(HttpSecurity http) throws Exception { @@ -63,21 +66,6 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter { - - /** - * 用户名密码认证授权提供者 - * - * @return - */ - @Bean - public DaoAuthenticationProvider daoAuthenticationProvider() { - DaoAuthenticationProvider provider = new DaoAuthenticationProvider(); - provider.setUserDetailsService(sysUserDetailsService); - provider.setPasswordEncoder(passwordEncoder()); - provider.setHideUserNotFoundExceptions(false); // 是否隐藏用户不存在异常,默认:true-隐藏;false-抛出异常; - return provider; - } - /** * 重写父类自定义AuthenticationManager 将provider注入进去 * 当然我们也可以考虑不重写 在父类的manager里面注入provider @@ -85,10 +73,24 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Bean @Override protected AuthenticationManager authenticationManager(){ - return new ProviderManager(sm4AuthenticationProvider); + return new ProviderManager(sm4AuthenticationProvider,smsAuthenticationProvider); } + + /** + * 用户名密码认证授权提供者 + */ + @Bean + public DaoAuthenticationProvider daoAuthenticationProvider() { + DaoAuthenticationProvider provider = new DaoAuthenticationProvider(); + provider.setUserDetailsService(sysUserDetailsService); + provider.setPasswordEncoder(passwordEncoder()); + // 是否隐藏用户不存在异常,默认:true-隐藏;false-抛出异常; + provider.setHideUserNotFoundExceptions(false); + return provider; + } + /** * 密码编码器 *

diff --git a/pqs-auth/src/main/java/com/njcn/auth/controller/AuthController.java b/pqs-auth/src/main/java/com/njcn/auth/controller/AuthController.java index ef1fbc0bf..e49cd2e5d 100644 --- a/pqs-auth/src/main/java/com/njcn/auth/controller/AuthController.java +++ b/pqs-auth/src/main/java/com/njcn/auth/controller/AuthController.java @@ -75,15 +75,19 @@ public class AuthController extends BaseController { @ApiImplicitParam(name = SecurityConstants.USERNAME, value = "登录用户名"), @ApiImplicitParam(name = SecurityConstants.PASSWORD, value = "登录密码"), @ApiImplicitParam(name = SecurityConstants.IMAGE_CODE, value = "图形验证码"), + @ApiImplicitParam(name = SecurityConstants.PHONE, value = "手机号"), + @ApiImplicitParam(name = SecurityConstants.SMS_CODE, value = "短信验证码"), }) @PostMapping("/token") public Object postAccessToken(@ApiIgnore Principal principal, @RequestParam @ApiIgnore Map parameters) throws HttpRequestMethodNotSupportedException { String methodDescribe = getMethodDescribe("postAccessToken"); String username = parameters.get(SecurityConstants.USERNAME); String grantType = parameters.get(SecurityConstants.GRANT_TYPE); - //正式环境需删除,均是加密的用户名 - if (!grantType.equalsIgnoreCase(SecurityConstants.PASSWORD)) { + if (grantType.equalsIgnoreCase(SecurityConstants.GRANT_CAPTCHA)) { username = DesUtils.aesDecrypt(username); + }else if(grantType.equalsIgnoreCase(SecurityConstants.GRANT_SMS_CODE)){ + //短信方式登录,将手机号赋值为用户名 + username = parameters.get(SecurityConstants.PHONE); } if (grantType.equalsIgnoreCase(SecurityConstants.REFRESH_TOKEN_KEY)) { //如果是刷新token,需要去黑名单校验 @@ -92,7 +96,9 @@ public class AuthController extends BaseController { RequestUtil.saveLoginName(username); OAuth2AccessToken oAuth2AccessToken = tokenEndpoint.postAccessToken(principal, parameters).getBody(); //用户的登录名&密码校验成功后,判断当前该用户是否可以正常使用系统 - userFeignClient.judgeUserStatus(username); + if(!grantType.equalsIgnoreCase(SecurityConstants.GRANT_SMS_CODE)){ + userFeignClient.judgeUserStatus(username); + } //登录成功后,记录token信息,并处理踢人效果 userTokenService.recordUserInfo(oAuth2AccessToken,RequestUtil.getRealIp()); if (!grantType.equalsIgnoreCase(SecurityConstants.PASSWORD)) { diff --git a/pqs-auth/src/main/java/com/njcn/auth/security/extension/captcha/CaptchaTokenGranter.java b/pqs-auth/src/main/java/com/njcn/auth/security/granter/CaptchaTokenGranter.java similarity index 90% rename from pqs-auth/src/main/java/com/njcn/auth/security/extension/captcha/CaptchaTokenGranter.java rename to pqs-auth/src/main/java/com/njcn/auth/security/granter/CaptchaTokenGranter.java index 5abe29f8a..b5aa9f613 100644 --- a/pqs-auth/src/main/java/com/njcn/auth/security/extension/captcha/CaptchaTokenGranter.java +++ b/pqs-auth/src/main/java/com/njcn/auth/security/granter/CaptchaTokenGranter.java @@ -1,4 +1,4 @@ -package com.njcn.auth.security.extension.captcha; +package com.njcn.auth.security.granter; import cn.hutool.core.util.StrUtil; import com.njcn.common.pojo.constant.SecurityConstants; @@ -37,7 +37,7 @@ public class CaptchaTokenGranter extends AbstractTokenGranter { OAuth2RequestFactory requestFactory, AuthenticationManager authenticationManager, RedisUtil redisUtil ) { - //SecurityConstants.GRANT_CAPTCHA:申明为授权码模式 + //SecurityConstants.GRANT_CAPTCHA:申明为验证码模式 super(tokenServices, clientDetailsService, requestFactory, SecurityConstants.GRANT_CAPTCHA); this.authenticationManager = authenticationManager; this.redisUtil = redisUtil; @@ -49,6 +49,7 @@ public class CaptchaTokenGranter extends AbstractTokenGranter { String username = parameters.get(SecurityConstants.USERNAME); username = DesUtils.aesDecrypt(username); String verifyCode = parameters.get(SecurityConstants.VERIFY_CODE); + //判断是否需要校验图形验证码,用户错误后,前端要求用户填写验证码 if(StrUtil.isEmpty(verifyCode)||verifyCode.equals("1")){ if (!judgeImageCode(parameters.get(SecurityConstants.IMAGE_CODE), RequestUtil.getRequest())) { throw new BusinessException(UserResponseEnum.LOGIN_WRONG_CODE); @@ -58,18 +59,20 @@ public class CaptchaTokenGranter extends AbstractTokenGranter { String ip = RequestUtil.getRequest().getHeader(SecurityConstants.REQUEST_HEADER_KEY_CLIENT_REAL_IP); //密码处理 String privateKey = redisUtil.getStringByKey(username + ip); -// //秘钥用完即删 + //秘钥用完即删 redisUtil.delete(username + ip); //对SM2解密面进行验证 password = Sm2.getPasswordSM2Verify(privateKey, password); if (StrUtil.isBlankIfStr(password)) { throw new BusinessException(UserResponseEnum.PASSWORD_TRANSPORT_ERROR); } - //正式环境放行 + //1、不将密码放入details内,防止密码泄漏 parameters.remove(SecurityConstants.PASSWORD); + //2、组装用户密码模式的认证信息 Authentication userAuth = new UsernamePasswordAuthenticationToken(username, password); ((AbstractAuthenticationToken) userAuth).setDetails(parameters); try { + //3、认证组装好的信息 userAuth = authenticationManager.authenticate(userAuth); } catch (AccountStatusException | BadCredentialsException ase) { //covers expired, locked, disabled cases @@ -77,7 +80,7 @@ public class CaptchaTokenGranter extends AbstractTokenGranter { } // If the username/password are wrong the spec says we should send 400/invalid grant if (userAuth == null || !userAuth.isAuthenticated()) { - throw new InvalidGrantException("Could not authenticate user: " + username); + throw new InvalidGrantException("无法认证用户: " + username); } OAuth2Request storedOauth2Request = getRequestFactory().createOAuth2Request(client, tokenRequest); diff --git a/pqs-auth/src/main/java/com/njcn/auth/security/extension/refresh/PreAuthenticatedUserDetailsService.java b/pqs-auth/src/main/java/com/njcn/auth/security/granter/PreAuthenticatedUserDetailsService.java similarity index 91% rename from pqs-auth/src/main/java/com/njcn/auth/security/extension/refresh/PreAuthenticatedUserDetailsService.java rename to pqs-auth/src/main/java/com/njcn/auth/security/granter/PreAuthenticatedUserDetailsService.java index af5cac7dc..a3d020f03 100644 --- a/pqs-auth/src/main/java/com/njcn/auth/security/extension/refresh/PreAuthenticatedUserDetailsService.java +++ b/pqs-auth/src/main/java/com/njcn/auth/security/granter/PreAuthenticatedUserDetailsService.java @@ -1,7 +1,5 @@ -package com.njcn.auth.security.extension.refresh; +package com.njcn.auth.security.granter; -import com.njcn.common.pojo.constant.SecurityConstants; -import com.njcn.common.pojo.enums.auth.AuthenticationMethodEnum; import com.njcn.web.utils.RequestUtil; import lombok.NoArgsConstructor; import org.springframework.beans.factory.InitializingBean; @@ -11,7 +9,6 @@ import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer; -import org.springframework.stereotype.Component; import org.springframework.util.Assert; import java.util.Map; diff --git a/pqs-auth/src/main/java/com/njcn/auth/security/granter/SmsTokenGranter.java b/pqs-auth/src/main/java/com/njcn/auth/security/granter/SmsTokenGranter.java new file mode 100644 index 000000000..13bc1037f --- /dev/null +++ b/pqs-auth/src/main/java/com/njcn/auth/security/granter/SmsTokenGranter.java @@ -0,0 +1,92 @@ +package com.njcn.auth.security.granter; + +import cn.hutool.core.util.StrUtil; +import com.njcn.auth.security.token.SmsCodeAuthenticationToken; +import com.njcn.common.pojo.constant.SecurityConstants; +import com.njcn.common.pojo.exception.BusinessException; +import com.njcn.redis.pojo.enums.RedisKeyEnum; +import com.njcn.redis.utils.RedisUtil; +import com.njcn.user.enums.UserResponseEnum; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.authentication.*; +import org.springframework.security.core.Authentication; +import org.springframework.security.oauth2.common.exceptions.InvalidGrantException; +import org.springframework.security.oauth2.provider.*; +import org.springframework.security.oauth2.provider.token.AbstractTokenGranter; +import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices; + +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * @author hongawen + * @version 1.0.0 + * @date 2021年12月15日 14:23 + */ +@Slf4j +public class SmsTokenGranter extends AbstractTokenGranter { + + private final AuthenticationManager authenticationManager; + + private final RedisUtil redisUtil; + + public SmsTokenGranter(AuthorizationServerTokenServices tokenServices, ClientDetailsService clientDetailsService, + OAuth2RequestFactory requestFactory, AuthenticationManager authenticationManager, + RedisUtil redisUtil + ) { + //SecurityConstants.GRANT_CAPTCHA:申明为手机短信模式 + super(tokenServices, clientDetailsService, requestFactory, SecurityConstants.GRANT_SMS_CODE); + this.authenticationManager = authenticationManager; + this.redisUtil = redisUtil; + } + + @Override + protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) { + Map parameters = new LinkedHashMap<>(tokenRequest.getRequestParameters()); + String phone = parameters.get(SecurityConstants.PHONE); + String smsCode = parameters.get(SecurityConstants.SMS_CODE); + if (StrUtil.isBlank(phone)) { + throw new BusinessException(UserResponseEnum.REGISTER_PHONE_WRONG); + } + if (judgeSmsCode(phone, smsCode)) { + throw new BusinessException(UserResponseEnum.LOGIN_WRONG_CODE); + } + //2、组装用户手机号认证信息 + Authentication userAuth = new SmsCodeAuthenticationToken(phone, null); + ((AbstractAuthenticationToken) userAuth).setDetails(parameters); + try { + //3、认证组装好的信息 + userAuth = authenticationManager.authenticate(userAuth); + } catch (AccountStatusException | BadCredentialsException ase) { + throw new InvalidGrantException(ase.getMessage()); + } + if (userAuth == null || !userAuth.isAuthenticated()) { + throw new InvalidGrantException("无法认证用户: " + phone); + } + + OAuth2Request storedOauth2Request = getRequestFactory().createOAuth2Request(client, tokenRequest); + return new OAuth2Authentication(storedOauth2Request, userAuth); + } + + /** + * 验证用户的短信验证码是否正确 + * + * @param phone 手机号 + * @param smsCode 用户输入的短信验证码 + * @return boolean + * @author hongawen + * @date 2023/6/14 15:25 + */ + private boolean judgeSmsCode(String phone, String smsCode) { + if (StrUtil.isBlankIfStr(smsCode)) { + return false; + } + String key = RedisKeyEnum.SMS_LOGIN_KEY.getKey().concat(phone); + String redisImageCode = redisUtil.getStringByKey(key); + if (smsCode.equalsIgnoreCase(redisImageCode)) { + redisUtil.delete(key); + return true; + } + return false; + } +} diff --git a/pqs-auth/src/main/java/com/njcn/auth/security/provider/AbstractSmsAuthenticationProvider.java b/pqs-auth/src/main/java/com/njcn/auth/security/provider/AbstractSmsAuthenticationProvider.java new file mode 100644 index 000000000..dadcebdbe --- /dev/null +++ b/pqs-auth/src/main/java/com/njcn/auth/security/provider/AbstractSmsAuthenticationProvider.java @@ -0,0 +1,337 @@ +package com.njcn.auth.security.provider; + +import com.njcn.auth.security.token.SmsCodeAuthenticationToken; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.context.MessageSource; +import org.springframework.context.MessageSourceAware; +import org.springframework.context.support.MessageSourceAccessor; +import org.springframework.security.authentication.*; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.SpringSecurityMessageSource; +import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper; +import org.springframework.security.core.authority.mapping.NullAuthoritiesMapper; +import org.springframework.security.core.userdetails.UserCache; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsChecker; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.security.core.userdetails.cache.NullUserCache; +import org.springframework.util.Assert; + +/** + * @author hongawen + * @version 1.0.0 + * @date 2023年06月15日 10:08 + */ +public abstract class AbstractSmsAuthenticationProvider implements AuthenticationProvider, InitializingBean, MessageSourceAware { + + protected final Log logger = LogFactory.getLog(getClass()); + + + + protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor(); + private UserCache userCache = new NullUserCache(); + private boolean forcePrincipalAsString = false; + protected boolean hideUserNotFoundExceptions = true; + private UserDetailsChecker preAuthenticationChecks = new AbstractSmsAuthenticationProvider.DefaultPreAuthenticationChecks(); + private UserDetailsChecker postAuthenticationChecks = new AbstractSmsAuthenticationProvider.DefaultPostAuthenticationChecks(); + private GrantedAuthoritiesMapper authoritiesMapper = new NullAuthoritiesMapper(); + + // ~ Methods + // ======================================================================================================== + + /** + * Allows subclasses to perform any additional checks of a returned (or cached) + * UserDetails for a given authentication request. Generally a subclass + * will at least compare the {@link Authentication#getCredentials()} with a + * {@link UserDetails#getPassword()}. If custom logic is needed to compare additional + * properties of UserDetails and/or + * SmsCodeAuthenticationToken, these should also appear in this + * method. + * + * @param userDetails as retrieved from the + * {@link #retrieveUser(String, SmsCodeAuthenticationToken)} or + * UserCache + * @param authentication the current request that needs to be authenticated + * + * @throws AuthenticationException AuthenticationException if the credentials could + * not be validated (generally a BadCredentialsException, an + * AuthenticationServiceException) + */ + protected abstract void additionalAuthenticationChecks(UserDetails userDetails, + SmsCodeAuthenticationToken authentication) + throws AuthenticationException; + + public final void afterPropertiesSet() throws Exception { + Assert.notNull(this.userCache, "A user cache must be set"); + Assert.notNull(this.messages, "A message source must be set"); + doAfterPropertiesSet(); + } + + public Authentication authenticate(Authentication authentication) + throws AuthenticationException { + Assert.isInstanceOf(SmsCodeAuthenticationToken.class, authentication, + () -> messages.getMessage( + "AbstractSmsAuthenticationProvider.onlySupports", + "Only SmsCodeAuthenticationToken is supported")); + + // Determine username + String username = (authentication.getPrincipal() == null) ? "NONE_PROVIDED" + : authentication.getName(); + + boolean cacheWasUsed = true; + UserDetails user = this.userCache.getUserFromCache(username); + + if (user == null) { + cacheWasUsed = false; + + try { + user = retrieveUser(username, + (SmsCodeAuthenticationToken) authentication); + } + catch (UsernameNotFoundException notFound) { + logger.debug("User '" + username + "' not found"); + + if (hideUserNotFoundExceptions) { + throw new BadCredentialsException(messages.getMessage( + "AbstractSmsAuthenticationProvider.badCredentials", + "Bad credentials")); + } + else { + throw notFound; + } + } + + Assert.notNull(user, + "retrieveUser returned null - a violation of the interface contract"); + } + + try { + preAuthenticationChecks.check(user); + additionalAuthenticationChecks(user, + (SmsCodeAuthenticationToken) authentication); + } + catch (AuthenticationException exception) { + if (cacheWasUsed) { + // There was a problem, so try again after checking + // we're using latest data (i.e. not from the cache) + cacheWasUsed = false; + user = retrieveUser(username, + (SmsCodeAuthenticationToken) authentication); + preAuthenticationChecks.check(user); + additionalAuthenticationChecks(user, + (SmsCodeAuthenticationToken) authentication); + } + else { + throw exception; + } + } + + postAuthenticationChecks.check(user); + + if (!cacheWasUsed) { + this.userCache.putUserInCache(user); + } + + Object principalToReturn = user; + + if (forcePrincipalAsString) { + principalToReturn = user.getUsername(); + } + + return createSuccessAuthentication(principalToReturn, authentication, user); + } + + /** + * Creates a successful {@link Authentication} object. + *

+ * Protected so subclasses can override. + *

+ *

+ * Subclasses will usually store the original credentials the user supplied (not + * salted or encoded passwords) in the returned Authentication object. + *

+ * + * @param principal that should be the principal in the returned object (defined by + * the {@link #isForcePrincipalAsString()} method) + * @param authentication that was presented to the provider for validation + * @param user that was loaded by the implementation + * + * @return the successful authentication token + */ + protected Authentication createSuccessAuthentication(Object principal, + Authentication authentication, UserDetails user) { + // Ensure we return the original credentials the user supplied, + // so subsequent attempts are successful even with encoded passwords. + // Also ensure we return the original getDetails(), so that future + // authentication events after cache expiry contain the details + SmsCodeAuthenticationToken result = new SmsCodeAuthenticationToken( + principal, authentication.getCredentials(), + authoritiesMapper.mapAuthorities(user.getAuthorities())); + result.setDetails(authentication.getDetails()); + + return result; + } + + protected void doAfterPropertiesSet() throws Exception { + } + + public UserCache getUserCache() { + return userCache; + } + + public boolean isForcePrincipalAsString() { + return forcePrincipalAsString; + } + + public boolean isHideUserNotFoundExceptions() { + return hideUserNotFoundExceptions; + } + + /** + * Allows subclasses to actually retrieve the UserDetails from an + * implementation-specific location, with the option of throwing an + * AuthenticationException immediately if the presented credentials are + * incorrect (this is especially useful if it is necessary to bind to a resource as + * the user in order to obtain or generate a UserDetails). + *

+ * Subclasses are not required to perform any caching, as the + * AbstractSmsAuthenticationProvider will by default cache the + * UserDetails. The caching of UserDetails does present + * additional complexity as this means subsequent requests that rely on the cache will + * need to still have their credentials validated, even if the correctness of + * credentials was assured by subclasses adopting a binding-based strategy in this + * method. Accordingly it is important that subclasses either disable caching (if they + * want to ensure that this method is the only method that is capable of + * authenticating a request, as no UserDetails will ever be cached) or + * ensure subclasses implement + * {@link #additionalAuthenticationChecks(UserDetails, SmsCodeAuthenticationToken)} + * to compare the credentials of a cached UserDetails with subsequent + * authentication requests. + *

+ *

+ * Most of the time subclasses will not perform credentials inspection in this method, + * instead performing it in + * {@link #additionalAuthenticationChecks(UserDetails, SmsCodeAuthenticationToken)} + * so that code related to credentials validation need not be duplicated across two + * methods. + *

+ * + * @param username The username to retrieve + * @param authentication The authentication request, which subclasses may + * need to perform a binding-based retrieval of the UserDetails + * + * @return the user information (never null - instead an exception should + * the thrown) + * + * @throws AuthenticationException if the credentials could not be validated + * (generally a BadCredentialsException, an + * AuthenticationServiceException or + * UsernameNotFoundException) + */ + protected abstract UserDetails retrieveUser(String username, + SmsCodeAuthenticationToken authentication) + throws AuthenticationException; + + public void setForcePrincipalAsString(boolean forcePrincipalAsString) { + this.forcePrincipalAsString = forcePrincipalAsString; + } + + /** + * By default the AbstractSmsAuthenticationProvider throws a + * BadCredentialsException if a username is not found or the password is + * incorrect. Setting this property to false will cause + * UsernameNotFoundExceptions to be thrown instead for the former. Note + * this is considered less secure than throwing BadCredentialsException + * for both exceptions. + * + * @param hideUserNotFoundExceptions set to false if you wish + * UsernameNotFoundExceptions to be thrown instead of the non-specific + * BadCredentialsException (defaults to true) + */ + public void setHideUserNotFoundExceptions(boolean hideUserNotFoundExceptions) { + this.hideUserNotFoundExceptions = hideUserNotFoundExceptions; + } + + public void setMessageSource(MessageSource messageSource) { + this.messages = new MessageSourceAccessor(messageSource); + } + + public void setUserCache(UserCache userCache) { + this.userCache = userCache; + } + + public boolean supports(Class authentication) { + return (SmsCodeAuthenticationToken.class + .isAssignableFrom(authentication)); + } + + protected UserDetailsChecker getPreAuthenticationChecks() { + return preAuthenticationChecks; + } + + /** + * Sets the policy will be used to verify the status of the loaded + * UserDetails before validation of the credentials takes place. + * + * @param preAuthenticationChecks strategy to be invoked prior to authentication. + */ + public void setPreAuthenticationChecks(UserDetailsChecker preAuthenticationChecks) { + this.preAuthenticationChecks = preAuthenticationChecks; + } + + protected UserDetailsChecker getPostAuthenticationChecks() { + return postAuthenticationChecks; + } + + public void setPostAuthenticationChecks(UserDetailsChecker postAuthenticationChecks) { + this.postAuthenticationChecks = postAuthenticationChecks; + } + + public void setAuthoritiesMapper(GrantedAuthoritiesMapper authoritiesMapper) { + this.authoritiesMapper = authoritiesMapper; + } + + private class DefaultPreAuthenticationChecks implements UserDetailsChecker { + public void check(UserDetails user) { + if (!user.isAccountNonLocked()) { + logger.debug("User account is locked"); + + throw new LockedException(messages.getMessage( + "AbstractSmsAuthenticationProvider.locked", + "User account is locked")); + } + + if (!user.isEnabled()) { + logger.debug("User account is disabled"); + + throw new DisabledException(messages.getMessage( + "AbstractSmsAuthenticationProvider.disabled", + "User is disabled")); + } + + if (!user.isAccountNonExpired()) { + logger.debug("User account is expired"); + + throw new AccountExpiredException(messages.getMessage( + "AbstractSmsAuthenticationProvider.expired", + "User account has expired")); + } + } + } + + private class DefaultPostAuthenticationChecks implements UserDetailsChecker { + public void check(UserDetails user) { + if (!user.isCredentialsNonExpired()) { + logger.debug("User account credentials have expired"); + + throw new CredentialsExpiredException(messages.getMessage( + "AbstractSmsAuthenticationProvider.credentialsExpired", + "User credentials have expired")); + } + } + } + } + diff --git a/pqs-auth/src/main/java/com/njcn/auth/security/sm4/Sm4AuthenticationProvider.java b/pqs-auth/src/main/java/com/njcn/auth/security/provider/Sm4AuthenticationProvider.java similarity index 98% rename from pqs-auth/src/main/java/com/njcn/auth/security/sm4/Sm4AuthenticationProvider.java rename to pqs-auth/src/main/java/com/njcn/auth/security/provider/Sm4AuthenticationProvider.java index 9218875ca..8492ca17d 100644 --- a/pqs-auth/src/main/java/com/njcn/auth/security/sm4/Sm4AuthenticationProvider.java +++ b/pqs-auth/src/main/java/com/njcn/auth/security/provider/Sm4AuthenticationProvider.java @@ -1,4 +1,4 @@ -package com.njcn.auth.security.sm4; +package com.njcn.auth.security.provider; import com.njcn.auth.pojo.bo.BusinessUser; import com.njcn.common.utils.sm.Sm4Utils; diff --git a/pqs-auth/src/main/java/com/njcn/auth/security/provider/SmsAuthenticationProvider.java b/pqs-auth/src/main/java/com/njcn/auth/security/provider/SmsAuthenticationProvider.java new file mode 100644 index 000000000..39e39d1ca --- /dev/null +++ b/pqs-auth/src/main/java/com/njcn/auth/security/provider/SmsAuthenticationProvider.java @@ -0,0 +1,73 @@ +package com.njcn.auth.security.provider; + +import com.njcn.auth.security.token.SmsCodeAuthenticationToken; +import com.njcn.auth.service.UserDetailsServiceImpl; +import com.njcn.common.pojo.exception.BusinessException; +import com.njcn.user.enums.UserResponseEnum; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.stereotype.Component; + +/** + * 手机短信码验证完后,返回用户的 + * @author hongawen + * @version 1.0.0 + * @date 2021年06月08日 15:43 + */ +@Slf4j +@Component +@AllArgsConstructor +public class SmsAuthenticationProvider extends AbstractSmsAuthenticationProvider { + + private final UserDetailsServiceImpl userDetailsService; + + + /** + * 校验密码有效性. + * 因为手机号验证码登录,验证码没问题后,密码无需校验,直接返回该用户的token信息便可以 + * + * @param userDetails 用户详细信息 + * @param authentication 用户登录的密码 + * @throws AuthenticationException . + */ + @Override + protected void additionalAuthenticationChecks( + UserDetails userDetails, SmsCodeAuthenticationToken authentication) + throws AuthenticationException { + + } + + /** + * 获取用户 + * + * @param phone 手机号 + * @param authentication 认证token + * @throws AuthenticationException . + */ + @Override + protected UserDetails retrieveUser( + String phone, SmsCodeAuthenticationToken authentication) + throws AuthenticationException { + //根据手机号获取用户信息 + UserDetails loadedUser = userDetailsService.loadUserByPhone(phone); + if (loadedUser == null) { + throw new BusinessException(UserResponseEnum.LOGIN_PHONE_NOT_REGISTER); + } + return loadedUser; + } + + + /** + * 授权持久化. + */ + @Override + protected Authentication createSuccessAuthentication(Object principal, + Authentication authentication, UserDetails user) { + return super.createSuccessAuthentication(principal, authentication, user); + } + + +} \ No newline at end of file diff --git a/pqs-auth/src/main/java/com/njcn/auth/security/token/SmsCodeAuthenticationToken.java b/pqs-auth/src/main/java/com/njcn/auth/security/token/SmsCodeAuthenticationToken.java new file mode 100644 index 000000000..e249b5d6e --- /dev/null +++ b/pqs-auth/src/main/java/com/njcn/auth/security/token/SmsCodeAuthenticationToken.java @@ -0,0 +1,62 @@ +package com.njcn.auth.security.token; + +import org.springframework.security.authentication.AbstractAuthenticationToken; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.util.Assert; + +import java.util.Collection; + +/** + * UsernamePasswordAuthenticationToken 一样, + * 继承 AbstractAuthenticationToken 抽象类, + * 需要实现 getPrincipal 和 getCredentials 两个方法。 + * 在用户名/密码认证中,principal 表示用户名, + * credentials 表示密码,在此,我们可以让它们指代手机号和验证码。 + * + * @author hongawen + * @version 1.0.0 + * @date 2023年06月14日 16:25 + */ +public class SmsCodeAuthenticationToken extends AbstractAuthenticationToken { + + private final Object principal; + + private Object credentials; + + public SmsCodeAuthenticationToken(Object principal, Object credentials) { + super(null); + this.principal = principal; + this.credentials = credentials; + setAuthenticated(false); + } + public SmsCodeAuthenticationToken(Object principal, Object credentials, + Collection authorities) { + super(authorities); + this.principal = principal; + this.credentials = credentials; + super.setAuthenticated(true); + } + + @Override + public Object getCredentials() { + return this.credentials; + } + + @Override + public Object getPrincipal() { + return this.principal; + } + + @Override + public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException { + Assert.isTrue(!isAuthenticated, + "Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead"); + super.setAuthenticated(false); + } + + @Override + public void eraseCredentials() { + super.eraseCredentials(); + this.credentials = null; + } +} diff --git a/pqs-auth/src/main/java/com/njcn/auth/service/CustomUserDetailsService.java b/pqs-auth/src/main/java/com/njcn/auth/service/CustomUserDetailsService.java new file mode 100644 index 000000000..2addd2849 --- /dev/null +++ b/pqs-auth/src/main/java/com/njcn/auth/service/CustomUserDetailsService.java @@ -0,0 +1,28 @@ +package com.njcn.auth.service; + +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; + +/** + * @author hongawen + * @version 1.0.0 + * @date 2023年06月15日 10:26 + */ +public interface CustomUserDetailsService extends UserDetailsService { + + /** + * @param username 用户名 + * @return 用户信息 + * @throws UsernameNotFoundException + */ + UserDetails loadUserByUsername(String username) throws UsernameNotFoundException; + + /** + * @param phone 手机号 + * @return 用户信息 + * @throws UsernameNotFoundException + */ + UserDetails loadUserByPhone(String phone) throws UsernameNotFoundException; + +} diff --git a/pqs-auth/src/main/java/com/njcn/auth/service/UserDetailsServiceImpl.java b/pqs-auth/src/main/java/com/njcn/auth/service/UserDetailsServiceImpl.java index ca5a1a767..b581b5992 100644 --- a/pqs-auth/src/main/java/com/njcn/auth/service/UserDetailsServiceImpl.java +++ b/pqs-auth/src/main/java/com/njcn/auth/service/UserDetailsServiceImpl.java @@ -25,7 +25,7 @@ import org.springframework.stereotype.Service; @Slf4j @Service @AllArgsConstructor -public class UserDetailsServiceImpl implements UserDetailsService { +public class UserDetailsServiceImpl implements CustomUserDetailsService { private final UserFeignClient userFeignClient; @@ -44,4 +44,18 @@ public class UserDetailsServiceImpl implements UserDetailsService { return businessUser; } + @Override + public UserDetails loadUserByPhone(String phone) throws UsernameNotFoundException { + String clientId = RequestUtil.getOAuth2ClientId(); + BusinessUser businessUser = new BusinessUser(phone, null, null); + businessUser.setClientId(clientId); + HttpResult result = userFeignClient.getUserByPhone(phone); + LogUtil.njcnDebug(log, "用户验证码认证时,用户名:{}获取用户信息:{}", phone, result.toString()); + //成功获取用户信息 + UserDTO userDTO = result.getData(); + BeanUtil.copyProperties(userDTO,businessUser,true); + businessUser.setAuthorities(AuthorityUtils.commaSeparatedStringToAuthorityList(String.join(",", userDTO.getRoleName()))); + return businessUser; + } + } diff --git a/pqs-auth/src/test/java/AuthTest.java b/pqs-auth/src/test/java/AuthTest.java index 2319a9c70..bf929f646 100644 --- a/pqs-auth/src/test/java/AuthTest.java +++ b/pqs-auth/src/test/java/AuthTest.java @@ -26,16 +26,14 @@ public class AuthTest extends BaseJunitTest { @SneakyThrows @Test public void test(){ - String userUrl = "http://127.0.0.1:10214/oauth/token"; + String userUrl = "http://127.0.0.1:10215/pqs-auth/oauth/token"; UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(userUrl) .queryParam("grant_type", "password") - .queryParam("client_id", "njcn_app") + .queryParam("client_id", "njcn") .queryParam("client_secret", "njcnpqs") .queryParam("username", "root") .queryParam("password", "@#001njcnpqs"); URI uri = builder.build().encode().toUri(); - JSONObject jsonObject = new JSONObject(); - jsonObject.set("",""); ResponseEntity userEntity = RestTemplateUtil.post(uri, OAuth2AccessToken.class); } diff --git a/pqs-common/common-core/src/main/java/com/njcn/common/pojo/constant/SecurityConstants.java b/pqs-common/common-core/src/main/java/com/njcn/common/pojo/constant/SecurityConstants.java index cee58637b..a8e86df3e 100644 --- a/pqs-common/common-core/src/main/java/com/njcn/common/pojo/constant/SecurityConstants.java +++ b/pqs-common/common-core/src/main/java/com/njcn/common/pojo/constant/SecurityConstants.java @@ -94,6 +94,8 @@ public interface SecurityConstants { String REFRESH_TOKEN = "refresh_token"; String USERNAME = "username"; String PASSWORD = "password"; + String PHONE = "phone"; + String SMS_CODE = "smsCode"; String IMAGE_CODE = "imageCode"; String VERIFY_CODE = "verifyCode"; diff --git a/pqs-common/common-redis/src/main/java/com/njcn/redis/pojo/enums/RedisKeyEnum.java b/pqs-common/common-redis/src/main/java/com/njcn/redis/pojo/enums/RedisKeyEnum.java index fa6e247c8..e9a745562 100644 --- a/pqs-common/common-redis/src/main/java/com/njcn/redis/pojo/enums/RedisKeyEnum.java +++ b/pqs-common/common-redis/src/main/java/com/njcn/redis/pojo/enums/RedisKeyEnum.java @@ -16,6 +16,11 @@ public enum RedisKeyEnum { ROLE_FUNCTION_KEY("ROLES_FUNCTIONS",-1L), PUBLIC_FUNCTIONS_KEY("PUBLIC_FUNCTIONS",-1L), + /*** + * app短信验证码,保存10分钟缓存 + */ + SMS_LOGIN_KEY("SMS_LOGIN",10L), + /** * 终端信息查询缓存的公共key前缀 */ diff --git a/pqs-user/user-api/src/main/java/com/njcn/user/api/UserFeignClient.java b/pqs-user/user-api/src/main/java/com/njcn/user/api/UserFeignClient.java index 8041add36..a49cc29a1 100644 --- a/pqs-user/user-api/src/main/java/com/njcn/user/api/UserFeignClient.java +++ b/pqs-user/user-api/src/main/java/com/njcn/user/api/UserFeignClient.java @@ -31,6 +31,15 @@ public interface UserFeignClient { @GetMapping("/getUserByName/{loginName}") HttpResult getUserByName(@PathVariable("loginName") String loginName); + /** + * 根据手机号查询用户信息 + * + * @param phone 登录名 + * @return 用户基本信息 + */ + @GetMapping("/getUserByPhone/{phone}") + HttpResult getUserByPhone(@PathVariable("phone")String phone); + /** * 认证后根据用户名判断用户状态 * @param loginName 登录名 @@ -55,4 +64,6 @@ public interface UserFeignClient { */ @PostMapping("/userByIdList") HttpResult> getUserByIdList(@RequestBody List ids); + + } diff --git a/pqs-user/user-api/src/main/java/com/njcn/user/api/fallback/UserFeignClientFallbackFactory.java b/pqs-user/user-api/src/main/java/com/njcn/user/api/fallback/UserFeignClientFallbackFactory.java index 27e364243..c903de668 100644 --- a/pqs-user/user-api/src/main/java/com/njcn/user/api/fallback/UserFeignClientFallbackFactory.java +++ b/pqs-user/user-api/src/main/java/com/njcn/user/api/fallback/UserFeignClientFallbackFactory.java @@ -43,6 +43,12 @@ public class UserFeignClientFallbackFactory implements FallbackFactory getUserByPhone(String phone) { + log.error("{}异常,降级处理,异常为:{}","根据手机号查询用户信息",cause.toString()); + throw new BusinessException(finalExceptionEnum); + } + @Override public HttpResult judgeUserStatus(String loginName) { log.error("{}异常,降级处理,异常为:{}","认证后根据用户名判断用户状态",cause.toString()); diff --git a/pqs-user/user-api/src/main/java/com/njcn/user/enums/UserResponseEnum.java b/pqs-user/user-api/src/main/java/com/njcn/user/enums/UserResponseEnum.java index 59497a30c..0c3890614 100644 --- a/pqs-user/user-api/src/main/java/com/njcn/user/enums/UserResponseEnum.java +++ b/pqs-user/user-api/src/main/java/com/njcn/user/enums/UserResponseEnum.java @@ -24,6 +24,7 @@ public enum UserResponseEnum { LOGIN_USERNAME_INVALID("A0101", "用户名非法"), LOGIN_USER_INDEX_INVALID("A0101", "用户索引非法"), LOGIN_PHONE_NOT_FOUND("A0101", "手机号不存在"), + LOGIN_PHONE_NOT_REGISTER("A0101", "手机号未注册"), KEY_WRONG("A0101","登录密码/验证码为空"), LOGIN_WRONG_PWD("A0101", "用户名密码错误"), LOGIN_WRONG_PHONE_CODE("A0101", "短信验证码错误"), diff --git a/pqs-user/user-api/src/main/java/com/njcn/user/pojo/po/AuthClient.java b/pqs-user/user-api/src/main/java/com/njcn/user/pojo/po/AuthClient.java index fb6239ea2..8bdcadd16 100644 --- a/pqs-user/user-api/src/main/java/com/njcn/user/pojo/po/AuthClient.java +++ b/pqs-user/user-api/src/main/java/com/njcn/user/pojo/po/AuthClient.java @@ -58,12 +58,12 @@ public class AuthClient { private String authorities; /** - * 认证令牌时效 + * 认证令牌时效 单位:秒 */ private Integer accessTokenValidity; /** - * 刷新令牌时效 + * 刷新令牌时效 单位:秒 */ private Integer refreshTokenValidity; diff --git a/pqs-user/user-boot/src/main/java/com/njcn/user/controller/UserController.java b/pqs-user/user-boot/src/main/java/com/njcn/user/controller/UserController.java index 26ffd08ba..0c0046f98 100644 --- a/pqs-user/user-boot/src/main/java/com/njcn/user/controller/UserController.java +++ b/pqs-user/user-boot/src/main/java/com/njcn/user/controller/UserController.java @@ -80,6 +80,24 @@ public class UserController extends BaseController { } } + + @OperateInfo + @ApiIgnore + @GetMapping("/getUserByPhone/{phone}") + @ApiOperation("根据手机号查询用户信息") + @ApiImplicitParam(name = "phone", value = "手机号", required = true) + public HttpResult getUserByPhone(@PathVariable String phone) { + RequestUtil.saveLoginName(phone); + String methodDescribe = getMethodDescribe("getUserByPhone"); + LogUtil.njcnDebug(log, "{},手机号为:{}", methodDescribe, phone); + UserDTO user = userService.loadUserByPhone(phone); + if (Objects.isNull(user)) { + throw new BusinessException(UserResponseEnum.LOGIN_PHONE_NOT_REGISTER); + } else { + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, user, methodDescribe); + } + } + @OperateInfo @GetMapping("/judgeUserStatus/{loginName}") @ApiOperation("认证后根据用户名判断用户状态") diff --git a/pqs-user/user-boot/src/main/java/com/njcn/user/service/IUserService.java b/pqs-user/user-boot/src/main/java/com/njcn/user/service/IUserService.java index 33d2b212f..23b31f091 100644 --- a/pqs-user/user-boot/src/main/java/com/njcn/user/service/IUserService.java +++ b/pqs-user/user-boot/src/main/java/com/njcn/user/service/IUserService.java @@ -27,6 +27,14 @@ public interface IUserService extends IService { */ UserDTO getUserByName(String loginName); + /** + * 根据手机号查询用户信息 + * + * @param phone 登录名 + * @return 用户基本信息 + */ + UserDTO loadUserByPhone(String phone); + /** * 认证结束后,判断用户状态是否能正常访问系统 * @param loginName 登录名 @@ -160,4 +168,6 @@ public interface IUserService extends IService { String exportUser(UserParam.UserQueryParam queryParam,String methodDescribe); boolean activateUser(String id); + + } diff --git a/pqs-user/user-boot/src/main/java/com/njcn/user/service/impl/UserServiceImpl.java b/pqs-user/user-boot/src/main/java/com/njcn/user/service/impl/UserServiceImpl.java index 0a1c430e1..d6de41c26 100644 --- a/pqs-user/user-boot/src/main/java/com/njcn/user/service/impl/UserServiceImpl.java +++ b/pqs-user/user-boot/src/main/java/com/njcn/user/service/impl/UserServiceImpl.java @@ -98,6 +98,17 @@ public class UserServiceImpl extends ServiceImpl implements IU return new UserDTO(user.getId(), user.getLoginName(), user.getName(), user.getPassword(), roleNames, userSet.getSecretKey(), userSet.getStandBy(), user.getDeptId(), user.getType()); } + @Override + public UserDTO loadUserByPhone(String phone) { + User user = getUserByPhone(phone,false,null); + if (Objects.isNull(user)) { + return null; + } + List roleNames = roleService.getRoleNameByUserId(user.getId()); + UserSet userSet = userSetService.lambdaQuery().eq(UserSet::getUserId, user.getId()).one(); + return new UserDTO(user.getId(), user.getLoginName(), user.getName(), user.getPassword(), roleNames, userSet.getSecretKey(), userSet.getStandBy(), user.getDeptId(), user.getType()); + } + @Override public void judgeUserStatus(String loginName) { User user = getUserByLoginName(loginName);