♻️ Refactoring code. 重构认证中心代码 简易代码实现

This commit is contained in:
lbw 2022-06-04 17:10:23 +08:00
parent dc0931af40
commit 856c4e645e
6 changed files with 82 additions and 54 deletions

View File

@ -18,6 +18,7 @@ package com.pig4cloud.pig.auth.config;
import com.pig4cloud.pig.auth.support.CustomeOAuth2AccessTokenGenerator;
import com.pig4cloud.pig.auth.support.core.CustomeOAuth2TokenCustomizer;
import com.pig4cloud.pig.auth.support.core.FormIdentityLoginConfigurer;
import com.pig4cloud.pig.auth.support.core.PigDaoAuthenticationProvider;
import com.pig4cloud.pig.auth.support.handler.PigAuthenticationFailureEventHandler;
import com.pig4cloud.pig.auth.support.handler.PigAuthenticationSuccessEventHandler;
@ -43,6 +44,7 @@ import org.springframework.security.oauth2.server.authorization.web.authenticati
import org.springframework.security.web.DefaultSecurityFilterChain;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.AuthenticationConverter;
import org.springframework.security.web.util.matcher.RequestMatcher;
import java.util.Arrays;
@ -70,13 +72,12 @@ public class AuthorizationServerConfiguration {
}).authorizationEndpoint( // 授权码端点个性化confirm页面
authorizationEndpoint -> authorizationEndpoint.consentPage(SecurityConstants.CUSTOM_CONSENT_PAGE_URI)));
DefaultSecurityFilterChain securityFilterChain = http
.requestMatcher(authorizationServerConfigurer.getEndpointsMatcher())
.authorizeRequests(authorizeRequests -> authorizeRequests.anyRequest().authenticated())// 授权端点开启安全认证
RequestMatcher endpointsMatcher = authorizationServerConfigurer.getEndpointsMatcher();
DefaultSecurityFilterChain securityFilterChain = http.requestMatcher(endpointsMatcher)
.authorizeRequests(authorizeRequests -> authorizeRequests.anyRequest().authenticated())
.apply(authorizationServerConfigurer.authorizationService(authorizationService)// redis存储token的实现
.providerSettings(ProviderSettings.builder().issuer(SecurityConstants.PROJECT_LICENSE).build()))
.and()// SAS 统一认证调整至登录页面
.formLogin(loginConfigurer -> loginConfigurer.loginPage("/token/login")).csrf().disable().build();
.and().apply(new FormIdentityLoginConfigurer()).and().build();
// 注入自定义授权模式实现
addCustomOAuth2GrantAuthenticationProvider(http);

View File

