代码调整
This commit is contained in:
@@ -5,8 +5,9 @@ import cn.hutool.core.util.StrUtil;
|
|||||||
import com.njcn.auth.filter.CustomClientCredentialsTokenEndpointFilter;
|
import com.njcn.auth.filter.CustomClientCredentialsTokenEndpointFilter;
|
||||||
import com.njcn.auth.pojo.bo.BusinessUser;
|
import com.njcn.auth.pojo.bo.BusinessUser;
|
||||||
import com.njcn.auth.security.clientdetails.ClientDetailsServiceImpl;
|
import com.njcn.auth.security.clientdetails.ClientDetailsServiceImpl;
|
||||||
import com.njcn.auth.security.extension.captcha.CaptchaTokenGranter;
|
import com.njcn.auth.security.granter.CaptchaTokenGranter;
|
||||||
import com.njcn.auth.security.extension.refresh.PreAuthenticatedUserDetailsService;
|
import com.njcn.auth.security.granter.PreAuthenticatedUserDetailsService;
|
||||||
|
import com.njcn.auth.security.granter.SmsTokenGranter;
|
||||||
import com.njcn.auth.service.UserDetailsServiceImpl;
|
import com.njcn.auth.service.UserDetailsServiceImpl;
|
||||||
import com.njcn.common.pojo.constant.SecurityConstants;
|
import com.njcn.common.pojo.constant.SecurityConstants;
|
||||||
import com.njcn.common.pojo.enums.auth.ClientEnum;
|
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(),
|
granterList.add(new CaptchaTokenGranter(endpoints.getTokenServices(), endpoints.getClientDetailsService(),
|
||||||
endpoints.getOAuth2RequestFactory(), authenticationManager, redisUtil
|
endpoints.getOAuth2RequestFactory(), authenticationManager, redisUtil
|
||||||
));
|
));
|
||||||
|
// 添加短信授权模式授权者
|
||||||
|
granterList.add(new SmsTokenGranter(endpoints.getTokenServices(), endpoints.getClientDetailsService(),
|
||||||
|
endpoints.getOAuth2RequestFactory(), authenticationManager, redisUtil
|
||||||
|
));
|
||||||
//todo... 后续可以扩展更多授权模式,比如:微信小程序、移动app
|
//todo... 后续可以扩展更多授权模式,比如:微信小程序、移动app
|
||||||
|
|
||||||
|
|
||||||
CompositeTokenGranter compositeTokenGranter = new CompositeTokenGranter(granterList);
|
CompositeTokenGranter compositeTokenGranter = new CompositeTokenGranter(granterList);
|
||||||
endpoints.authenticationManager(authenticationManager)
|
endpoints.authenticationManager(authenticationManager)
|
||||||
.accessTokenConverter(jwtAccessTokenConverter())
|
.accessTokenConverter(jwtAccessTokenConverter())
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.njcn.auth.config;
|
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.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
@@ -29,6 +30,8 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
|
|||||||
|
|
||||||
private final Sm4AuthenticationProvider sm4AuthenticationProvider;
|
private final Sm4AuthenticationProvider sm4AuthenticationProvider;
|
||||||
|
|
||||||
|
private final SmsAuthenticationProvider smsAuthenticationProvider;
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void configure(HttpSecurity http) throws Exception {
|
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注入进去
|
* 重写父类自定义AuthenticationManager 将provider注入进去
|
||||||
* 当然我们也可以考虑不重写 在父类的manager里面注入provider
|
* 当然我们也可以考虑不重写 在父类的manager里面注入provider
|
||||||
@@ -85,10 +73,24 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
|
|||||||
@Bean
|
@Bean
|
||||||
@Override
|
@Override
|
||||||
protected AuthenticationManager authenticationManager(){
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 密码编码器
|
* 密码编码器
|
||||||
* <p>
|
* <p>
|
||||||
|
|||||||
@@ -75,15 +75,19 @@ public class AuthController extends BaseController {
|
|||||||
@ApiImplicitParam(name = SecurityConstants.USERNAME, value = "登录用户名"),
|
@ApiImplicitParam(name = SecurityConstants.USERNAME, value = "登录用户名"),
|
||||||
@ApiImplicitParam(name = SecurityConstants.PASSWORD, value = "登录密码"),
|
@ApiImplicitParam(name = SecurityConstants.PASSWORD, value = "登录密码"),
|
||||||
@ApiImplicitParam(name = SecurityConstants.IMAGE_CODE, value = "图形验证码"),
|
@ApiImplicitParam(name = SecurityConstants.IMAGE_CODE, value = "图形验证码"),
|
||||||
|
@ApiImplicitParam(name = SecurityConstants.PHONE, value = "手机号"),
|
||||||
|
@ApiImplicitParam(name = SecurityConstants.SMS_CODE, value = "短信验证码"),
|
||||||
})
|
})
|
||||||
@PostMapping("/token")
|
@PostMapping("/token")
|
||||||
public Object postAccessToken(@ApiIgnore Principal principal, @RequestParam @ApiIgnore Map<String, String> parameters) throws HttpRequestMethodNotSupportedException {
|
public Object postAccessToken(@ApiIgnore Principal principal, @RequestParam @ApiIgnore Map<String, String> parameters) throws HttpRequestMethodNotSupportedException {
|
||||||
String methodDescribe = getMethodDescribe("postAccessToken");
|
String methodDescribe = getMethodDescribe("postAccessToken");
|
||||||
String username = parameters.get(SecurityConstants.USERNAME);
|
String username = parameters.get(SecurityConstants.USERNAME);
|
||||||
String grantType = parameters.get(SecurityConstants.GRANT_TYPE);
|
String grantType = parameters.get(SecurityConstants.GRANT_TYPE);
|
||||||
//正式环境需删除,均是加密的用户名
|
if (grantType.equalsIgnoreCase(SecurityConstants.GRANT_CAPTCHA)) {
|
||||||
if (!grantType.equalsIgnoreCase(SecurityConstants.PASSWORD)) {
|
|
||||||
username = DesUtils.aesDecrypt(username);
|
username = DesUtils.aesDecrypt(username);
|
||||||
|
}else if(grantType.equalsIgnoreCase(SecurityConstants.GRANT_SMS_CODE)){
|
||||||
|
//短信方式登录,将手机号赋值为用户名
|
||||||
|
username = parameters.get(SecurityConstants.PHONE);
|
||||||
}
|
}
|
||||||
if (grantType.equalsIgnoreCase(SecurityConstants.REFRESH_TOKEN_KEY)) {
|
if (grantType.equalsIgnoreCase(SecurityConstants.REFRESH_TOKEN_KEY)) {
|
||||||
//如果是刷新token,需要去黑名单校验
|
//如果是刷新token,需要去黑名单校验
|
||||||
@@ -92,7 +96,9 @@ public class AuthController extends BaseController {
|
|||||||
RequestUtil.saveLoginName(username);
|
RequestUtil.saveLoginName(username);
|
||||||
OAuth2AccessToken oAuth2AccessToken = tokenEndpoint.postAccessToken(principal, parameters).getBody();
|
OAuth2AccessToken oAuth2AccessToken = tokenEndpoint.postAccessToken(principal, parameters).getBody();
|
||||||
//用户的登录名&密码校验成功后,判断当前该用户是否可以正常使用系统
|
//用户的登录名&密码校验成功后,判断当前该用户是否可以正常使用系统
|
||||||
userFeignClient.judgeUserStatus(username);
|
if(!grantType.equalsIgnoreCase(SecurityConstants.GRANT_SMS_CODE)){
|
||||||
|
userFeignClient.judgeUserStatus(username);
|
||||||
|
}
|
||||||
//登录成功后,记录token信息,并处理踢人效果
|
//登录成功后,记录token信息,并处理踢人效果
|
||||||
userTokenService.recordUserInfo(oAuth2AccessToken,RequestUtil.getRealIp());
|
userTokenService.recordUserInfo(oAuth2AccessToken,RequestUtil.getRealIp());
|
||||||
if (!grantType.equalsIgnoreCase(SecurityConstants.PASSWORD)) {
|
if (!grantType.equalsIgnoreCase(SecurityConstants.PASSWORD)) {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package com.njcn.auth.security.extension.captcha;
|
package com.njcn.auth.security.granter;
|
||||||
|
|
||||||
import cn.hutool.core.util.StrUtil;
|
import cn.hutool.core.util.StrUtil;
|
||||||
import com.njcn.common.pojo.constant.SecurityConstants;
|
import com.njcn.common.pojo.constant.SecurityConstants;
|
||||||
@@ -37,7 +37,7 @@ public class CaptchaTokenGranter extends AbstractTokenGranter {
|
|||||||
OAuth2RequestFactory requestFactory, AuthenticationManager authenticationManager,
|
OAuth2RequestFactory requestFactory, AuthenticationManager authenticationManager,
|
||||||
RedisUtil redisUtil
|
RedisUtil redisUtil
|
||||||
) {
|
) {
|
||||||
//SecurityConstants.GRANT_CAPTCHA:申明为授权码模式
|
//SecurityConstants.GRANT_CAPTCHA:申明为验证码模式
|
||||||
super(tokenServices, clientDetailsService, requestFactory, SecurityConstants.GRANT_CAPTCHA);
|
super(tokenServices, clientDetailsService, requestFactory, SecurityConstants.GRANT_CAPTCHA);
|
||||||
this.authenticationManager = authenticationManager;
|
this.authenticationManager = authenticationManager;
|
||||||
this.redisUtil = redisUtil;
|
this.redisUtil = redisUtil;
|
||||||
@@ -49,6 +49,7 @@ public class CaptchaTokenGranter extends AbstractTokenGranter {
|
|||||||
String username = parameters.get(SecurityConstants.USERNAME);
|
String username = parameters.get(SecurityConstants.USERNAME);
|
||||||
username = DesUtils.aesDecrypt(username);
|
username = DesUtils.aesDecrypt(username);
|
||||||
String verifyCode = parameters.get(SecurityConstants.VERIFY_CODE);
|
String verifyCode = parameters.get(SecurityConstants.VERIFY_CODE);
|
||||||
|
//判断是否需要校验图形验证码,用户错误后,前端要求用户填写验证码
|
||||||
if(StrUtil.isEmpty(verifyCode)||verifyCode.equals("1")){
|
if(StrUtil.isEmpty(verifyCode)||verifyCode.equals("1")){
|
||||||
if (!judgeImageCode(parameters.get(SecurityConstants.IMAGE_CODE), RequestUtil.getRequest())) {
|
if (!judgeImageCode(parameters.get(SecurityConstants.IMAGE_CODE), RequestUtil.getRequest())) {
|
||||||
throw new BusinessException(UserResponseEnum.LOGIN_WRONG_CODE);
|
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 ip = RequestUtil.getRequest().getHeader(SecurityConstants.REQUEST_HEADER_KEY_CLIENT_REAL_IP);
|
||||||
//密码处理
|
//密码处理
|
||||||
String privateKey = redisUtil.getStringByKey(username + ip);
|
String privateKey = redisUtil.getStringByKey(username + ip);
|
||||||
// //秘钥用完即删
|
//秘钥用完即删
|
||||||
redisUtil.delete(username + ip);
|
redisUtil.delete(username + ip);
|
||||||
//对SM2解密面进行验证
|
//对SM2解密面进行验证
|
||||||
password = Sm2.getPasswordSM2Verify(privateKey, password);
|
password = Sm2.getPasswordSM2Verify(privateKey, password);
|
||||||
if (StrUtil.isBlankIfStr(password)) {
|
if (StrUtil.isBlankIfStr(password)) {
|
||||||
throw new BusinessException(UserResponseEnum.PASSWORD_TRANSPORT_ERROR);
|
throw new BusinessException(UserResponseEnum.PASSWORD_TRANSPORT_ERROR);
|
||||||
}
|
}
|
||||||
//正式环境放行
|
//1、不将密码放入details内,防止密码泄漏
|
||||||
parameters.remove(SecurityConstants.PASSWORD);
|
parameters.remove(SecurityConstants.PASSWORD);
|
||||||
|
//2、组装用户密码模式的认证信息
|
||||||
Authentication userAuth = new UsernamePasswordAuthenticationToken(username, password);
|
Authentication userAuth = new UsernamePasswordAuthenticationToken(username, password);
|
||||||
((AbstractAuthenticationToken) userAuth).setDetails(parameters);
|
((AbstractAuthenticationToken) userAuth).setDetails(parameters);
|
||||||
try {
|
try {
|
||||||
|
//3、认证组装好的信息
|
||||||
userAuth = authenticationManager.authenticate(userAuth);
|
userAuth = authenticationManager.authenticate(userAuth);
|
||||||
} catch (AccountStatusException | BadCredentialsException ase) {
|
} catch (AccountStatusException | BadCredentialsException ase) {
|
||||||
//covers expired, locked, disabled cases
|
//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 the username/password are wrong the spec says we should send 400/invalid grant
|
||||||
if (userAuth == null || !userAuth.isAuthenticated()) {
|
if (userAuth == null || !userAuth.isAuthenticated()) {
|
||||||
throw new InvalidGrantException("Could not authenticate user: " + username);
|
throw new InvalidGrantException("无法认证用户: " + username);
|
||||||
}
|
}
|
||||||
|
|
||||||
OAuth2Request storedOauth2Request = getRequestFactory().createOAuth2Request(client, tokenRequest);
|
OAuth2Request storedOauth2Request = getRequestFactory().createOAuth2Request(client, tokenRequest);
|
||||||
@@ -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 com.njcn.web.utils.RequestUtil;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
import org.springframework.beans.factory.InitializingBean;
|
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.UserDetailsService;
|
||||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||||
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
|
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -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<String, String> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
* <code>UserDetails</code> 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 <code>UserDetails</code> and/or
|
||||||
|
* <code>SmsCodeAuthenticationToken</code>, these should also appear in this
|
||||||
|
* method.
|
||||||
|
*
|
||||||
|
* @param userDetails as retrieved from the
|
||||||
|
* {@link #retrieveUser(String, SmsCodeAuthenticationToken)} or
|
||||||
|
* <code>UserCache</code>
|
||||||
|
* @param authentication the current request that needs to be authenticated
|
||||||
|
*
|
||||||
|
* @throws AuthenticationException AuthenticationException if the credentials could
|
||||||
|
* not be validated (generally a <code>BadCredentialsException</code>, an
|
||||||
|
* <code>AuthenticationServiceException</code>)
|
||||||
|
*/
|
||||||
|
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.
|
||||||
|
* <p>
|
||||||
|
* Protected so subclasses can override.
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* Subclasses will usually store the original credentials the user supplied (not
|
||||||
|
* salted or encoded passwords) in the returned <code>Authentication</code> object.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @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 <code>UserDetails</code> from an
|
||||||
|
* implementation-specific location, with the option of throwing an
|
||||||
|
* <code>AuthenticationException</code> 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 <code>UserDetails</code>).
|
||||||
|
* <p>
|
||||||
|
* Subclasses are not required to perform any caching, as the
|
||||||
|
* <code>AbstractSmsAuthenticationProvider</code> will by default cache the
|
||||||
|
* <code>UserDetails</code>. The caching of <code>UserDetails</code> 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 <code>UserDetails</code> will ever be cached) or
|
||||||
|
* ensure subclasses implement
|
||||||
|
* {@link #additionalAuthenticationChecks(UserDetails, SmsCodeAuthenticationToken)}
|
||||||
|
* to compare the credentials of a cached <code>UserDetails</code> with subsequent
|
||||||
|
* authentication requests.
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* 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.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param username The username to retrieve
|
||||||
|
* @param authentication The authentication request, which subclasses <em>may</em>
|
||||||
|
* need to perform a binding-based retrieval of the <code>UserDetails</code>
|
||||||
|
*
|
||||||
|
* @return the user information (never <code>null</code> - instead an exception should
|
||||||
|
* the thrown)
|
||||||
|
*
|
||||||
|
* @throws AuthenticationException if the credentials could not be validated
|
||||||
|
* (generally a <code>BadCredentialsException</code>, an
|
||||||
|
* <code>AuthenticationServiceException</code> or
|
||||||
|
* <code>UsernameNotFoundException</code>)
|
||||||
|
*/
|
||||||
|
protected abstract UserDetails retrieveUser(String username,
|
||||||
|
SmsCodeAuthenticationToken authentication)
|
||||||
|
throws AuthenticationException;
|
||||||
|
|
||||||
|
public void setForcePrincipalAsString(boolean forcePrincipalAsString) {
|
||||||
|
this.forcePrincipalAsString = forcePrincipalAsString;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* By default the <code>AbstractSmsAuthenticationProvider</code> throws a
|
||||||
|
* <code>BadCredentialsException</code> if a username is not found or the password is
|
||||||
|
* incorrect. Setting this property to <code>false</code> will cause
|
||||||
|
* <code>UsernameNotFoundException</code>s to be thrown instead for the former. Note
|
||||||
|
* this is considered less secure than throwing <code>BadCredentialsException</code>
|
||||||
|
* for both exceptions.
|
||||||
|
*
|
||||||
|
* @param hideUserNotFoundExceptions set to <code>false</code> if you wish
|
||||||
|
* <code>UsernameNotFoundException</code>s to be thrown instead of the non-specific
|
||||||
|
* <code>BadCredentialsException</code> (defaults to <code>true</code>)
|
||||||
|
*/
|
||||||
|
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
|
||||||
|
* <tt>UserDetails</tt> <em>before</em> 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"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -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.auth.pojo.bo.BusinessUser;
|
||||||
import com.njcn.common.utils.sm.Sm4Utils;
|
import com.njcn.common.utils.sm.Sm4Utils;
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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<? extends GrantedAuthority> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -25,7 +25,7 @@ import org.springframework.stereotype.Service;
|
|||||||
@Slf4j
|
@Slf4j
|
||||||
@Service
|
@Service
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
public class UserDetailsServiceImpl implements UserDetailsService {
|
public class UserDetailsServiceImpl implements CustomUserDetailsService {
|
||||||
|
|
||||||
private final UserFeignClient userFeignClient;
|
private final UserFeignClient userFeignClient;
|
||||||
|
|
||||||
@@ -44,4 +44,18 @@ public class UserDetailsServiceImpl implements UserDetailsService {
|
|||||||
return businessUser;
|
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<UserDTO> 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;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,16 +26,14 @@ public class AuthTest extends BaseJunitTest {
|
|||||||
@SneakyThrows
|
@SneakyThrows
|
||||||
@Test
|
@Test
|
||||||
public void 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)
|
UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(userUrl)
|
||||||
.queryParam("grant_type", "password")
|
.queryParam("grant_type", "password")
|
||||||
.queryParam("client_id", "njcn_app")
|
.queryParam("client_id", "njcn")
|
||||||
.queryParam("client_secret", "njcnpqs")
|
.queryParam("client_secret", "njcnpqs")
|
||||||
.queryParam("username", "root")
|
.queryParam("username", "root")
|
||||||
.queryParam("password", "@#001njcnpqs");
|
.queryParam("password", "@#001njcnpqs");
|
||||||
URI uri = builder.build().encode().toUri();
|
URI uri = builder.build().encode().toUri();
|
||||||
JSONObject jsonObject = new JSONObject();
|
|
||||||
jsonObject.set("","");
|
|
||||||
|
|
||||||
ResponseEntity<OAuth2AccessToken> userEntity = RestTemplateUtil.post(uri, OAuth2AccessToken.class);
|
ResponseEntity<OAuth2AccessToken> userEntity = RestTemplateUtil.post(uri, OAuth2AccessToken.class);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -94,6 +94,8 @@ public interface SecurityConstants {
|
|||||||
String REFRESH_TOKEN = "refresh_token";
|
String REFRESH_TOKEN = "refresh_token";
|
||||||
String USERNAME = "username";
|
String USERNAME = "username";
|
||||||
String PASSWORD = "password";
|
String PASSWORD = "password";
|
||||||
|
String PHONE = "phone";
|
||||||
|
String SMS_CODE = "smsCode";
|
||||||
String IMAGE_CODE = "imageCode";
|
String IMAGE_CODE = "imageCode";
|
||||||
String VERIFY_CODE = "verifyCode";
|
String VERIFY_CODE = "verifyCode";
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,11 @@ public enum RedisKeyEnum {
|
|||||||
ROLE_FUNCTION_KEY("ROLES_FUNCTIONS",-1L),
|
ROLE_FUNCTION_KEY("ROLES_FUNCTIONS",-1L),
|
||||||
PUBLIC_FUNCTIONS_KEY("PUBLIC_FUNCTIONS",-1L),
|
PUBLIC_FUNCTIONS_KEY("PUBLIC_FUNCTIONS",-1L),
|
||||||
|
|
||||||
|
/***
|
||||||
|
* app短信验证码,保存10分钟缓存
|
||||||
|
*/
|
||||||
|
SMS_LOGIN_KEY("SMS_LOGIN",10L),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 终端信息查询缓存的公共key前缀
|
* 终端信息查询缓存的公共key前缀
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -31,6 +31,15 @@ public interface UserFeignClient {
|
|||||||
@GetMapping("/getUserByName/{loginName}")
|
@GetMapping("/getUserByName/{loginName}")
|
||||||
HttpResult<UserDTO> getUserByName(@PathVariable("loginName") String loginName);
|
HttpResult<UserDTO> getUserByName(@PathVariable("loginName") String loginName);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据手机号查询用户信息
|
||||||
|
*
|
||||||
|
* @param phone 登录名
|
||||||
|
* @return 用户基本信息
|
||||||
|
*/
|
||||||
|
@GetMapping("/getUserByPhone/{phone}")
|
||||||
|
HttpResult<UserDTO> getUserByPhone(@PathVariable("phone")String phone);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 认证后根据用户名判断用户状态
|
* 认证后根据用户名判断用户状态
|
||||||
* @param loginName 登录名
|
* @param loginName 登录名
|
||||||
@@ -55,4 +64,6 @@ public interface UserFeignClient {
|
|||||||
*/
|
*/
|
||||||
@PostMapping("/userByIdList")
|
@PostMapping("/userByIdList")
|
||||||
HttpResult<List<User>> getUserByIdList(@RequestBody List<String> ids);
|
HttpResult<List<User>> getUserByIdList(@RequestBody List<String> ids);
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,6 +43,12 @@ public class UserFeignClientFallbackFactory implements FallbackFactory<UserFeign
|
|||||||
throw new BusinessException(finalExceptionEnum);
|
throw new BusinessException(finalExceptionEnum);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public HttpResult<UserDTO> getUserByPhone(String phone) {
|
||||||
|
log.error("{}异常,降级处理,异常为:{}","根据手机号查询用户信息",cause.toString());
|
||||||
|
throw new BusinessException(finalExceptionEnum);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public HttpResult<Boolean> judgeUserStatus(String loginName) {
|
public HttpResult<Boolean> judgeUserStatus(String loginName) {
|
||||||
log.error("{}异常,降级处理,异常为:{}","认证后根据用户名判断用户状态",cause.toString());
|
log.error("{}异常,降级处理,异常为:{}","认证后根据用户名判断用户状态",cause.toString());
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ public enum UserResponseEnum {
|
|||||||
LOGIN_USERNAME_INVALID("A0101", "用户名非法"),
|
LOGIN_USERNAME_INVALID("A0101", "用户名非法"),
|
||||||
LOGIN_USER_INDEX_INVALID("A0101", "用户索引非法"),
|
LOGIN_USER_INDEX_INVALID("A0101", "用户索引非法"),
|
||||||
LOGIN_PHONE_NOT_FOUND("A0101", "手机号不存在"),
|
LOGIN_PHONE_NOT_FOUND("A0101", "手机号不存在"),
|
||||||
|
LOGIN_PHONE_NOT_REGISTER("A0101", "手机号未注册"),
|
||||||
KEY_WRONG("A0101","登录密码/验证码为空"),
|
KEY_WRONG("A0101","登录密码/验证码为空"),
|
||||||
LOGIN_WRONG_PWD("A0101", "用户名密码错误"),
|
LOGIN_WRONG_PWD("A0101", "用户名密码错误"),
|
||||||
LOGIN_WRONG_PHONE_CODE("A0101", "短信验证码错误"),
|
LOGIN_WRONG_PHONE_CODE("A0101", "短信验证码错误"),
|
||||||
|
|||||||
@@ -58,12 +58,12 @@ public class AuthClient {
|
|||||||
private String authorities;
|
private String authorities;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 认证令牌时效
|
* 认证令牌时效 单位:秒
|
||||||
*/
|
*/
|
||||||
private Integer accessTokenValidity;
|
private Integer accessTokenValidity;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 刷新令牌时效
|
* 刷新令牌时效 单位:秒
|
||||||
*/
|
*/
|
||||||
private Integer refreshTokenValidity;
|
private Integer refreshTokenValidity;
|
||||||
|
|
||||||
|
|||||||
@@ -80,6 +80,24 @@ public class UserController extends BaseController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@OperateInfo
|
||||||
|
@ApiIgnore
|
||||||
|
@GetMapping("/getUserByPhone/{phone}")
|
||||||
|
@ApiOperation("根据手机号查询用户信息")
|
||||||
|
@ApiImplicitParam(name = "phone", value = "手机号", required = true)
|
||||||
|
public HttpResult<UserDTO> 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
|
@OperateInfo
|
||||||
@GetMapping("/judgeUserStatus/{loginName}")
|
@GetMapping("/judgeUserStatus/{loginName}")
|
||||||
@ApiOperation("认证后根据用户名判断用户状态")
|
@ApiOperation("认证后根据用户名判断用户状态")
|
||||||
|
|||||||
@@ -27,6 +27,14 @@ public interface IUserService extends IService<User> {
|
|||||||
*/
|
*/
|
||||||
UserDTO getUserByName(String loginName);
|
UserDTO getUserByName(String loginName);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据手机号查询用户信息
|
||||||
|
*
|
||||||
|
* @param phone 登录名
|
||||||
|
* @return 用户基本信息
|
||||||
|
*/
|
||||||
|
UserDTO loadUserByPhone(String phone);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 认证结束后,判断用户状态是否能正常访问系统
|
* 认证结束后,判断用户状态是否能正常访问系统
|
||||||
* @param loginName 登录名
|
* @param loginName 登录名
|
||||||
@@ -160,4 +168,6 @@ public interface IUserService extends IService<User> {
|
|||||||
String exportUser(UserParam.UserQueryParam queryParam,String methodDescribe);
|
String exportUser(UserParam.UserQueryParam queryParam,String methodDescribe);
|
||||||
|
|
||||||
boolean activateUser(String id);
|
boolean activateUser(String id);
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -98,6 +98,17 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IU
|
|||||||
return new UserDTO(user.getId(), user.getLoginName(), user.getName(), user.getPassword(), roleNames, userSet.getSecretKey(), userSet.getStandBy(), user.getDeptId(), user.getType());
|
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<String> 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
|
@Override
|
||||||
public void judgeUserStatus(String loginName) {
|
public void judgeUserStatus(String loginName) {
|
||||||
User user = getUserByLoginName(loginName);
|
User user = getUserByLoginName(loginName);
|
||||||
|
|||||||
Reference in New Issue
Block a user