♻️ 重构 close #3472 【用户多表存储】支持多用户体系设置

This commit is contained in:
qraddx committed 2022-02-07 21:54:52 +08:00
1 parent 2d2a995a83
commit bbe1917e01
16 files changed
+622 -412

No files matched your search

@@ -22,6 +22,7 @@ package com.pig4cloud.pigx.auth.config;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.pig4cloud.pigx.common.security.component.PigxCommenceAuthExceptionEntryPoint;
import com.pig4cloud.pigx.common.security.component.PigxWebResponseExceptionTranslator;
import com.pig4cloud.pigx.common.security.grant.ResourceOwnerCustomeAppTokenGranter;
import com.pig4cloud.pigx.common.security.service.PigxCustomTokenServices;
import lombok.AllArgsConstructor;
import lombok.SneakyThrows;
@@ -38,11 +39,15 @@ import org.springframework.security.oauth2.config.annotation.web.configuration.E
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.CompositeTokenGranter;
import org.springframework.security.oauth2.provider.TokenGranter;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
/**
@@ -52,7 +57,7 @@ import java.util.Collections;
@Configuration
@AllArgsConstructor
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
private final ClientDetailsService pigxClientDetailsServiceImpl;
@@ -62,6 +67,8 @@ public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdap
private final AuthorizationCodeServices authorizationCodeServices;
private final AuthenticationManager authenticationManager;
private final TokenStore redisTokenStore;
private final TokenEnhancer tokenEnhancer;
@@ -84,10 +91,29 @@ public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdap
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
endpoints.allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST).tokenServices(tokenServices())
.tokenStore(redisTokenStore).tokenEnhancer(tokenEnhancer).userDetailsService(pigxUserDetailsService)
.tokenStore(redisTokenStore).tokenEnhancer(tokenEnhancer)
.authorizationCodeServices(authorizationCodeServices).authenticationManager(authenticationManagerBean)
.reuseRefreshTokens(false).pathMapping("/oauth/confirm_access", "/token/confirm_access")
.exceptionTranslator(new PigxWebResponseExceptionTranslator());
// 注入自定义认证类型
setTokenGranter(endpoints);
}
/**
* 自定义 APP 认证类型
* @param endpoints AuthorizationServerEndpointsConfigurer
*/
private void setTokenGranter(AuthorizationServerEndpointsConfigurer endpoints) {
// 获取默认授权类型
TokenGranter tokenGranter = endpoints.getTokenGranter();
ArrayList<TokenGranter> tokenGranters = new ArrayList<>(Arrays.asList(tokenGranter));
ResourceOwnerCustomeAppTokenGranter resourceOwnerCustomeAppTokenGranter = new ResourceOwnerCustomeAppTokenGranter(
authenticationManager, endpoints.getTokenServices(), endpoints.getClientDetailsService(),
endpoints.getOAuth2RequestFactory());
tokenGranters.add(resourceOwnerCustomeAppTokenGranter);
CompositeTokenGranter compositeTokenGranter = new CompositeTokenGranter(tokenGranters);
endpoints.tokenGranter(compositeTokenGranter);
}
@Bean
@@ -91,19 +91,9 @@ public interface SecurityConstants {
String OAUTH_TOKEN_URL = "/oauth/token";
/**
* 手机号登录URL
* 移动端授权
*/
String SMS_TOKEN_URL = "/mobile/token/sms";
/**
* 社交登录URL
*/
String SOCIAL_TOKEN_URL = "/mobile/token/social";
/**
* 自定义登录URL
*/
String MOBILE_TOKEN_URL = "/mobile/token/*";
String GRANT_MOBILE = "mobile";
/**
* 微信获取OPENID
@@ -0,0 +1,174 @@
package com.pig4cloud.pigx.common.security.component;
import cn.hutool.extra.spring.SpringUtil;
import com.pig4cloud.pigx.common.core.util.WebUtils;
import com.pig4cloud.pigx.common.security.service.PigxUserDetailsService;
import org.springframework.core.Ordered;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.InternalAuthenticationServiceException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsPasswordService;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.util.Assert;
import java.util.Comparator;
import java.util.Map;
import java.util.Optional;
/**
* @author lengleng
* @date 2022/1/15
*/
public class PigxDaoAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
/**
* The plaintext password used to perform PasswordEncoder#matches(CharSequence,
* String)} on when the user is not found to avoid SEC-2056.
*/
private static final String USER_NOT_FOUND_PASSWORD = "userNotFoundPassword";
private PasswordEncoder passwordEncoder;
/**
* The password used to perform {@link PasswordEncoder#matches(CharSequence, String)}
* on when the user is not found to avoid SEC-2056. This is necessary, because some
* {@link PasswordEncoder} implementations will short circuit if the password is not
* in a valid format.
*/
private volatile String userNotFoundEncodedPassword;
private UserDetailsService userDetailsService;
private UserDetailsPasswordService userDetailsPasswordService;
public PigxDaoAuthenticationProvider() {
setPasswordEncoder(PasswordEncoderFactories.createDelegatingPasswordEncoder());
}
@Override
@SuppressWarnings("deprecation")
protected void additionalAuthenticationChecks(UserDetails userDetails,
UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
if (authentication.getCredentials() == null) {
this.logger.debug("Failed to authenticate since no credentials provided");
throw new BadCredentialsException(this.messages
.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
}
String presentedPassword = authentication.getCredentials().toString();
if (!this.passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
this.logger.debug("Failed to authenticate since password does not match stored value");
throw new BadCredentialsException(this.messages
.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
}
}
@Override
protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException {
prepareTimingAttackProtection();
// 此处已获得 客户端认证 获取对应 userDetailsService
Authentication clientAuthentication = SecurityContextHolder.getContext().getAuthentication();
// SSO NPE 处理
String clientId;
if (clientAuthentication == null) {
clientId = WebUtils.getRequest().getParameter("clientId");
}
else {
clientId = clientAuthentication.getName();
}
Map<String, PigxUserDetailsService> userDetailsServiceMap = SpringUtil
.getBeansOfType(PigxUserDetailsService.class);
Optional<PigxUserDetailsService> optional = userDetailsServiceMap.values().stream()
.filter(service -> service.support(clientId, null)).max(Comparator.comparingInt(Ordered::getOrder));
if (!optional.isPresent()) {
throw new InternalAuthenticationServiceException("UserDetailsService error , not register");
}
try {
UserDetails loadedUser = optional.get().loadUserByUsername(username);
if (loadedUser == null) {
throw new InternalAuthenticationServiceException(
"UserDetailsService returned null, which is an interface contract violation");
}
return loadedUser;
}
catch (UsernameNotFoundException ex) {
mitigateAgainstTimingAttack(authentication);
throw ex;
}
catch (InternalAuthenticationServiceException ex) {
throw ex;
}
catch (Exception ex) {
throw new InternalAuthenticationServiceException(ex.getMessage(), ex);
}
}
@Override
protected Authentication createSuccessAuthentication(Object principal, Authentication authentication,
UserDetails user) {
boolean upgradeEncoding = this.userDetailsPasswordService != null
&& this.passwordEncoder.upgradeEncoding(user.getPassword());
if (upgradeEncoding) {
String presentedPassword = authentication.getCredentials().toString();
String newPassword = this.passwordEncoder.encode(presentedPassword);
user = this.userDetailsPasswordService.updatePassword(user, newPassword);
}
return super.createSuccessAuthentication(principal, authentication, user);
}
private void prepareTimingAttackProtection() {
if (this.userNotFoundEncodedPassword == null) {
this.userNotFoundEncodedPassword = this.passwordEncoder.encode(USER_NOT_FOUND_PASSWORD);
}
}
private void mitigateAgainstTimingAttack(UsernamePasswordAuthenticationToken authentication) {
if (authentication.getCredentials() != null) {
String presentedPassword = authentication.getCredentials().toString();
this.passwordEncoder.matches(presentedPassword, this.userNotFoundEncodedPassword);
}
}
/**
* Sets the PasswordEncoder instance to be used to encode and validate passwords. If
* not set, the password will be compared using
* {@link PasswordEncoderFactories#createDelegatingPasswordEncoder()}
* @param passwordEncoder must be an instance of one of the {@code PasswordEncoder}
* types.
*/
public void setPasswordEncoder(PasswordEncoder passwordEncoder) {
Assert.notNull(passwordEncoder, "passwordEncoder cannot be null");
this.passwordEncoder = passwordEncoder;
this.userNotFoundEncodedPassword = null;
}
protected PasswordEncoder getPasswordEncoder() {
return this.passwordEncoder;
}
public void setUserDetailsService(UserDetailsService userDetailsService) {
this.userDetailsService = userDetailsService;
}
protected UserDetailsService getUserDetailsService() {
return this.userDetailsService;
}
public void setUserDetailsPasswordService(UserDetailsPasswordService userDetailsPasswordService) {
this.userDetailsPasswordService = userDetailsPasswordService;
}
}
@@ -1,14 +1,17 @@
package com.pig4cloud.pigx.common.security.component;
import cn.hutool.extra.spring.SpringUtil;
import com.pig4cloud.pigx.common.security.exception.UnauthorizedException;
import com.pig4cloud.pigx.common.security.service.PigxUser;
import com.pig4cloud.pigx.common.security.service.PigxUserDetailsService;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Primary;
import org.springframework.core.Ordered;
import org.springframework.security.authentication.InternalAuthenticationServiceException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
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.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.exceptions.InvalidTokenException;
@@ -18,6 +21,10 @@ import org.springframework.security.oauth2.provider.token.ResourceServerTokenSer
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.stereotype.Service;
import java.util.Comparator;
import java.util.Map;
import java.util.Optional;
/**
* @author lengleng
* @date 2020/9/29
@@ -29,8 +36,6 @@ public class PigxLocalResourceServerTokenServices implements ResourceServerToken
private final TokenStore tokenStore;
private final UserDetailsService userDetailsService;
@Override
public OAuth2Authentication loadAuthentication(String accessToken)
throws AuthenticationException, InvalidTokenException {
@@ -44,15 +49,28 @@ public class PigxLocalResourceServerTokenServices implements ResourceServerToken
return oAuth2Authentication;
}
String clientId = oAuth2Request.getClientId();
Map<String, PigxUserDetailsService> userDetailsServiceMap = SpringUtil
.getBeansOfType(PigxUserDetailsService.class);
Optional<PigxUserDetailsService> optional = userDetailsServiceMap.values().stream()
.filter(service -> service.support(clientId, oAuth2Request.getGrantType()))
.max(Comparator.comparingInt(Ordered::getOrder));
if (!optional.isPresent()) {
throw new InternalAuthenticationServiceException("UserDetailsService error , not register");
}
// 根据 username 查询 spring cache 最新的值 并返回
PigxUser pigxUser = (PigxUser) oAuth2Authentication.getPrincipal();
PigxUser pigUser = (PigxUser) oAuth2Authentication.getPrincipal();
UserDetails userDetails;
try {
userDetails = userDetailsService.loadUserByUsername(pigxUser.getUsername());
userDetails = optional.get().loadUserByUser(pigUser);
}
catch (UsernameNotFoundException notFoundException) {
throw new UnauthorizedException(String.format("%s username not found", pigxUser.getUsername()),
throw new UnauthorizedException(String.format("%s username not found", pigUser.getUsername()),
notFoundException);
}
Authentication userAuthentication = new UsernamePasswordAuthenticationToken(userDetails, "N/A",
@@ -0,0 +1,100 @@
package com.pig4cloud.pigx.common.security.grant;
import cn.hutool.extra.spring.SpringUtil;
import com.pig4cloud.pigx.common.security.service.PigxUserDetailsService;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.Ordered;
import org.springframework.security.authentication.AccountStatusUserDetailsChecker;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.InternalAuthenticationServiceException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsChecker;
import java.util.Comparator;
import java.util.Map;
import java.util.Optional;
/**
* @author hzq
* @since 2021-09-14
*/
@Slf4j
public class CustomAppAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
/**
* user 属性校验
*/
@Setter
private UserDetailsChecker preAuthenticationChecks = new AccountStatusUserDetailsChecker();
/**
* 校验 请求信息userDetails
* @param userDetails 用户信息
* @param authentication 认证信息
* @throws AuthenticationException
*/
@Override
protected void additionalAuthenticationChecks(UserDetails userDetails,
UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
if (authentication.getCredentials() == null) {
this.logger.debug("Failed to authenticate since no credentials provided");
throw new BadCredentialsException(this.messages
.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
}
}
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
if (authentication.getCredentials() == null) {
log.debug("Failed to authenticate since no credentials provided");
throw new BadCredentialsException("Bad credentials");
}
CustomAppAuthenticationToken requestToken = (CustomAppAuthenticationToken) authentication;
// 此处已获得 客户端认证 获取对应 userDetailsService
Authentication clientAuthentication = SecurityContextHolder.getContext().getAuthentication();
String clientId = clientAuthentication.getName();
Map<String, PigxUserDetailsService> userDetailsServiceMap = SpringUtil
.getBeansOfType(PigxUserDetailsService.class);
Optional<PigxUserDetailsService> optional = userDetailsServiceMap.values().stream()
.filter(service -> service.support(clientId, requestToken.getGrantType()))
.max(Comparator.comparingInt(Ordered::getOrder));
if (!optional.isPresent()) {
throw new InternalAuthenticationServiceException("UserDetailsService error , not register");
}
// 手机号
String phone = authentication.getName();
UserDetails userDetails = optional.get().loadUserByUsername(phone);
// userDeails 校验
preAuthenticationChecks.check(userDetails);
CustomAppAuthenticationToken token = new CustomAppAuthenticationToken(userDetails);
token.setDetails(authentication.getDetails());
return token;
}
@Override
protected UserDetails retrieveUser(String phone, UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException {
return null;
}
@Override
public boolean supports(Class<?> authentication) {
return authentication.isAssignableFrom(CustomAppAuthenticationToken.class);
}
}
@@ -0,0 +1,50 @@
package com.pig4cloud.pigx.common.security.grant;
import lombok.Getter;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.UserDetails;
/**
* @author hzq
* @since 2021-09-14
*/
public class CustomAppAuthenticationToken extends AbstractAuthenticationToken {
private final Object principal;
/**
* 验证码/密码
*/
private String code;
/**
* 授权类型
*/
@Getter
private String grantType;
public CustomAppAuthenticationToken(String phone, String code, String grantType) {
super(AuthorityUtils.NO_AUTHORITIES);
this.principal = phone;
this.code = code;
this.grantType = grantType;
}
public CustomAppAuthenticationToken(UserDetails sysUser) {
super(sysUser.getAuthorities());
this.principal = sysUser;
super.setAuthenticated(true); // 设置认证成功 必须
}
@Override
public Object getPrincipal() {
return this.principal;
}
@Override
public Object getCredentials() {
return this.code;
}
}
@@ -0,0 +1,78 @@
package com.pig4cloud.pigx.common.security.grant;
import cn.hutool.core.util.StrUtil;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.authentication.AccountStatusException;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
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 hzq
* @since 2021-09-14
*/
public class ResourceOwnerCustomeAppTokenGranter extends AbstractTokenGranter {
private static final String GRANT_TYPE = "mobile";
private final AuthenticationManager authenticationManager;
public ResourceOwnerCustomeAppTokenGranter(AuthenticationManager authenticationManager,
AuthorizationServerTokenServices tokenServices, ClientDetailsService clientDetailsService,
OAuth2RequestFactory requestFactory) {
this(authenticationManager, tokenServices, clientDetailsService, requestFactory, GRANT_TYPE);
}
protected ResourceOwnerCustomeAppTokenGranter(AuthenticationManager authenticationManager,
AuthorizationServerTokenServices tokenServices, ClientDetailsService clientDetailsService,
OAuth2RequestFactory requestFactory, String grantType) {
super(tokenServices, clientDetailsService, requestFactory, grantType);
this.authenticationManager = authenticationManager;
}
@Override
protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) {
Map<String, String> parameters = new LinkedHashMap<>(tokenRequest.getRequestParameters());
// 手机号
String mobile = parameters.get("mobile");
// 验证码/密码
String code = parameters.get("code");
if (StrUtil.isBlank(mobile) || StrUtil.isBlank(code)) {
throw new InvalidGrantException("Bad credentials [ params must be has phone with code ]");
}
// Protect from downstream leaks of code
parameters.remove("code");
Authentication userAuth = new CustomAppAuthenticationToken(mobile, code, tokenRequest.getGrantType());
((AbstractAuthenticationToken) userAuth).setDetails(parameters);
try {
userAuth = authenticationManager.authenticate(userAuth);
}
catch (AccountStatusException | BadCredentialsException ase) {
// covers expired, locked, disabled cases (mentioned in section 5.2, draft 31)
throw new InvalidGrantException(ase.getMessage());
}
// If the phone/code are wrong the spec says we should send 400/invalid grant
if (userAuth == null || !userAuth.isAuthenticated()) {
throw new InvalidGrantException("Could not authenticate user: " + mobile);
}
OAuth2Request storedOAuth2Request = getRequestFactory().createOAuth2Request(client, tokenRequest);
return new OAuth2Authentication(storedOAuth2Request, userAuth);
}
}
@@ -1,111 +0,0 @@
/*
* Copyright (c) 2018-2025, lengleng All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* Neither the name of the pig4cloud.com developer nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
* Author: lengleng (wangiegie@gmail.com)
*/
package com.pig4cloud.pigx.common.security.mobile;
import com.pig4cloud.pigx.common.core.constant.SecurityConstants;
import lombok.Getter;
import lombok.Setter;
import lombok.SneakyThrows;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @author lengleng
* @date 2018/1/9 手机号登录验证filter
*/
public class MobileAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
private static final String SPRING_SECURITY_FORM_MOBILE_KEY = "mobile";
@Getter
@Setter
private String mobileParameter = SPRING_SECURITY_FORM_MOBILE_KEY;
@Getter
@Setter
private boolean postOnly = true;
@Getter
@Setter
private AuthenticationEntryPoint authenticationEntryPoint;
public MobileAuthenticationFilter() {
super(new AntPathRequestMatcher(SecurityConstants.MOBILE_TOKEN_URL, "POST"));
}
@Override
@SneakyThrows
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) {
if (postOnly && !request.getMethod().equals(HttpMethod.POST.name())) {
throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
}
String mobile = obtainMobile(request);
if (mobile == null) {
mobile = "";
}
mobile = mobile.trim();
MobileAuthenticationToken mobileAuthenticationToken = new MobileAuthenticationToken(mobile);
setDetails(request, mobileAuthenticationToken);
Authentication authResult = null;
try {
authResult = this.getAuthenticationManager().authenticate(mobileAuthenticationToken);
logger.debug("Authentication success: " + authResult);
SecurityContextHolder.getContext().setAuthentication(authResult);
}
catch (Exception failed) {
SecurityContextHolder.clearContext();
logger.debug("Authentication request failed: " + failed);
try {
authenticationEntryPoint.commence(request, response,
new UsernameNotFoundException(failed.getMessage(), failed));
}
catch (Exception e) {
logger.error("authenticationEntryPoint handle error:{}", failed);
}
}
return authResult;
}
private String obtainMobile(HttpServletRequest request) {
return request.getParameter(mobileParameter);
}
private void setDetails(HttpServletRequest request, MobileAuthenticationToken authRequest) {
authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
}
}
@@ -1,77 +0,0 @@
/*
* Copyright (c) 2018-2025, lengleng All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* Neither the name of the pig4cloud.com developer nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
* Author: lengleng (wangiegie@gmail.com)
*/
package com.pig4cloud.pigx.common.security.mobile;
import com.pig4cloud.pigx.common.security.component.PigxPreAuthenticationChecks;
import com.pig4cloud.pigx.common.security.service.PigxUserDetailsService;
import com.pig4cloud.pigx.common.security.util.PigxSecurityMessageSourceUtil;
import lombok.Getter;
import lombok.Setter;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsChecker;
/**
* @author lengleng
* @date 2018/8/5 手机登录校验逻辑 验证码登录、社交登录
*/
@Slf4j
public class MobileAuthenticationProvider implements AuthenticationProvider {
private MessageSourceAccessor messages = PigxSecurityMessageSourceUtil.getAccessor();
private UserDetailsChecker detailsChecker = new PigxPreAuthenticationChecks();
@Getter
@Setter
private PigxUserDetailsService userDetailsService;
@Override
@SneakyThrows
public Authentication authenticate(Authentication authentication) {
MobileAuthenticationToken mobileAuthenticationToken = (MobileAuthenticationToken) authentication;
String principal = mobileAuthenticationToken.getPrincipal().toString();
UserDetails userDetails = userDetailsService.loadUserBySocial(principal);
if (userDetails == null) {
log.debug("Authentication failed: no credentials provided");
throw new BadCredentialsException(messages
.getMessage("AbstractUserDetailsAuthenticationProvider.noopBindAccount", "Noop Bind Account"));
}
// 检查账号状态
detailsChecker.check(userDetails);
MobileAuthenticationToken authenticationToken = new MobileAuthenticationToken(userDetails,
userDetails.getAuthorities());
authenticationToken.setDetails(mobileAuthenticationToken.getDetails());
return authenticationToken;
}
@Override
public boolean supports(Class<?> authentication) {
return MobileAuthenticationToken.class.isAssignableFrom(authentication);
}
}
@@ -1,79 +0,0 @@
/*
* Copyright (c) 2018-2025, lengleng All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* Neither the name of the pig4cloud.com developer nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
* Author: lengleng (wangiegie@gmail.com)
*/
package com.pig4cloud.pigx.common.security.mobile;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.SneakyThrows;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.SpringSecurityCoreVersion;
import java.util.Collection;
/**
* @author lengleng
* @date 2018/1/9 手机号登录令牌
*/
public class MobileAuthenticationToken extends AbstractAuthenticationToken {
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
private final Object principal;
public MobileAuthenticationToken(String mobile) {
super(null);
this.principal = mobile;
setAuthenticated(false);
}
@JsonCreator
public MobileAuthenticationToken(@JsonProperty("principal") Object principal,
@JsonProperty("authorities") Collection<? extends GrantedAuthority> authorities) {
super(authorities);
this.principal = principal;
super.setAuthenticated(true);
}
@Override
public Object getPrincipal() {
return this.principal;
}
@Override
public Object getCredentials() {
return null;
}
@Override
@SneakyThrows
public void setAuthenticated(boolean isAuthenticated) {
if (isAuthenticated) {
throw new IllegalArgumentException(
"Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead");
}
super.setAuthenticated(false);
}
@Override
public void eraseCredentials() {
super.eraseCredentials();
}
}
@@ -1,63 +0,0 @@
/*
* Copyright (c) 2018-2025, lengleng All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* Neither the name of the pig4cloud.com developer nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
* Author: lengleng (wangiegie@gmail.com)
*/
package com.pig4cloud.pigx.common.security.mobile;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.pig4cloud.pigx.common.security.component.PigxCommenceAuthExceptionEntryPoint;
import com.pig4cloud.pigx.common.security.service.PigxUserDetailsService;
import lombok.Getter;
import lombok.Setter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.SecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.DefaultSecurityFilterChain;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
/**
* @author lengleng
* @date 2018/8/5 手机号登录配置入口
*/
@Getter
@Setter
public class MobileSecurityConfigurer extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
@Autowired
private ObjectMapper objectMapper;
@Autowired
private AuthenticationSuccessHandler mobileLoginSuccessHandler;
@Autowired
private PigxUserDetailsService userDetailsService;
@Override
public void configure(HttpSecurity http) {
MobileAuthenticationFilter mobileAuthenticationFilter = new MobileAuthenticationFilter();
mobileAuthenticationFilter.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class));
mobileAuthenticationFilter.setAuthenticationSuccessHandler(mobileLoginSuccessHandler);
mobileAuthenticationFilter.setAuthenticationEntryPoint(new PigxCommenceAuthExceptionEntryPoint(objectMapper));
MobileAuthenticationProvider mobileAuthenticationProvider = new MobileAuthenticationProvider();
mobileAuthenticationProvider.setUserDetailsService(userDetailsService);
http.authenticationProvider(mobileAuthenticationProvider).addFilterAfter(mobileAuthenticationFilter,
UsernamePasswordAuthenticationFilter.class);
}
}
@@ -0,0 +1,90 @@
/*
* Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.pig4cloud.pigx.common.security.service;
import com.pig4cloud.pigx.admin.api.dto.UserInfo;
import com.pig4cloud.pigx.admin.api.feign.RemoteUserService;
import com.pig4cloud.pigx.common.core.constant.CacheConstants;
import com.pig4cloud.pigx.common.core.constant.SecurityConstants;
import com.pig4cloud.pigx.common.core.util.R;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
/**
* 用户详细信息
*
* @author lengleng hccake
*/
@Slf4j
@RequiredArgsConstructor
public class PigxAppUserDetailsServiceImpl implements PigxUserDetailsService {
private final UserDetailsService pigxDefaultUserDetailsServiceImpl;
private final RemoteUserService remoteUserService;
private final CacheManager cacheManager;
/**
* 手机号登录
* @param phone 手机号
* @return
*/
@Override
@SneakyThrows
public UserDetails loadUserByUsername(String phone) {
Cache cache = cacheManager.getCache(CacheConstants.USER_DETAILS);
if (cache != null && cache.get(phone) != null) {
return (PigxUser) cache.get(phone).get();
}
R<UserInfo> result = remoteUserService.social(phone, SecurityConstants.FROM_IN);
UserDetails userDetails = getUserDetails(result);
if (cache != null) {
cache.put(phone, userDetails);
}
return userDetails;
}
/**
* check-token 使用
* @param pigxUser user
* @return UserDetails
*/
@Override
public UserDetails loadUserByUser(PigxUser pigxUser) {
return pigxDefaultUserDetailsServiceImpl.loadUserByUsername(pigxUser.getUsername());
}
/**
* 支持所有的 mobile 类型
* @param clientId 目标客户端
* @param grantType 授权类型
* @return true/false
*/
@Override
public boolean support(String clientId, String grantType) {
return SecurityConstants.GRANT_MOBILE.equals(grantType);
}
}
@@ -17,13 +17,9 @@
package com.pig4cloud.pigx.common.security.service;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import com.pig4cloud.pigx.admin.api.dto.UserInfo;
import com.pig4cloud.pigx.admin.api.entity.SysUser;
import com.pig4cloud.pigx.admin.api.feign.RemoteUserService;
import com.pig4cloud.pigx.common.core.constant.CacheConstants;
import com.pig4cloud.pigx.common.core.constant.CommonConstants;
import com.pig4cloud.pigx.common.core.constant.SecurityConstants;
import com.pig4cloud.pigx.common.core.util.R;
import lombok.RequiredArgsConstructor;
@@ -32,25 +28,18 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.context.annotation.Primary;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
/**
* 用户详细信息
* 用户详细信息 default
*
* @author lengleng
*/
@Slf4j
@Primary
@RequiredArgsConstructor
public class PigxUserDetailsServiceImpl implements PigxUserDetailsService {
public class PigxDefaultUserDetailsServiceImpl implements PigxUserDetailsService {
private final RemoteUserService remoteUserService;
@@ -76,47 +65,9 @@ public class PigxUserDetailsServiceImpl implements PigxUserDetailsService {
return userDetails;
}
/**
* 根据社交登录code 登录
* @param inStr TYPE@CODE
* @return UserDetails
* @throws UsernameNotFoundException
*/
@Override
@SneakyThrows
public UserDetails loadUserBySocial(String inStr) {
return getUserDetails(remoteUserService.social(inStr, SecurityConstants.FROM_IN));
}
/**
* 构建userdetails
* @param result 用户信息
* @return
*/
private UserDetails getUserDetails(R<UserInfo> result) {
if (result == null || result.getData() == null) {
throw new UsernameNotFoundException("用户不存在");
}
UserInfo info = result.getData();
Set<String> dbAuthsSet = new HashSet<>();
if (ArrayUtil.isNotEmpty(info.getRoles())) {
// 获取角色
Arrays.stream(info.getRoles()).forEach(roleId -> dbAuthsSet.add(SecurityConstants.ROLE + roleId));
// 获取资源
dbAuthsSet.addAll(Arrays.asList(info.getPermissions()));
}
Collection<? extends GrantedAuthority> authorities = AuthorityUtils
.createAuthorityList(dbAuthsSet.toArray(new String[0]));
SysUser user = info.getSysUser();
boolean enabled = StrUtil.equals(user.getLockFlag(), CommonConstants.STATUS_NORMAL);
// 构造security用户
return new PigxUser(user.getUserId(), user.getUsername(), user.getDeptId(), user.getPhone(), user.getAvatar(),
user.getNickname(), user.getName(), user.getEmail(), user.getTenantId(),
SecurityConstants.BCRYPT + user.getPassword(), enabled, true, true,
!CommonConstants.STATUS_LOCK.equals(user.getLockFlag()), authorities);
public int getOrder() {
return Integer.MIN_VALUE;
}
}
@@ -16,22 +16,85 @@
*/
package com.pig4cloud.pigx.common.security.service;
import cn.hutool.core.util.ArrayUtil;
import com.pig4cloud.pigx.admin.api.dto.UserInfo;
import com.pig4cloud.pigx.admin.api.entity.SysUser;
import com.pig4cloud.pigx.common.core.constant.CommonConstants;
import com.pig4cloud.pigx.common.core.constant.SecurityConstants;
import com.pig4cloud.pigx.common.core.util.R;
import org.springframework.core.Ordered;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
/**
* @author lengleng
* @date 2018/8/15
*/
public interface PigxUserDetailsService extends UserDetailsService {
public interface PigxUserDetailsService extends UserDetailsService, Ordered {
/**
* 根据社交登录code 登录
* @param code TYPE@CODE
* @return UserDetails
* @throws UsernameNotFoundException
* 是否支持此客户端校验
* @param clientId 请求客户端
* @param grantType 授权类型
* @return true/false
*/
UserDetails loadUserBySocial(String code) throws UsernameNotFoundException;
default boolean support(String clientId, String grantType) {
return true;
}
/**
* 排序值 默认取最大的
* @return 排序值
*/
default int getOrder() {
return 0;
}
/**
* 构建userdetails
* @param result 用户信息
* @return UserDetails
*/
default UserDetails getUserDetails(R<UserInfo> result) {
if (result == null || result.getData() == null) {
throw new UsernameNotFoundException("用户不存在");
}
UserInfo info = result.getData();
Set<String> dbAuthsSet = new HashSet<>();
if (ArrayUtil.isNotEmpty(info.getRoles())) {
// 获取角色
Arrays.stream(info.getRoles()).forEach(roleId -> dbAuthsSet.add(SecurityConstants.ROLE + roleId));
// 获取资源
dbAuthsSet.addAll(Arrays.asList(info.getPermissions()));
}
Collection<? extends GrantedAuthority> authorities = AuthorityUtils
.createAuthorityList(dbAuthsSet.toArray(new String[0]));
SysUser user = info.getSysUser();
// 构造security用户
return new PigxUser(user.getUserId(), user.getUsername(), user.getDeptId(), user.getPhone(), user.getAvatar(),
user.getNickname(), user.getName(), user.getEmail(), user.getTenantId(),
SecurityConstants.BCRYPT + user.getPassword(), true, true, true,
!CommonConstants.STATUS_LOCK.equals(user.getLockFlag()), authorities);
}
/**
* 通过用户实体查询
* @param pigxUser user
* @return
*/
default UserDetails loadUserByUser(PigxUser pigxUser) {
return this.loadUserByUsername(pigxUser.getUsername());
}
}
@@ -1,7 +1,8 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.pig4cloud.pigx.common.security.component.PigxTokenEnhancer,\
com.pig4cloud.pigx.common.security.component.PigxSecurityInnerAspect,\
com.pig4cloud.pigx.common.security.service.PigxUserDetailsServiceImpl,\
com.pig4cloud.pigx.common.security.service.PigxDefaultUserDetailsServiceImpl,\
com.pig4cloud.pigx.common.security.service.PigxAppUserDetailsServiceImpl,\
com.pig4cloud.pigx.common.security.service.PigxClientDetailsServiceImpl,\
com.pig4cloud.pigx.common.security.component.PigxTokenStoreAutoConfiguration,\
com.pig4cloud.pigx.common.security.component.PigxSecurityMessageSourceConfiguration,\
@@ -67,8 +67,7 @@ public class ValidateCodeGatewayFilter extends AbstractGatewayFilterFactory {
ServerHttpRequest request = exchange.getRequest();
// 不是账号密码登录、短信登录,直接向下执行
if (!StrUtil.containsAnyIgnoreCase(request.getURI().getPath(), SecurityConstants.OAUTH_TOKEN_URL,
SecurityConstants.SMS_TOKEN_URL)) {
if (!StrUtil.containsAnyIgnoreCase(request.getURI().getPath(), SecurityConstants.OAUTH_TOKEN_URL)) {
return chain.filter(exchange);
}