mirror of
https://gitee.com/youlaitech/youlai-mall.git
synced 2024-12-22 20:54:26 +08:00
refactor: 授权服务器代码优化和注释完善
This commit is contained in:
parent
3fd84f84d6
commit
beb0f74d0f
@ -1,6 +1,7 @@
|
|||||||
package com.youlai.auth.authentication.captcha;
|
package com.youlai.auth.authentication.captcha;
|
||||||
|
|
||||||
import cn.hutool.core.lang.Assert;
|
import cn.hutool.core.lang.Assert;
|
||||||
|
import cn.hutool.core.util.ReflectUtil;
|
||||||
import cn.hutool.core.util.StrUtil;
|
import cn.hutool.core.util.StrUtil;
|
||||||
import com.youlai.auth.util.OAuth2AuthenticationProviderUtils;
|
import com.youlai.auth.util.OAuth2AuthenticationProviderUtils;
|
||||||
import com.youlai.common.constant.SecurityConstants;
|
import com.youlai.common.constant.SecurityConstants;
|
||||||
@ -120,6 +121,9 @@ public class CaptchaAuthenticationProvider implements AuthenticationProvider {
|
|||||||
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(CaptchaAuthenticationToken.CAPTCHA)
|
.authorizationGrantType(CaptchaAuthenticationToken.CAPTCHA)
|
||||||
@ -150,6 +154,7 @@ public class CaptchaAuthenticationProvider implements AuthenticationProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
OAuth2Authorization authorization = authorizationBuilder.build();
|
OAuth2Authorization authorization = authorizationBuilder.build();
|
||||||
|
// 持久化令牌发放记录到数据库
|
||||||
this.authorizationService.save(authorization);
|
this.authorizationService.save(authorization);
|
||||||
additionalParameters = Collections.EMPTY_MAP;
|
additionalParameters = Collections.EMPTY_MAP;
|
||||||
return new OAuth2AccessTokenAuthenticationToken(registeredClient, clientPrincipal, accessToken, refreshToken, additionalParameters);
|
return new OAuth2AccessTokenAuthenticationToken(registeredClient, clientPrincipal, accessToken, refreshToken, additionalParameters);
|
||||||
|
@ -128,14 +128,14 @@ public class PasswordAuthenticationProvider implements AuthenticationProvider {
|
|||||||
throw new OAuth2AuthenticationException(error);
|
throw new OAuth2AuthenticationException(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
|
OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
|
||||||
generatedAccessToken.getTokenValue(), generatedAccessToken.getIssuedAt(),
|
generatedAccessToken.getTokenValue(), generatedAccessToken.getIssuedAt(),
|
||||||
generatedAccessToken.getExpiresAt(), tokenContext.getAuthorizedScopes());
|
generatedAccessToken.getExpiresAt(), tokenContext.getAuthorizedScopes());
|
||||||
|
|
||||||
|
// 权限数据(perms)比较多通过反射移除,不随令牌一起持久化至数据库
|
||||||
ReflectUtil.setFieldValue(usernamePasswordAuthentication.getPrincipal(), "perms", null);
|
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)
|
||||||
@ -166,10 +166,12 @@ public class PasswordAuthenticationProvider implements AuthenticationProvider {
|
|||||||
authorizationBuilder.refreshToken(refreshToken);
|
authorizationBuilder.refreshToken(refreshToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
OAuth2Authorization authorization = authorizationBuilder.build();
|
OAuth2Authorization authorization = authorizationBuilder.build();
|
||||||
|
|
||||||
|
// 持久化令牌发放记录到数据库
|
||||||
this.authorizationService.save(authorization);
|
this.authorizationService.save(authorization);
|
||||||
additionalParameters = Collections.emptyMap();
|
additionalParameters = Collections.emptyMap();
|
||||||
|
|
||||||
return new OAuth2AccessTokenAuthenticationToken(registeredClient, clientPrincipal, accessToken, refreshToken, additionalParameters);
|
return new OAuth2AccessTokenAuthenticationToken(registeredClient, clientPrincipal, accessToken, refreshToken, additionalParameters);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,7 +87,7 @@ public class AuthorizationServerConfig {
|
|||||||
private final OAuth2TokenCustomizer<JwtEncodingContext> jwtCustomizer;
|
private final OAuth2TokenCustomizer<JwtEncodingContext> jwtCustomizer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 授权配置
|
* 授权服务器端点配置
|
||||||
*/
|
*/
|
||||||
@Bean
|
@Bean
|
||||||
@Order(Ordered.HIGHEST_PRECEDENCE)
|
@Order(Ordered.HIGHEST_PRECEDENCE)
|
||||||
@ -106,6 +106,7 @@ public class AuthorizationServerConfig {
|
|||||||
tokenEndpoint
|
tokenEndpoint
|
||||||
.accessTokenRequestConverters(
|
.accessTokenRequestConverters(
|
||||||
authenticationConverters ->// <1>
|
authenticationConverters ->// <1>
|
||||||
|
// 自定义授权模式转换器(Converter)
|
||||||
authenticationConverters.addAll(
|
authenticationConverters.addAll(
|
||||||
List.of(
|
List.of(
|
||||||
new PasswordAuthenticationConverter(),
|
new PasswordAuthenticationConverter(),
|
||||||
@ -116,6 +117,7 @@ public class AuthorizationServerConfig {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
.authenticationProviders(authenticationProviders ->// <2>
|
.authenticationProviders(authenticationProviders ->// <2>
|
||||||
|
// 自定义授权模式提供者(Provider)
|
||||||
authenticationProviders.addAll(
|
authenticationProviders.addAll(
|
||||||
List.of(
|
List.of(
|
||||||
new PasswordAuthenticationProvider(authenticationManager, authorizationService, tokenGenerator),
|
new PasswordAuthenticationProvider(authenticationManager, authorizationService, tokenGenerator),
|
||||||
@ -126,7 +128,7 @@ public class AuthorizationServerConfig {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
.accessTokenResponseHandler(new MyAuthenticationSuccessHandler()) // 自定义成功响应
|
.accessTokenResponseHandler(new MyAuthenticationSuccessHandler()) // 自定义成功响应
|
||||||
.errorResponseHandler(new MyAuthenticationFailureHandler()) // 自定义异常响应
|
.errorResponseHandler(new MyAuthenticationFailureHandler()) // 自定义失败响应
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
@ -140,15 +142,6 @@ public class AuthorizationServerConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* @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();
|
||||||
@ -261,8 +254,11 @@ public class AuthorizationServerConfig {
|
|||||||
String clientSecret = "123456";
|
String clientSecret = "123456";
|
||||||
String clientName = "商城管理客户端";
|
String clientName = "商城管理客户端";
|
||||||
|
|
||||||
// 如果使用明文,在客户端认证的时候会自动升级加密方式(修改密码), 直接使用 bcrypt 加密避免不必要的麻烦
|
|
||||||
// 不开玩笑,官方ISSUE: https://github.com/spring-projects/spring-authorization-server/issues/1099
|
/*
|
||||||
|
如果使用明文,客户端认证时会自动升级加密方式,换句话说直接修改客户端密码,所以直接使用 bcrypt 加密避免不必要的麻烦
|
||||||
|
官方ISSUE: https://github.com/spring-projects/spring-authorization-server/issues/1099
|
||||||
|
*/
|
||||||
String encodeSecret = passwordEncoder().encode(clientSecret);
|
String encodeSecret = passwordEncoder().encode(clientSecret);
|
||||||
|
|
||||||
RegisteredClient registeredMallAdminClient = registeredClientRepository.findByClientId(clientId);
|
RegisteredClient registeredMallAdminClient = registeredClientRepository.findByClientId(clientId);
|
||||||
|
@ -1,9 +1,5 @@
|
|||||||
package com.youlai.auth.config;
|
package com.youlai.auth.config;
|
||||||
|
|
||||||
import cn.hutool.core.collection.CollectionUtil;
|
|
||||||
import cn.hutool.core.convert.Convert;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
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.core.annotation.Order;
|
||||||
@ -14,17 +10,10 @@ import org.springframework.security.config.annotation.web.configuration.WebSecur
|
|||||||
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
|
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
|
||||||
import org.springframework.security.web.SecurityFilterChain;
|
import org.springframework.security.web.SecurityFilterChain;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
@EnableWebSecurity
|
@EnableWebSecurity
|
||||||
@Configuration(proxyBeanMethods = false)
|
@Configuration(proxyBeanMethods = false)
|
||||||
public class DefaultSecurityConfig {
|
public class DefaultSecurityConfig {
|
||||||
|
|
||||||
/**
|
|
||||||
* 读取 配置中的白名单
|
|
||||||
*/
|
|
||||||
@Setter
|
|
||||||
private List<String> ignoreUrls;
|
|
||||||
/**
|
/**
|
||||||
* Spring Security 安全过滤器链配置
|
* Spring Security 安全过滤器链配置
|
||||||
*
|
*
|
||||||
@ -36,12 +25,7 @@ public class DefaultSecurityConfig {
|
|||||||
SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
|
SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
|
||||||
http
|
http
|
||||||
.authorizeHttpRequests(requestMatcherRegistry ->
|
.authorizeHttpRequests(requestMatcherRegistry ->
|
||||||
{
|
requestMatcherRegistry.anyRequest().authenticated()
|
||||||
if (CollectionUtil.isNotEmpty(ignoreUrls)) {
|
|
||||||
requestMatcherRegistry.requestMatchers(Convert.toStrArray(ignoreUrls)).permitAll();
|
|
||||||
}
|
|
||||||
requestMatcherRegistry.anyRequest().authenticated();
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
.csrf(AbstractHttpConfigurer::disable)
|
.csrf(AbstractHttpConfigurer::disable)
|
||||||
.formLogin(Customizer.withDefaults());
|
.formLogin(Customizer.withDefaults());
|
||||||
|
@ -5,7 +5,6 @@ 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 lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.http.HttpStatus;
|
|
||||||
import org.springframework.http.converter.HttpMessageConverter;
|
import org.springframework.http.converter.HttpMessageConverter;
|
||||||
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
|
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
|
||||||
import org.springframework.http.server.ServletServerHttpResponse;
|
import org.springframework.http.server.ServletServerHttpResponse;
|
||||||
@ -13,26 +12,26 @@ import org.springframework.security.core.AuthenticationException;
|
|||||||
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
||||||
import org.springframework.security.oauth2.core.OAuth2Error;
|
import org.springframework.security.oauth2.core.OAuth2Error;
|
||||||
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
|
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 认证异常处理器
|
* 认证失败处理器
|
||||||
*
|
*
|
||||||
* @author haoxr
|
* @author haoxr
|
||||||
* @since 2023/7/6
|
* @since 3.0.0
|
||||||
*/
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler {
|
public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MappingJackson2HttpMessageConverter 是 Spring 框架提供的一个 HTTP 消息转换器,用于将 HTTP 请求和响应的 JSON 数据与 Java 对象之间进行转换
|
||||||
|
*/
|
||||||
private final HttpMessageConverter<Object> accessTokenHttpResponseConverter = new MappingJackson2HttpMessageConverter();
|
private final HttpMessageConverter<Object> accessTokenHttpResponseConverter = new MappingJackson2HttpMessageConverter();
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
|
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
|
||||||
log.warn(" authentication failure: ", exception);
|
|
||||||
|
|
||||||
OAuth2Error error = ((OAuth2AuthenticationException) exception).getError();
|
OAuth2Error error = ((OAuth2AuthenticationException) exception).getError();
|
||||||
ServletServerHttpResponse httpResponse = new ServletServerHttpResponse(response);
|
ServletServerHttpResponse httpResponse = new ServletServerHttpResponse(response);
|
||||||
Result result = Result.failed(error.getErrorCode());
|
Result result = Result.failed(error.getErrorCode());
|
||||||
|
@ -25,21 +25,26 @@ import java.util.Map;
|
|||||||
* 认证成功处理器
|
* 认证成功处理器
|
||||||
*
|
*
|
||||||
* @author haoxr
|
* @author haoxr
|
||||||
* @since 2023/7/3
|
* @since 3.0.0
|
||||||
*/
|
*/
|
||||||
public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
|
public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MappingJackson2HttpMessageConverter 是 Spring 框架提供的一个 HTTP 消息转换器,用于将 HTTP 请求和响应的 JSON 数据与 Java 对象之间进行转换
|
||||||
|
*/
|
||||||
private final HttpMessageConverter<Object> accessTokenHttpResponseConverter = new MappingJackson2HttpMessageConverter();
|
private final HttpMessageConverter<Object> accessTokenHttpResponseConverter = new MappingJackson2HttpMessageConverter();
|
||||||
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
|
* @param request the request which caused the successful authentication
|
||||||
* @param response the response
|
* @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
|
||||||
@ -68,7 +73,7 @@ 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);
|
ServletServerHttpResponse httpResponse = new ServletServerHttpResponse(response);
|
||||||
// 自定义认证成功响应数据结构
|
|
||||||
this.accessTokenHttpResponseConverter.write(Result.success(tokenResponseParameters), null, httpResponse);
|
this.accessTokenHttpResponseConverter.write(Result.success(tokenResponseParameters), null, httpResponse);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -39,10 +39,9 @@ class SysUserDeserializer extends JsonDeserializer<SysUserDetails> {
|
|||||||
* @param ctxt the DeserializationContext
|
* @param ctxt the DeserializationContext
|
||||||
* @return the user
|
* @return the user
|
||||||
* @throws IOException if a exception during IO occurs
|
* @throws IOException if a exception during IO occurs
|
||||||
* @throws JsonProcessingException if an error during JSON processing occurs
|
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public SysUserDetails deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
|
public SysUserDetails deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException {
|
||||||
ObjectMapper mapper = (ObjectMapper) jp.getCodec();
|
ObjectMapper mapper = (ObjectMapper) jp.getCodec();
|
||||||
JsonNode jsonNode = mapper.readTree(jp);
|
JsonNode jsonNode = mapper.readTree(jp);
|
||||||
Set<? extends GrantedAuthority> authorities = mapper.convertValue(jsonNode.get("authorities"),
|
Set<? extends GrantedAuthority> authorities = mapper.convertValue(jsonNode.get("authorities"),
|
||||||
|
@ -29,18 +29,19 @@ public class PasswordAuthenticationTests {
|
|||||||
* 测试密码模式登录
|
* 测试密码模式登录
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
void testPasswordAuthentication() throws Exception {
|
void testPasswordLogin() throws Exception {
|
||||||
HttpHeaders headers = new HttpHeaders();
|
HttpHeaders headers = new HttpHeaders();
|
||||||
|
// 客户端ID和密钥
|
||||||
headers.setBasicAuth("mall-admin", "123456");
|
headers.setBasicAuth("mall-admin", "123456");
|
||||||
|
|
||||||
this.mvc.perform(post("/oauth2/token")
|
this.mvc.perform(post("/oauth2/token")
|
||||||
.param(OAuth2ParameterNames.GRANT_TYPE, "password")
|
.param(OAuth2ParameterNames.GRANT_TYPE, "password") // 密码模式
|
||||||
.param(OAuth2ParameterNames.USERNAME, "admin")
|
.param(OAuth2ParameterNames.USERNAME, "admin") // 用户名
|
||||||
.param(OAuth2ParameterNames.PASSWORD, "123456")
|
.param(OAuth2ParameterNames.PASSWORD, "123456") // 密码
|
||||||
.headers(headers))
|
.headers(headers))
|
||||||
.andDo(print())
|
.andDo(print())
|
||||||
.andExpect(status().isOk())
|
.andExpect(status().isOk())
|
||||||
.andExpect(jsonPath("$.access_token").isNotEmpty());
|
.andExpect(jsonPath("$.data.access_token").isNotEmpty());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -108,7 +108,7 @@ public class SysMenuServiceImpl extends ServiceImpl<SysMenuMapper, SysMenu> impl
|
|||||||
* 路由列表
|
* 路由列表
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
@Cacheable(cacheNames = "system", key = "'routes'")
|
//@Cacheable(cacheNames = "system", key = "'routes'")
|
||||||
public List<RouteVO> listRoutes() {
|
public List<RouteVO> listRoutes() {
|
||||||
List<RouteBO> menuList = this.baseMapper.listRoutes();
|
List<RouteBO> menuList = this.baseMapper.listRoutes();
|
||||||
return recurRoutes(SystemConstants.ROOT_NODE_ID, menuList);
|
return recurRoutes(SystemConstants.ROOT_NODE_ID, menuList);
|
||||||
|
Loading…
Reference in New Issue
Block a user