@ -16,9 +16,8 @@
package com.pig4cloud.pig.auth.config;
import com.pig4cloud.pig.auth.support.core.FormIdentityLoginConfigurer;
import com.pig4cloud.pig.auth.support.core.PigDaoAuthenticationProvider;
import com.pig4cloud.pig.auth.support.handler.FormAuthenticationFailureHandler;
import com.pig4cloud.pig.auth.support.handler.SsoLogoutSuccessHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
@ -42,19 +41,9 @@ public class WebSecurityConfiguration {
*/
@Bean
SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
http.authorizeRequests(
// 暴露自定义 password 等端点
authorizeRequests -> authorizeRequests.antMatchers("/token/*").permitAll().anyRequest().authenticated())
// 个性化 formLogin
.formLogin().loginPage("/token/login").loginProcessingUrl("/token/form")
// SSO登录失败处理
.failureHandler(new FormAuthenticationFailureHandler()).and().logout()
// SSO登出成功处理
.logoutSuccessHandler(new SsoLogoutSuccessHandler()).deleteCookies("JSESSIONID")
.invalidateHttpSession(true)
// 禁止 login form csrf
.and().csrf().disable();
http.authorizeRequests(authorizeRequests -> authorizeRequests.antMatchers("/token/*").permitAll()// 开放自定义的部分端点
.anyRequest().authenticated()).headers().frameOptions().sameOrigin()// 避免iframe同源无法登录
.and().apply(new FormIdentityLoginConfigurer()); // 注入form login
// 处理 UsernamePasswordAuthenticationToken
http.authenticationProvider(new PigDaoAuthenticationProvider());
return http.build();

View File

@ -0,0 +1,29 @@
package com.pig4cloud.pig.auth.support.core;
import com.pig4cloud.pig.auth.support.handler.FormAuthenticationFailureHandler;
import com.pig4cloud.pig.auth.support.handler.SsoLogoutSuccessHandler;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
/**
* @author lengleng
* @data 2022-06-04
*
* 基于授权码模式 统一认证登录 spring security & sas 都可以使用 所以抽取成 HttpConfigurer
*/
public final class FormIdentityLoginConfigurer
extends AbstractHttpConfigurer<FormIdentityLoginConfigurer, HttpSecurity> {
@Override
public void init(HttpSecurity http) throws Exception {
http.formLogin(formLogin -> {
formLogin.loginPage("/token/login");
formLogin.loginProcessingUrl("/token/form");
formLogin.failureHandler(new FormAuthenticationFailureHandler());
}).logout() // SSO登出成功处理
.logoutSuccessHandler(new SsoLogoutSuccessHandler()).deleteCookies("JSESSIONID")
.invalidateHttpSession(true).and().csrf().disable();
}
}

View File

@ -1,10 +1,12 @@
package com.pig4cloud.pig.auth.support.core;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.servlet.ServletUtil;
import cn.hutool.extra.spring.SpringUtil;
import com.pig4cloud.pig.common.core.constant.SecurityConstants;
import com.pig4cloud.pig.common.core.util.WebUtils;
import com.pig4cloud.pig.common.security.service.PigUserDetailsService;
import lombok.SneakyThrows;
import org.springframework.core.Ordered;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.InternalAuthenticationServiceException;
@ -12,7 +14,6 @@ import org.springframework.security.authentication.UsernamePasswordAuthenticatio
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;
@ -20,12 +21,14 @@ import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationToken;
import org.springframework.security.web.authentication.www.BasicAuthenticationConverter;
import org.springframework.util.Assert;
import javax.servlet.http.HttpServletRequest;
import java.util.Comparator;
import java.util.Map;
import java.util.Optional;
import java.util.function.Supplier;
/**
* @author lengleng
@ -39,6 +42,8 @@ public class PigDaoAuthenticationProvider extends AbstractUserDetailsAuthenticat
*/
private static final String USER_NOT_FOUND_PASSWORD = "userNotFoundPassword";
private final static BasicAuthenticationConverter basicConvert = new BasicAuthenticationConverter();
private PasswordEncoder passwordEncoder;
/**
@ -82,29 +87,29 @@ public class PigDaoAuthenticationProvider extends AbstractUserDetailsAuthenticat
}
}
@SneakyThrows
@Override
protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException {
protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) {
prepareTimingAttackProtection();
HttpServletRequest request = WebUtils.getRequest().orElseThrow(
(Supplier<Throwable>) () -> new InternalAuthenticationServiceException("web request is empty"));
// 此处已获得 客户端认证 获取对应 userDetailsService
OAuth2ClientAuthenticationToken clientAuthentication = (OAuth2ClientAuthenticationToken) SecurityContextHolder
.getContext().getAuthentication();
Map<String, String> paramMap = ServletUtil.getParamMap(request);
String grantType = paramMap.get(OAuth2ParameterNames.GRANT_TYPE);
String clientId = paramMap.get(OAuth2ParameterNames.CLIENT_ID);
// SSO NPE 处理
String clientId;
if (clientAuthentication == null) {
clientId = WebUtils.getRequest().get().getParameter("clientId");
}
else {
clientId = clientAuthentication.getName();
if (StrUtil.isBlank(clientId)) {
clientId = basicConvert.convert(request).getName();
}
Map<String, PigUserDetailsService> userDetailsServiceMap = SpringUtil
.getBeansOfType(PigUserDetailsService.class);
String finalClientId = clientId;
Optional<PigUserDetailsService> optional = userDetailsServiceMap.values().stream()
.filter(service -> service.support(clientId, null)).max(Comparator.comparingInt(Ordered::getOrder));
.filter(service -> service.support(finalClientId, grantType))
.max(Comparator.comparingInt(Ordered::getOrder));
if (!optional.isPresent()) {
throw new InternalAuthenticationServiceException("UserDetailsService error , not register");

View File

@ -98,6 +98,9 @@ public class PigAuthenticationSuccessEventHandler implements AuthenticationSucce
}
OAuth2AccessTokenResponse accessTokenResponse = builder.build();
ServletServerHttpResponse httpResponse = new ServletServerHttpResponse(response);
// 无状态 注意删除 context 上下文的信息
SecurityContextHolder.clearContext();
this.accessTokenHttpResponseConverter.write(accessTokenResponse, null, httpResponse);
}

View File

@ -1,35 +1,36 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- The above 3 meta tags *must* come first in the head; any other head content must come *after* these tags -->
<meta name="description" content="">
<meta name="author" content="">
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- The above 3 meta tags *must* come first in the head; any other head content must come *after* these tags -->
<meta name="description" content="">
<meta name="author" content="">
<title>Pig 微服务统一认证</title>
<title>Pig 微服务统一认证</title>
<link href="/css/bootstrap.min.css" rel="stylesheet">
<link href="/css/signin.css" rel="stylesheet">
<link href="/css/bootstrap.min.css" rel="stylesheet">
<link href="/css/signin.css" rel="stylesheet">
</head>
<body class="sign_body">
<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>
<form class="form-signin" action="/token/form" method="post">
<h2 class="form-signin-heading" align="center">统一认证系统</h2>
<input type="hidden" name="client_id" class="form-control" value="pig" placeholder="所属客户端">
<input type="hidden" name="grant_type" class="form-control" value="password" 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>
<#if error??>
<span style="color: red; ">${error}</span>
<span style="color: red; ">${error}</span>
</#if>
</form>
</form>
</div>
<footer>
<p>support by: pig4cloud</p>
<p>email: <a href="mailto:pig4cloud@qq.com">pig4cloud@qq.com</a>.</p>
<p>support by: pig4cloud</p>
<p>email: <a href="mailto:pig4cloud@qq.com">pig4cloud@qq.com</a>.</p>
</footer>
</body>
</html>