♻️ Refactoring code. 重构支持多用户体系, SSO 多用户体系

This commit is contained in:
qraddx 2022-01-30 00:03:00 +08:00
parent 2bee5ba814
commit fb60fc51ce
11 changed files with 200 additions and 14 deletions

View File

@ -33,11 +33,11 @@
| 依赖 | 版本 |
| ---------------------- |----------|
| Spring Boot | 2.6.2 |
| Spring Boot | 2.6.3 |
| Spring Cloud | 2021.0.0 |
| Spring Cloud Alibaba | 2021.1 |
| Spring Security OAuth2 | 2.3.6 |
| Mybatis Plus | 3.5.0 |
| Mybatis Plus | 3.5.1 |
| hutool | 5.7.19 |
| Avue | 2.6.18 |

View File

@ -16,6 +16,7 @@
package com.pig4cloud.pig.auth.config;
import com.pig4cloud.pig.common.security.component.PigDaoAuthenticationProvider;
import com.pig4cloud.pig.common.security.grant.CustomAppAuthenticationProvider;
import com.pig4cloud.pig.common.security.handler.FormAuthenticationFailureHandler;
import com.pig4cloud.pig.common.security.handler.SsoLogoutSuccessHandler;
@ -26,7 +27,6 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.annotation.Order;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
@ -65,9 +65,8 @@ public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) {
DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
PigDaoAuthenticationProvider daoAuthenticationProvider = new PigDaoAuthenticationProvider();
daoAuthenticationProvider.setPasswordEncoder(passwordEncoder());
daoAuthenticationProvider.setUserDetailsService(pigUserDetailsServiceImpl);
// 处理默认的密码模式认证
auth.authenticationProvider(daoAuthenticationProvider);

View File

@ -18,6 +18,7 @@
<div class="container form-margin-top">
<form class="form-signin" action="/token/form" method="post">
<h2 class="form-signin-heading" align="center">统一认证系统</h2>
<input type="hidden" name="clientId" class="form-control" value="pig" placeholder="所属客户端" >
<input type="text" name="username" class="form-control form-margin-top" placeholder="账号" required autofocus>
<input type="password" name="password" class="form-control" placeholder="密码" required>
<button class="btn btn-lg btn-primary btn-block" type="submit">sign in</button>

View File

@ -15,7 +15,7 @@
<properties>
<pig.common.version>${project.version}</pig.common.version>
<spring-boot.version>2.6.2</spring-boot.version>
<spring-boot.version>2.6.3</spring-boot.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<security.oauth.version>2.1.8.RELEASE</security.oauth.version>
<log4j2.version>2.17.1</log4j2.version>
@ -25,7 +25,7 @@
<spring.checkstyle.plugin>0.0.29</spring.checkstyle.plugin>
<fastjson.version>1.2.78</fastjson.version>
<swagger.core.version>1.5.24</swagger.core.version>
<mybatis-plus.version>3.5.0</mybatis-plus.version>
<mybatis-plus.version>3.5.1</mybatis-plus.version>
<nacos.version>2.0.3</nacos.version>
<excel.version>1.1.0</excel.version>
<oss.version>1.0.3</oss.version>

View File

@ -0,0 +1,174 @@
package com.pig4cloud.pig.common.security.component;
import cn.hutool.extra.spring.SpringUtil;
import com.pig4cloud.pig.common.core.util.WebUtils;
import com.pig4cloud.pig.common.security.service.PigUserDetailsService;
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 PigDaoAuthenticationProvider 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 PigDaoAuthenticationProvider() {
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().get().getParameter("clientId");
}
else {
clientId = clientAuthentication.getName();
}
Map<String, PigUserDetailsService> userDetailsServiceMap = SpringUtil
.getBeansOfType(PigUserDetailsService.class);
Optional<PigUserDetailsService> 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;
}
}

View File

@ -50,7 +50,8 @@ public class PigLocalResourceServerTokenServices implements ResourceServerTokenS
Map<String, PigUserDetailsService> userDetailsServiceMap = SpringUtil
.getBeansOfType(PigUserDetailsService.class);
Optional<PigUserDetailsService> optional = userDetailsServiceMap.values().stream()
.filter(service -> service.support(clientId)).max(Comparator.comparingInt(Ordered::getOrder));
.filter(service -> service.support(clientId, oAuth2Request.getGrantType()))
.max(Comparator.comparingInt(Ordered::getOrder));
if (!optional.isPresent()) {
throw new InternalAuthenticationServiceException("UserDetailsService error , not register");

View File

@ -59,13 +59,16 @@ public class CustomAppAuthenticationProvider extends AbstractUserDetailsAuthenti
throw new BadCredentialsException("Bad credentials");
}
CustomAppAuthenticationToken requestToken = (CustomAppAuthenticationToken) authentication;
// 此处已获得 客户端认证 获取对应 userDetailsService
Authentication clientAuthentication = SecurityContextHolder.getContext().getAuthentication();
String clientId = clientAuthentication.getName();
Map<String, PigUserDetailsService> userDetailsServiceMap = SpringUtil
.getBeansOfType(PigUserDetailsService.class);
Optional<PigUserDetailsService> optional = userDetailsServiceMap.values().stream()
.filter(service -> service.support(clientId)).max(Comparator.comparingInt(Ordered::getOrder));
.filter(service -> service.support(clientId, requestToken.getGrantType()))
.max(Comparator.comparingInt(Ordered::getOrder));
if (!optional.isPresent()) {
throw new InternalAuthenticationServiceException("UserDetailsService error , not register");

View File

@ -1,5 +1,6 @@
package com.pig4cloud.pig.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;
@ -15,10 +16,17 @@ public class CustomAppAuthenticationToken extends AbstractAuthenticationToken {
// 验证码/密码
private String code;
public CustomAppAuthenticationToken(String phone, 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) {

View File

@ -56,7 +56,7 @@ public class ResourceOwnerCustomeAppTokenGranter extends AbstractTokenGranter {
// Protect from downstream leaks of code
parameters.remove("code");
Authentication userAuth = new CustomAppAuthenticationToken(mobile, code);
Authentication userAuth = new CustomAppAuthenticationToken(mobile, code, tokenRequest.getGrantType());
((AbstractAuthenticationToken) userAuth).setDetails(parameters);
try {
userAuth = authenticationManager.authenticate(userAuth);

View File

@ -79,8 +79,8 @@ public class PigAppUserDetailsServiceImpl implements PigUserDetailsService {
* @return true/false
*/
@Override
public boolean support(String clientId) {
return "app".equals(clientId);
public boolean support(String clientId, String grantType) {
return SecurityConstants.APP.equals(grantType);
}
}

View File

@ -30,7 +30,7 @@ public interface PigUserDetailsService extends UserDetailsService, Ordered {
* @param clientId 目标客户端
* @return true/false
*/
default boolean support(String clientId) {
default boolean support(String clientId, String grantType) {
return true;
}