mirror of
https://gitee.com/youlaitech/youlai-mall.git
synced 2024-12-22 12:48:59 +08:00
feat: 完善OAuth2自定义异常处理逻辑
This commit is contained in:
parent
a07a3c8839
commit
4a943ab531
@ -43,11 +43,6 @@
|
|||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- OAuth2 认证服务器-->
|
<!-- OAuth2 认证服务器-->
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework.boot</groupId>
|
|
||||||
<artifactId>spring-boot-starter-security</artifactId>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.security</groupId>
|
<groupId>org.springframework.security</groupId>
|
||||||
<artifactId>spring-security-oauth2-authorization-server</artifactId>
|
<artifactId>spring-security-oauth2-authorization-server</artifactId>
|
||||||
|
@ -94,7 +94,7 @@ public class CaptchaAuthenticationProvider implements AuthenticationProvider {
|
|||||||
String username = (String) additionalParameters.get(OAuth2ParameterNames.USERNAME);
|
String username = (String) additionalParameters.get(OAuth2ParameterNames.USERNAME);
|
||||||
String password = (String) additionalParameters.get(OAuth2ParameterNames.PASSWORD);
|
String password = (String) additionalParameters.get(OAuth2ParameterNames.PASSWORD);
|
||||||
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(username, password);
|
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(username, password);
|
||||||
// 用户名密码身份验证,成功后返回带有权限的认证信息
|
// 用户名密码身份验证,成功后返回 带有权限的认证信息
|
||||||
Authentication usernamePasswordAuthentication = authenticationManager.authenticate(usernamePasswordAuthenticationToken);
|
Authentication usernamePasswordAuthentication = authenticationManager.authenticate(usernamePasswordAuthenticationToken);
|
||||||
|
|
||||||
// 访问令牌(Access Token) 构造器
|
// 访问令牌(Access Token) 构造器
|
||||||
@ -119,6 +119,7 @@ public class CaptchaAuthenticationProvider implements AuthenticationProvider {
|
|||||||
generatedAccessToken.getTokenValue(), generatedAccessToken.getIssuedAt(),
|
generatedAccessToken.getTokenValue(), generatedAccessToken.getIssuedAt(),
|
||||||
generatedAccessToken.getExpiresAt(), tokenContext.getAuthorizedScopes());
|
generatedAccessToken.getExpiresAt(), tokenContext.getAuthorizedScopes());
|
||||||
|
|
||||||
|
|
||||||
OAuth2Authorization.Builder authorizationBuilder = OAuth2Authorization.withRegisteredClient(registeredClient)
|
OAuth2Authorization.Builder authorizationBuilder = OAuth2Authorization.withRegisteredClient(registeredClient)
|
||||||
.principalName(usernamePasswordAuthentication.getName())
|
.principalName(usernamePasswordAuthentication.getName())
|
||||||
.authorizationGrantType(CaptchaAuthenticationToken.CAPTCHA)
|
.authorizationGrantType(CaptchaAuthenticationToken.CAPTCHA)
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package com.youlai.auth.authentication.wxminiapp;
|
package com.youlai.auth.authentication.miniapp;
|
||||||
|
|
||||||
import cn.hutool.core.util.StrUtil;
|
import cn.hutool.core.util.StrUtil;
|
||||||
import com.youlai.auth.util.OAuth2EndpointUtils;
|
import com.youlai.auth.util.OAuth2EndpointUtils;
|
@ -1,4 +1,4 @@
|
|||||||
package com.youlai.auth.authentication.wxminiapp;
|
package com.youlai.auth.authentication.miniapp;
|
||||||
|
|
||||||
import cn.binarywang.wx.miniapp.api.WxMaService;
|
import cn.binarywang.wx.miniapp.api.WxMaService;
|
||||||
import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult;
|
import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult;
|
@ -1,4 +1,4 @@
|
|||||||
package com.youlai.auth.authentication.wxminiapp;
|
package com.youlai.auth.authentication.miniapp;
|
||||||
|
|
||||||
import jakarta.annotation.Nullable;
|
import jakarta.annotation.Nullable;
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
@ -2,6 +2,7 @@ package com.youlai.auth.authentication.password;
|
|||||||
|
|
||||||
import cn.hutool.core.util.StrUtil;
|
import cn.hutool.core.util.StrUtil;
|
||||||
import com.youlai.auth.util.OAuth2EndpointUtils;
|
import com.youlai.auth.util.OAuth2EndpointUtils;
|
||||||
|
import org.springframework.security.authentication.InternalAuthenticationServiceException;
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||||
@ -32,6 +33,7 @@ public class PasswordAuthenticationConverter implements AuthenticationConverter
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Authentication convert(HttpServletRequest request) {
|
public Authentication convert(HttpServletRequest request) {
|
||||||
|
|
||||||
// 授权类型 (必需)
|
// 授权类型 (必需)
|
||||||
String grantType = request.getParameter(OAuth2ParameterNames.GRANT_TYPE);
|
String grantType = request.getParameter(OAuth2ParameterNames.GRANT_TYPE);
|
||||||
if (!AuthorizationGrantType.PASSWORD.getValue().equals(grantType)) {
|
if (!AuthorizationGrantType.PASSWORD.getValue().equals(grantType)) {
|
||||||
|
@ -2,13 +2,15 @@ package com.youlai.auth.authentication.password;
|
|||||||
|
|
||||||
|
|
||||||
import cn.hutool.core.lang.Assert;
|
import cn.hutool.core.lang.Assert;
|
||||||
|
import cn.hutool.core.util.ReflectUtil;
|
||||||
import com.youlai.auth.util.OAuth2AuthenticationProviderUtils;
|
import com.youlai.auth.util.OAuth2AuthenticationProviderUtils;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.security.authentication.AuthenticationManager;
|
import org.springframework.security.authentication.*;
|
||||||
import org.springframework.security.authentication.AuthenticationProvider;
|
import org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider;
|
||||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
import org.springframework.security.core.AuthenticationException;
|
import org.springframework.security.core.AuthenticationException;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetailsChecker;
|
||||||
import org.springframework.security.oauth2.core.*;
|
import org.springframework.security.oauth2.core.*;
|
||||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||||
import org.springframework.security.oauth2.core.oidc.OidcIdToken;
|
import org.springframework.security.oauth2.core.oidc.OidcIdToken;
|
||||||
@ -35,7 +37,6 @@ import java.util.stream.Collectors;
|
|||||||
* 密码模式身份验证提供者
|
* 密码模式身份验证提供者
|
||||||
* <p>
|
* <p>
|
||||||
* 处理基于用户名和密码的身份验证
|
* 处理基于用户名和密码的身份验证
|
||||||
*
|
|
||||||
* @author haoxr
|
* @author haoxr
|
||||||
* @since 3.0.0
|
* @since 3.0.0
|
||||||
*/
|
*/
|
||||||
@ -86,7 +87,12 @@ public class PasswordAuthenticationProvider implements AuthenticationProvider {
|
|||||||
Map<String, Object> additionalParameters = resourceOwnerPasswordAuthentication.getAdditionalParameters();
|
Map<String, Object> additionalParameters = resourceOwnerPasswordAuthentication.getAdditionalParameters();
|
||||||
String username = (String) additionalParameters.get(OAuth2ParameterNames.USERNAME);
|
String username = (String) additionalParameters.get(OAuth2ParameterNames.USERNAME);
|
||||||
String password = (String) additionalParameters.get(OAuth2ParameterNames.PASSWORD);
|
String password = (String) additionalParameters.get(OAuth2ParameterNames.PASSWORD);
|
||||||
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(username, password);
|
|
||||||
|
// 这种
|
||||||
|
// https://github.com/Basit-Mahmood/spring-authorization-server-password-grant-type-support/blob/master/SpringAuthorizationServer/src/main/java/pk/training/basit/oauth2/authentication/OAuth2ResourceOwnerPasswordAuthenticationProvider.java
|
||||||
|
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(username, password);
|
||||||
|
|
||||||
|
|
||||||
// 用户名密码身份验证,成功后返回带有权限的认证信息
|
// 用户名密码身份验证,成功后返回带有权限的认证信息
|
||||||
Authentication usernamePasswordAuthentication = authenticationManager.authenticate(usernamePasswordAuthenticationToken);
|
Authentication usernamePasswordAuthentication = authenticationManager.authenticate(usernamePasswordAuthenticationToken);
|
||||||
|
|
||||||
@ -126,11 +132,15 @@ public class PasswordAuthenticationProvider implements AuthenticationProvider {
|
|||||||
generatedAccessToken.getTokenValue(), generatedAccessToken.getIssuedAt(),
|
generatedAccessToken.getTokenValue(), generatedAccessToken.getIssuedAt(),
|
||||||
generatedAccessToken.getExpiresAt(), tokenContext.getAuthorizedScopes());
|
generatedAccessToken.getExpiresAt(), tokenContext.getAuthorizedScopes());
|
||||||
|
|
||||||
|
|
||||||
|
ReflectUtil.setFieldValue(usernamePasswordAuthentication.getPrincipal(), "perms", null);
|
||||||
|
|
||||||
|
// 持久化令牌发放记录到数据库
|
||||||
OAuth2Authorization.Builder authorizationBuilder = OAuth2Authorization.withRegisteredClient(registeredClient)
|
OAuth2Authorization.Builder authorizationBuilder = OAuth2Authorization.withRegisteredClient(registeredClient)
|
||||||
.principalName(usernamePasswordAuthentication.getName())
|
.principalName(usernamePasswordAuthentication.getName())
|
||||||
.authorizationGrantType(AuthorizationGrantType.PASSWORD)
|
.authorizationGrantType(AuthorizationGrantType.PASSWORD)
|
||||||
.authorizedScopes(authorizedScopes)
|
.authorizedScopes(authorizedScopes)
|
||||||
.attribute(Principal.class.getName(), usernamePasswordAuthentication);
|
.attribute(Principal.class.getName(), usernamePasswordAuthentication); // attribute 字段
|
||||||
if (generatedAccessToken instanceof ClaimAccessor) {
|
if (generatedAccessToken instanceof ClaimAccessor) {
|
||||||
authorizationBuilder.token(accessToken, (metadata) ->
|
authorizationBuilder.token(accessToken, (metadata) ->
|
||||||
metadata.put(OAuth2Authorization.Token.CLAIMS_METADATA_NAME, ((ClaimAccessor) generatedAccessToken).getClaims()));
|
metadata.put(OAuth2Authorization.Token.CLAIMS_METADATA_NAME, ((ClaimAccessor) generatedAccessToken).getClaims()));
|
||||||
@ -197,4 +207,6 @@ public class PasswordAuthenticationProvider implements AuthenticationProvider {
|
|||||||
return PasswordAuthenticationToken.class.isAssignableFrom(authentication);
|
return PasswordAuthenticationToken.class.isAssignableFrom(authentication);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,8 @@
|
|||||||
package com.youlai.auth.config;
|
package com.youlai.auth.config;
|
||||||
|
|
||||||
import cn.binarywang.wx.miniapp.api.WxMaService;
|
import cn.binarywang.wx.miniapp.api.WxMaService;
|
||||||
|
import cn.hutool.core.collection.CollectionUtil;
|
||||||
|
import cn.hutool.core.convert.Convert;
|
||||||
import com.fasterxml.jackson.databind.Module;
|
import com.fasterxml.jackson.databind.Module;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import com.nimbusds.jose.jwk.JWKSet;
|
import com.nimbusds.jose.jwk.JWKSet;
|
||||||
@ -17,14 +19,16 @@ import com.youlai.auth.authentication.password.PasswordAuthenticationProvider;
|
|||||||
import com.youlai.auth.authentication.smscode.SmsCodeAuthenticationConverter;
|
import com.youlai.auth.authentication.smscode.SmsCodeAuthenticationConverter;
|
||||||
import com.youlai.auth.authentication.smscode.SmsCodeAuthenticationProvider;
|
import com.youlai.auth.authentication.smscode.SmsCodeAuthenticationProvider;
|
||||||
import com.youlai.auth.authentication.smscode.SmsCodeAuthenticationToken;
|
import com.youlai.auth.authentication.smscode.SmsCodeAuthenticationToken;
|
||||||
import com.youlai.auth.authentication.wxminiapp.WxMiniAppAuthenticationConverter;
|
import com.youlai.auth.authentication.miniapp.WxMiniAppAuthenticationConverter;
|
||||||
import com.youlai.auth.authentication.wxminiapp.WxMiniAppAuthenticationProvider;
|
import com.youlai.auth.authentication.miniapp.WxMiniAppAuthenticationProvider;
|
||||||
import com.youlai.auth.authentication.wxminiapp.WxMiniAppAuthenticationToken;
|
import com.youlai.auth.authentication.miniapp.WxMiniAppAuthenticationToken;
|
||||||
|
import com.youlai.auth.handler.MyAuthenticationFailureHandler;
|
||||||
import com.youlai.auth.handler.MyAuthenticationSuccessHandler;
|
import com.youlai.auth.handler.MyAuthenticationSuccessHandler;
|
||||||
import com.youlai.auth.userdetails.member.MemberDetailsService;
|
import com.youlai.auth.userdetails.member.MemberDetailsService;
|
||||||
import com.youlai.auth.userdetails.user.SysUserDetails;
|
import com.youlai.auth.userdetails.user.SysUserDetails;
|
||||||
import com.youlai.auth.userdetails.user.jackson.SysUseMixin;
|
import com.youlai.auth.userdetails.user.jackson.SysUserMixin;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.Setter;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
@ -34,8 +38,10 @@ import org.springframework.data.redis.core.StringRedisTemplate;
|
|||||||
import org.springframework.jdbc.core.JdbcTemplate;
|
import org.springframework.jdbc.core.JdbcTemplate;
|
||||||
import org.springframework.jdbc.support.lob.DefaultLobHandler;
|
import org.springframework.jdbc.support.lob.DefaultLobHandler;
|
||||||
import org.springframework.security.authentication.AuthenticationManager;
|
import org.springframework.security.authentication.AuthenticationManager;
|
||||||
|
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
|
||||||
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
|
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
|
||||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||||
|
import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer;
|
||||||
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
|
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
|
||||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
import org.springframework.security.jackson2.SecurityJackson2Modules;
|
import org.springframework.security.jackson2.SecurityJackson2Modules;
|
||||||
@ -98,22 +104,23 @@ public class AuthorizationServerConfig {
|
|||||||
|
|
||||||
) throws Exception {
|
) throws Exception {
|
||||||
|
|
||||||
OAuth2AuthorizationServerConfigurer authorizationServerConfigurer =
|
OAuth2AuthorizationServerConfigurer authorizationServerConfigurer = new OAuth2AuthorizationServerConfigurer();
|
||||||
new OAuth2AuthorizationServerConfigurer();
|
|
||||||
authorizationServerConfigurer
|
authorizationServerConfigurer
|
||||||
.tokenEndpoint(tokenEndpoint ->
|
.tokenEndpoint(tokenEndpoint ->
|
||||||
tokenEndpoint
|
tokenEndpoint
|
||||||
.accessTokenRequestConverters(authenticationConverters ->// <1>
|
.accessTokenRequestConverters(
|
||||||
authenticationConverters.addAll(
|
authenticationConverters ->// <1>
|
||||||
List.of(
|
authenticationConverters.addAll(
|
||||||
new PasswordAuthenticationConverter(),
|
List.of(
|
||||||
new CaptchaAuthenticationConverter(),
|
new PasswordAuthenticationConverter(),
|
||||||
new WxMiniAppAuthenticationConverter(),
|
new CaptchaAuthenticationConverter(),
|
||||||
new SmsCodeAuthenticationConverter()
|
new WxMiniAppAuthenticationConverter(),
|
||||||
|
new SmsCodeAuthenticationConverter()
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
|
||||||
)
|
)
|
||||||
.authenticationProviders(authenticationProviders ->// <2>
|
/*.authenticationProviders(authenticationProviders ->// <2>
|
||||||
authenticationProviders.addAll(
|
authenticationProviders.addAll(
|
||||||
List.of(
|
List.of(
|
||||||
new PasswordAuthenticationProvider(authenticationManager, authorizationService, tokenGenerator),
|
new PasswordAuthenticationProvider(authenticationManager, authorizationService, tokenGenerator),
|
||||||
@ -122,25 +129,37 @@ public class AuthorizationServerConfig {
|
|||||||
new SmsCodeAuthenticationProvider(authorizationService, tokenGenerator, memberDetailsService, redisTemplate)
|
new SmsCodeAuthenticationProvider(authorizationService, tokenGenerator, memberDetailsService, redisTemplate)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)*/
|
||||||
.accessTokenResponseHandler(new MyAuthenticationSuccessHandler())
|
.accessTokenResponseHandler(new MyAuthenticationSuccessHandler()) // 自定义成功响应
|
||||||
|
.errorResponseHandler(new MyAuthenticationFailureHandler()) // 自定义异常响应
|
||||||
);
|
);
|
||||||
|
|
||||||
RequestMatcher endpointsMatcher = authorizationServerConfigurer.getEndpointsMatcher();
|
|
||||||
|
|
||||||
http
|
RequestMatcher endpointsMatcher = authorizationServerConfigurer.getEndpointsMatcher();
|
||||||
.securityMatcher(endpointsMatcher)
|
http.securityMatcher(endpointsMatcher)
|
||||||
.authorizeHttpRequests(authorize ->
|
.authorizeHttpRequests(authorizeRequests -> authorizeRequests.anyRequest().authenticated())
|
||||||
authorize
|
|
||||||
.anyRequest().authenticated()
|
|
||||||
)
|
|
||||||
.csrf(csrf -> csrf.ignoringRequestMatchers(endpointsMatcher))
|
.csrf(csrf -> csrf.ignoringRequestMatchers(endpointsMatcher))
|
||||||
.apply(authorizationServerConfigurer);
|
.apply(authorizationServerConfigurer);
|
||||||
|
|
||||||
|
|
||||||
|
AuthenticationManagerBuilder authenticationManagerBuilder = http.getSharedObject(AuthenticationManagerBuilder.class);
|
||||||
|
authenticationManagerBuilder.parentAuthenticationManager(null);
|
||||||
|
authenticationManagerBuilder.authenticationProvider(new PasswordAuthenticationProvider(authenticationManager, authorizationService, tokenGenerator));
|
||||||
|
|
||||||
|
|
||||||
return http.build();
|
return http.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* @Bean
|
||||||
|
public AuthenticationProvider daoAuthenticationProvider() {
|
||||||
|
DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
|
||||||
|
daoAuthenticationProvider.setUserDetailsService(userDetailsService);
|
||||||
|
daoAuthenticationProvider.setHideUserNotFoundExceptions(false) ; // 抛出用户不存在异常
|
||||||
|
return daoAuthenticationProvider ;
|
||||||
|
}*/
|
||||||
|
|
||||||
|
|
||||||
@Bean // <5>
|
@Bean // <5>
|
||||||
public JWKSource<SecurityContext> jwkSource() {
|
public JWKSource<SecurityContext> jwkSource() {
|
||||||
KeyPair keyPair = generateRsaKey();
|
KeyPair keyPair = generateRsaKey();
|
||||||
@ -208,7 +227,7 @@ public class AuthorizationServerConfig {
|
|||||||
objectMapper.registerModules(securityModules);
|
objectMapper.registerModules(securityModules);
|
||||||
objectMapper.registerModule(new OAuth2AuthorizationServerJackson2Module());
|
objectMapper.registerModule(new OAuth2AuthorizationServerJackson2Module());
|
||||||
// You will need to write the Mixin for your class so Jackson can marshall it.
|
// You will need to write the Mixin for your class so Jackson can marshall it.
|
||||||
objectMapper.addMixIn(SysUserDetails.class, SysUseMixin.class);
|
objectMapper.addMixIn(SysUserDetails.class, SysUserMixin.class);
|
||||||
objectMapper.addMixIn(Long.class, Object.class);
|
objectMapper.addMixIn(Long.class, Object.class);
|
||||||
rowMapper.setObjectMapper(objectMapper);
|
rowMapper.setObjectMapper(objectMapper);
|
||||||
service.setAuthorizationRowMapper(rowMapper);
|
service.setAuthorizationRowMapper(rowMapper);
|
||||||
@ -236,11 +255,9 @@ public class AuthorizationServerConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
|
public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
|
||||||
|
|
||||||
return authenticationConfiguration.getAuthenticationManager();
|
return authenticationConfiguration.getAuthenticationManager();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,26 +6,22 @@ import lombok.RequiredArgsConstructor;
|
|||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.core.annotation.Order;
|
||||||
|
import org.springframework.security.config.Customizer;
|
||||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||||
import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer;
|
import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer;
|
||||||
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
|
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
|
||||||
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
|
|
||||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
|
||||||
import org.springframework.security.web.SecurityFilterChain;
|
import org.springframework.security.web.SecurityFilterChain;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import static org.springframework.security.config.Customizer.withDefaults;
|
@EnableWebSecurity
|
||||||
|
@Configuration(proxyBeanMethods = false)
|
||||||
@Configuration
|
public class DefaultSecurityConfig {
|
||||||
@EnableWebSecurity(debug = true)
|
|
||||||
@RequiredArgsConstructor
|
|
||||||
public class SecurityConfig {
|
|
||||||
|
|
||||||
@Setter
|
@Setter
|
||||||
private List<String> ignoreUrls;
|
private List<String> ignoreUrls;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Spring Security 安全过滤器链配置
|
* Spring Security 安全过滤器链配置
|
||||||
*
|
*
|
||||||
@ -33,6 +29,7 @@ public class SecurityConfig {
|
|||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
@Bean
|
@Bean
|
||||||
|
@Order(0)
|
||||||
SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
|
SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
|
||||||
http
|
http
|
||||||
.authorizeHttpRequests(requestMatcherRegistry ->
|
.authorizeHttpRequests(requestMatcherRegistry ->
|
||||||
@ -45,8 +42,8 @@ public class SecurityConfig {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
.csrf(AbstractHttpConfigurer::disable)
|
.csrf(AbstractHttpConfigurer::disable)
|
||||||
.formLogin(withDefaults())
|
.formLogin(Customizer.withDefaults());
|
||||||
;
|
|
||||||
return http.build();
|
return http.build();
|
||||||
}
|
}
|
||||||
|
|
@ -8,7 +8,6 @@ import org.springframework.context.annotation.Bean;
|
|||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.data.redis.core.RedisTemplate;
|
import org.springframework.data.redis.core.RedisTemplate;
|
||||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||||
import org.springframework.security.core.GrantedAuthority;
|
|
||||||
import org.springframework.security.core.authority.AuthorityUtils;
|
import org.springframework.security.core.authority.AuthorityUtils;
|
||||||
import org.springframework.security.core.userdetails.User;
|
import org.springframework.security.core.userdetails.User;
|
||||||
import org.springframework.security.oauth2.core.oidc.endpoint.OidcParameterNames;
|
import org.springframework.security.oauth2.core.oidc.endpoint.OidcParameterNames;
|
||||||
@ -17,7 +16,6 @@ import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
|
|||||||
import org.springframework.security.oauth2.server.authorization.token.JwtEncodingContext;
|
import org.springframework.security.oauth2.server.authorization.token.JwtEncodingContext;
|
||||||
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenCustomizer;
|
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenCustomizer;
|
||||||
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
@ -27,16 +25,17 @@ import java.util.stream.Collectors;
|
|||||||
* 自定义 JWT 的 Claims(声明)
|
* 自定义 JWT 的 Claims(声明)
|
||||||
*
|
*
|
||||||
* @author haoxr
|
* @author haoxr
|
||||||
|
* @see <a href="https://github.com/spring-projects/spring-authorization-server/pull/1264">How-to: Authorize an access token containing custom authorities</a>
|
||||||
* @since 2023/7/4
|
* @since 2023/7/4
|
||||||
*/
|
*/
|
||||||
@Configuration
|
@Configuration
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class JwtCustomizerConfig {
|
public class JwtTokenClaimsConfig {
|
||||||
|
|
||||||
private final RedisTemplate redisTemplate;
|
private final RedisTemplate redisTemplate;
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public OAuth2TokenCustomizer<JwtEncodingContext> jwtCustomizer() {
|
public OAuth2TokenCustomizer<JwtEncodingContext> jwtTokenCustomizer() {
|
||||||
return context -> {
|
return context -> {
|
||||||
if (OAuth2TokenType.ACCESS_TOKEN.equals(context.getTokenType()) && context.getPrincipal() instanceof UsernamePasswordAuthenticationToken) {
|
if (OAuth2TokenType.ACCESS_TOKEN.equals(context.getTokenType()) && context.getPrincipal() instanceof UsernamePasswordAuthenticationToken) {
|
||||||
// Customize headers/claims for access_token
|
// Customize headers/claims for access_token
|
||||||
@ -59,7 +58,7 @@ public class JwtCustomizerConfig {
|
|||||||
|
|
||||||
} else if (principal instanceof MemberDetails userDetails) {
|
} else if (principal instanceof MemberDetails userDetails) {
|
||||||
claims.claim("member_id", String.valueOf(userDetails.getId()));
|
claims.claim("member_id", String.valueOf(userDetails.getId()));
|
||||||
}else{
|
} else {
|
||||||
User user = (User) principal;
|
User user = (User) principal;
|
||||||
|
|
||||||
// 这里存入角色至JWT,解析JWT的角色用于鉴权的位置: ResourceServerConfig#jwtAuthenticationConverter
|
// 这里存入角色至JWT,解析JWT的角色用于鉴权的位置: ResourceServerConfig#jwtAuthenticationConverter
|
@ -3,6 +3,8 @@ package com.youlai.auth.controller;
|
|||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* 认证控制器
|
||||||
|
*
|
||||||
* @author haoxr
|
* @author haoxr
|
||||||
* @since 2023/6/29
|
* @since 2023/6/29
|
||||||
*/
|
*/
|
||||||
|
@ -0,0 +1,42 @@
|
|||||||
|
package com.youlai.auth.handler;
|
||||||
|
|
||||||
|
import com.youlai.common.result.Result;
|
||||||
|
import jakarta.servlet.ServletException;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.converter.HttpMessageConverter;
|
||||||
|
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
|
||||||
|
import org.springframework.http.server.ServletServerHttpResponse;
|
||||||
|
import org.springframework.security.core.AuthenticationException;
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2Error;
|
||||||
|
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 认证异常处理器
|
||||||
|
*
|
||||||
|
* @author haoxr
|
||||||
|
* @since 2023/7/6
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler {
|
||||||
|
|
||||||
|
private final HttpMessageConverter<Object> accessTokenHttpResponseConverter = new MappingJackson2HttpMessageConverter();
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
|
||||||
|
log.warn(" authentication failure: ", exception);
|
||||||
|
|
||||||
|
OAuth2Error error = ((OAuth2AuthenticationException) exception).getError();
|
||||||
|
ServletServerHttpResponse httpResponse = new ServletServerHttpResponse(response);
|
||||||
|
httpResponse.setStatusCode(HttpStatus.OK);
|
||||||
|
Result result = Result.failed(error.getDescription());
|
||||||
|
accessTokenHttpResponseConverter.write(result, null, httpResponse);
|
||||||
|
}
|
||||||
|
}
|
@ -1,18 +1,18 @@
|
|||||||
package com.youlai.auth.handler;
|
package com.youlai.auth.handler;
|
||||||
|
|
||||||
import cn.hutool.json.JSONUtil;
|
|
||||||
import com.youlai.common.result.Result;
|
import com.youlai.common.result.Result;
|
||||||
import jakarta.servlet.ServletException;
|
import jakarta.servlet.ServletException;
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
import org.springframework.core.convert.converter.Converter;
|
import org.springframework.core.convert.converter.Converter;
|
||||||
import org.springframework.http.converter.HttpMessageConverter;
|
import org.springframework.http.converter.HttpMessageConverter;
|
||||||
|
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
|
||||||
|
import org.springframework.http.server.ServletServerHttpResponse;
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
||||||
import org.springframework.security.oauth2.core.OAuth2RefreshToken;
|
import org.springframework.security.oauth2.core.OAuth2RefreshToken;
|
||||||
import org.springframework.security.oauth2.core.endpoint.DefaultOAuth2AccessTokenResponseMapConverter;
|
import org.springframework.security.oauth2.core.endpoint.DefaultOAuth2AccessTokenResponseMapConverter;
|
||||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
|
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
|
||||||
import org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter;
|
|
||||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AccessTokenAuthenticationToken;
|
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AccessTokenAuthenticationToken;
|
||||||
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
|
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
|
||||||
import org.springframework.util.CollectionUtils;
|
import org.springframework.util.CollectionUtils;
|
||||||
@ -22,23 +22,24 @@ import java.time.temporal.ChronoUnit;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* 认证成功处理器
|
||||||
|
*
|
||||||
* @author haoxr
|
* @author haoxr
|
||||||
* @since 2023/7/3
|
* @since 2023/7/3
|
||||||
*/
|
*/
|
||||||
public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
|
public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
|
||||||
|
|
||||||
private final HttpMessageConverter<OAuth2AccessTokenResponse> accessTokenHttpResponseConverter =
|
private final HttpMessageConverter<Object> accessTokenHttpResponseConverter = new MappingJackson2HttpMessageConverter();
|
||||||
new OAuth2AccessTokenResponseHttpMessageConverter();
|
|
||||||
private Converter<OAuth2AccessTokenResponse, Map<String, Object>> accessTokenResponseParametersConverter = new DefaultOAuth2AccessTokenResponseMapConverter();
|
private Converter<OAuth2AccessTokenResponse, Map<String, Object>> accessTokenResponseParametersConverter = new DefaultOAuth2AccessTokenResponseMapConverter();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
* @param request the request which caused the successful authentication
|
||||||
* @see org.springframework.security.oauth2.server.authorization.web.OAuth2TokenEndpointFilter#sendAccessTokenResponse
|
* @param response the response
|
||||||
* @param request the request which caused the successful authentication
|
|
||||||
* @param response the response
|
|
||||||
* @param authentication the <tt>Authentication</tt> object which was created during
|
* @param authentication the <tt>Authentication</tt> object which was created during
|
||||||
* the authentication process.
|
* the authentication process.
|
||||||
* @throws IOException
|
* @throws IOException
|
||||||
* @throws ServletException
|
* @throws ServletException
|
||||||
|
* @see org.springframework.security.oauth2.server.authorization.web.OAuth2TokenEndpointFilter#sendAccessTokenResponse
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -66,10 +67,8 @@ public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHand
|
|||||||
|
|
||||||
Map<String, Object> tokenResponseParameters = this.accessTokenResponseParametersConverter
|
Map<String, Object> tokenResponseParameters = this.accessTokenResponseParametersConverter
|
||||||
.convert(accessTokenResponse);
|
.convert(accessTokenResponse);
|
||||||
|
ServletServerHttpResponse httpResponse = new ServletServerHttpResponse(response);
|
||||||
response.setCharacterEncoding("UTF-8");
|
// 自定义认证成功响应数据结构
|
||||||
response.getWriter().write(JSONUtil.toJsonStr(Result.success(tokenResponseParameters)));
|
this.accessTokenHttpResponseConverter.write(Result.success(tokenResponseParameters), null, httpResponse);
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,11 +4,14 @@ import cn.hutool.core.collection.CollectionUtil;
|
|||||||
import com.youlai.common.enums.StatusEnum;
|
import com.youlai.common.enums.StatusEnum;
|
||||||
import com.youlai.system.dto.UserAuthInfo;
|
import com.youlai.system.dto.UserAuthInfo;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
import org.springframework.security.core.CredentialsContainer;
|
||||||
import org.springframework.security.core.GrantedAuthority;
|
import org.springframework.security.core.GrantedAuthority;
|
||||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||||
import org.springframework.security.core.userdetails.UserDetails;
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@ -22,7 +25,7 @@ import java.util.stream.Collectors;
|
|||||||
* @since 3.0.0
|
* @since 3.0.0
|
||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
public class SysUserDetails implements UserDetails {
|
public class SysUserDetails implements UserDetails, CredentialsContainer {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 扩展字段:用户ID
|
* 扩展字段:用户ID
|
||||||
@ -45,7 +48,13 @@ public class SysUserDetails implements UserDetails {
|
|||||||
private String username;
|
private String username;
|
||||||
private String password;
|
private String password;
|
||||||
private Boolean enabled;
|
private Boolean enabled;
|
||||||
private Collection<SimpleGrantedAuthority> authorities;
|
private Collection<GrantedAuthority> authorities;
|
||||||
|
|
||||||
|
private boolean accountNonExpired;
|
||||||
|
|
||||||
|
private boolean accountNonLocked;
|
||||||
|
|
||||||
|
private boolean credentialsNonExpired;
|
||||||
|
|
||||||
private Set<String> perms;
|
private Set<String> perms;
|
||||||
|
|
||||||
@ -67,6 +76,28 @@ public class SysUserDetails implements UserDetails {
|
|||||||
this.setPerms(user.getPerms());
|
this.setPerms(user.getPerms());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public SysUserDetails(
|
||||||
|
Long userId,
|
||||||
|
String username,
|
||||||
|
String password,
|
||||||
|
boolean enabled,
|
||||||
|
boolean accountNonExpired,
|
||||||
|
boolean credentialsNonExpired,
|
||||||
|
boolean accountNonLocked,
|
||||||
|
Set<? extends GrantedAuthority> authorities
|
||||||
|
) {
|
||||||
|
Assert.isTrue(username != null && !"".equals(username) && password != null,
|
||||||
|
"Cannot pass null or empty values to constructor");
|
||||||
|
this.userId = userId;
|
||||||
|
this.username = username;
|
||||||
|
this.password = password;
|
||||||
|
this.enabled = enabled;
|
||||||
|
this.accountNonExpired = accountNonExpired;
|
||||||
|
this.credentialsNonExpired = credentialsNonExpired;
|
||||||
|
this.accountNonLocked = accountNonLocked;
|
||||||
|
this.authorities = Collections.unmodifiableSet(authorities);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Collection<? extends GrantedAuthority> getAuthorities() {
|
public Collection<? extends GrantedAuthority> getAuthorities() {
|
||||||
return this.authorities;
|
return this.authorities;
|
||||||
@ -101,4 +132,10 @@ public class SysUserDetails implements UserDetails {
|
|||||||
public boolean isEnabled() {
|
public boolean isEnabled() {
|
||||||
return this.enabled;
|
return this.enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void eraseCredentials() {
|
||||||
|
this.password = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,16 +1,13 @@
|
|||||||
package com.youlai.auth.userdetails.user;
|
package com.youlai.auth.userdetails.user;
|
||||||
|
|
||||||
import cn.hutool.core.lang.Assert;
|
import cn.hutool.core.util.StrUtil;
|
||||||
import com.youlai.common.result.Result;
|
import com.youlai.common.result.Result;
|
||||||
import com.youlai.system.api.UserFeignClient;
|
import com.youlai.system.api.UserFeignClient;
|
||||||
import com.youlai.system.dto.UserAuthInfo;
|
import com.youlai.system.dto.UserAuthInfo;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.context.annotation.Primary;
|
|
||||||
import org.springframework.security.authentication.AccountExpiredException;
|
|
||||||
import org.springframework.security.authentication.DisabledException;
|
|
||||||
import org.springframework.security.authentication.LockedException;
|
|
||||||
import org.springframework.security.core.userdetails.UserDetails;
|
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.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -30,26 +27,16 @@ public class SysUserDetailsService implements UserDetailsService {
|
|||||||
* <p>
|
* <p>
|
||||||
* 用户名、密码用于后续认证,认证成功之后将权限授予用户
|
* 用户名、密码用于后续认证,认证成功之后将权限授予用户
|
||||||
*
|
*
|
||||||
* @param username 前端登录表单的用户名
|
* @param username 用户名
|
||||||
* @return {@link SysUserDetails}
|
* @return {@link SysUserDetails}
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public UserDetails loadUserByUsername(String username) {
|
public UserDetails loadUserByUsername(String username) {
|
||||||
Result<UserAuthInfo> result = userFeignClient.getUserAuthInfo(username);
|
Result<UserAuthInfo> result = userFeignClient.getUserAuthInfo(username);
|
||||||
|
if (!Result.isSuccess(result) || result.getData() == null) {
|
||||||
UserAuthInfo userAuthInfo = null;
|
throw new UsernameNotFoundException(StrUtil.format("用户{}不存在",username));
|
||||||
Assert.isTrue(Result.isSuccess(result) && (userAuthInfo = result.getData()) != null,
|
|
||||||
"用户不存在");
|
|
||||||
|
|
||||||
SysUserDetails userDetails = new SysUserDetails(userAuthInfo);
|
|
||||||
if (!userDetails.isEnabled()) {
|
|
||||||
throw new DisabledException("该账户已被禁用!");
|
|
||||||
} else if (!userDetails.isAccountNonLocked()) {
|
|
||||||
throw new LockedException("该账号已被锁定!");
|
|
||||||
} else if (!userDetails.isAccountNonExpired()) {
|
|
||||||
throw new AccountExpiredException("该账号已过期!");
|
|
||||||
}
|
}
|
||||||
return userDetails;
|
return new SysUserDetails(result.getData());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,19 +1,3 @@
|
|||||||
/*
|
|
||||||
* Copyright 2015-2018 the original author or authors.
|
|
||||||
*
|
|
||||||
* 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
|
|
||||||
*
|
|
||||||
* https://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.youlai.auth.userdetails.user.jackson;
|
package com.youlai.auth.userdetails.user.jackson;
|
||||||
|
|
||||||
import com.fasterxml.jackson.core.JsonParser;
|
import com.fasterxml.jackson.core.JsonParser;
|
||||||
@ -24,6 +8,7 @@ import com.fasterxml.jackson.databind.JsonDeserializer;
|
|||||||
import com.fasterxml.jackson.databind.JsonNode;
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import com.fasterxml.jackson.databind.node.MissingNode;
|
import com.fasterxml.jackson.databind.node.MissingNode;
|
||||||
|
import com.youlai.auth.userdetails.user.SysUserDetails;
|
||||||
import org.springframework.security.core.GrantedAuthority;
|
import org.springframework.security.core.GrantedAuthority;
|
||||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||||
import org.springframework.security.core.userdetails.User;
|
import org.springframework.security.core.userdetails.User;
|
||||||
@ -33,51 +18,53 @@ import java.util.Set;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Custom Deserializer for {@link User} class. This is already registered with
|
* Custom Deserializer for {@link User} class. This is already registered with
|
||||||
* {@link UserMixin}. You can also use it directly with your mixin class.
|
* {@link SysUserMixin}. You can also use it directly with your mixin class.
|
||||||
*
|
*
|
||||||
* @author Jitendra Singh
|
* @author Jitendra Singh
|
||||||
|
* @see SysUserMixin
|
||||||
* @since 4.2
|
* @since 4.2
|
||||||
* @see UserMixin
|
|
||||||
*/
|
*/
|
||||||
class SysUserDeserializer extends JsonDeserializer<User> {
|
class SysUserDeserializer extends JsonDeserializer<SysUserDetails> {
|
||||||
|
|
||||||
private static final TypeReference<Set<SimpleGrantedAuthority>> SIMPLE_GRANTED_AUTHORITY_SET = new TypeReference<Set<SimpleGrantedAuthority>>() {
|
private static final TypeReference<Set<SimpleGrantedAuthority>> SIMPLE_GRANTED_AUTHORITY_SET = new TypeReference<Set<SimpleGrantedAuthority>>() {
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method will create {@link User} object. It will ensure successful object
|
* This method will create {@link User} object. It will ensure successful object
|
||||||
* creation even if password key is null in serialized json, because credentials may
|
* creation even if password key is null in serialized json, because credentials may
|
||||||
* be removed from the {@link User} by invoking {@link User#eraseCredentials()}. In
|
* be removed from the {@link User} by invoking {@link User#eraseCredentials()}. In
|
||||||
* that case there won't be any password key in serialized json.
|
* that case there won't be any password key in serialized json.
|
||||||
* @param jp the JsonParser
|
*
|
||||||
* @param ctxt the DeserializationContext
|
* @param jp the JsonParser
|
||||||
* @return the user
|
* @param ctxt the DeserializationContext
|
||||||
* @throws IOException if a exception during IO occurs
|
* @return the user
|
||||||
* @throws JsonProcessingException if an error during JSON processing occurs
|
* @throws IOException if a exception during IO occurs
|
||||||
*/
|
* @throws JsonProcessingException if an error during JSON processing occurs
|
||||||
@Override
|
*/
|
||||||
public User deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
|
@Override
|
||||||
ObjectMapper mapper = (ObjectMapper) jp.getCodec();
|
public SysUserDetails deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
|
||||||
JsonNode jsonNode = mapper.readTree(jp);
|
ObjectMapper mapper = (ObjectMapper) jp.getCodec();
|
||||||
Set<? extends GrantedAuthority> authorities = mapper.convertValue(jsonNode.get("authorities"),
|
JsonNode jsonNode = mapper.readTree(jp);
|
||||||
SIMPLE_GRANTED_AUTHORITY_SET);
|
Set<? extends GrantedAuthority> authorities = mapper.convertValue(jsonNode.get("authorities"),
|
||||||
JsonNode passwordNode = readJsonNode(jsonNode, "password");
|
SIMPLE_GRANTED_AUTHORITY_SET);
|
||||||
String username = readJsonNode(jsonNode, "username").asText();
|
JsonNode passwordNode = readJsonNode(jsonNode, "password");
|
||||||
String password = passwordNode.asText("");
|
Long userId = readJsonNode(jsonNode, "userId").asLong();
|
||||||
boolean enabled = readJsonNode(jsonNode, "enabled").asBoolean();
|
String username = readJsonNode(jsonNode, "username").asText();
|
||||||
boolean accountNonExpired = readJsonNode(jsonNode, "accountNonExpired").asBoolean();
|
String password = passwordNode.asText("");
|
||||||
boolean credentialsNonExpired = readJsonNode(jsonNode, "credentialsNonExpired").asBoolean();
|
boolean enabled = readJsonNode(jsonNode, "enabled").asBoolean();
|
||||||
boolean accountNonLocked = readJsonNode(jsonNode, "accountNonLocked").asBoolean();
|
boolean accountNonExpired = readJsonNode(jsonNode, "accountNonExpired").asBoolean();
|
||||||
User result = new User(username, password, enabled, accountNonExpired, credentialsNonExpired, accountNonLocked,
|
boolean credentialsNonExpired = readJsonNode(jsonNode, "credentialsNonExpired").asBoolean();
|
||||||
authorities);
|
boolean accountNonLocked = readJsonNode(jsonNode, "accountNonLocked").asBoolean();
|
||||||
if (passwordNode.asText(null) == null) {
|
SysUserDetails result = new SysUserDetails(userId, username, password, enabled, accountNonExpired, credentialsNonExpired, accountNonLocked,
|
||||||
result.eraseCredentials();
|
authorities);
|
||||||
}
|
if (passwordNode.asText(null) == null) {
|
||||||
return result;
|
result.eraseCredentials();
|
||||||
}
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
private JsonNode readJsonNode(JsonNode jsonNode, String field) {
|
private JsonNode readJsonNode(JsonNode jsonNode, String field) {
|
||||||
return jsonNode.has(field) ? jsonNode.get(field) : MissingNode.getInstance();
|
return jsonNode.has(field) ? jsonNode.get(field) : MissingNode.getInstance();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -6,14 +6,17 @@ import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
|||||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see org.springframework.security.jackson2.UserMixin
|
* SysUserDetails 反序列化注册
|
||||||
|
*
|
||||||
|
* 刷新模式根据 refresh_token 从 oauth2_authorization 表中获取字段 attributes 内容反序列化成
|
||||||
*
|
*
|
||||||
* @author haoxr
|
* @author haoxr
|
||||||
|
* @see org.springframework.security.jackson2.UserMixin
|
||||||
* @since 2023/7/4
|
* @since 2023/7/4
|
||||||
*/
|
*/
|
||||||
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY)
|
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY)
|
||||||
@JsonDeserialize(using = SysUserDeserializer.class)
|
@JsonDeserialize(using = SysUserDeserializer.class)
|
||||||
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE, isGetterVisibility = JsonAutoDetect.Visibility.NONE)
|
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE, isGetterVisibility = JsonAutoDetect.Visibility.NONE)
|
||||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
public class SysUseMixin {
|
public class SysUserMixin {
|
||||||
}
|
}
|
@ -1,68 +0,0 @@
|
|||||||
package com.youlai.auth.util;
|
|
||||||
|
|
||||||
import cn.hutool.core.util.StrUtil;
|
|
||||||
import com.nimbusds.jose.JWSObject;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import org.springframework.web.context.request.RequestContextHolder;
|
|
||||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
|
||||||
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.text.ParseException;
|
|
||||||
import java.util.Base64;
|
|
||||||
|
|
||||||
@Slf4j
|
|
||||||
public class RequestUtils {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取登录认证的客户端ID
|
|
||||||
* <p>
|
|
||||||
* 兼容两种方式获取OAuth2客户端信息(client_id、client_secret)
|
|
||||||
* 方式一:client_id、client_secret放在请求路径中
|
|
||||||
* 方式二:放在请求头(Request Headers)中的Authorization字段,且经过加密,例如 Basic Y2xpZW50OnNlY3JldA== 明文等于 client:secret
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public static String getClientId() {
|
|
||||||
|
|
||||||
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
|
|
||||||
|
|
||||||
// 从请求路径中获取
|
|
||||||
String clientId = request.getParameter("client_id");
|
|
||||||
if (StrUtil.isNotBlank(clientId)) {
|
|
||||||
return clientId;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 从请求头获取
|
|
||||||
String basic = request.getHeader("Authorization");
|
|
||||||
if (StrUtil.isNotBlank(basic) && basic.startsWith("Basic ")) {
|
|
||||||
basic = basic.replace("Basic ", "");
|
|
||||||
String basicPlainText = new String(Base64.getDecoder().decode(basic.getBytes(StandardCharsets.UTF_8)), StandardCharsets.UTF_8);
|
|
||||||
clientId = basicPlainText.split(":")[0]; //client:secret
|
|
||||||
}
|
|
||||||
return clientId;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取JWT Payload
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public static String getJwtPayload() {
|
|
||||||
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
|
|
||||||
String payload = null;
|
|
||||||
String authorization = request.getHeader("Authorization");
|
|
||||||
if (StrUtil.isNotBlank(authorization) && StrUtil.startWithIgnoreCase(authorization, "Bearer ")) {
|
|
||||||
authorization = StrUtil.replaceIgnoreCase(authorization, "Bearer ", "");
|
|
||||||
try {
|
|
||||||
payload = JWSObject.parse(authorization).getPayload().toString();
|
|
||||||
} catch (ParseException e) {
|
|
||||||
log.error(e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return payload;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user