refactor: 授权服务器代码优化和注释完善

This commit is contained in:
郝先瑞 2023-07-22 00:44:29 +08:00
parent 3fd84f84d6
commit beb0f74d0f
9 changed files with 46 additions and 55 deletions

View File

@ -1,6 +1,7 @@
package com.youlai.auth.authentication.captcha;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import com.youlai.auth.util.OAuth2AuthenticationProviderUtils;
import com.youlai.common.constant.SecurityConstants;
@ -120,6 +121,9 @@ public class CaptchaAuthenticationProvider implements AuthenticationProvider {
generatedAccessToken.getExpiresAt(), tokenContext.getAuthorizedScopes());
// 权限数据比较多通过反射移除不持久化至数据库
ReflectUtil.setFieldValue(usernamePasswordAuthentication.getPrincipal(), "perms", null);
OAuth2Authorization.Builder authorizationBuilder = OAuth2Authorization.withRegisteredClient(registeredClient)
.principalName(usernamePasswordAuthentication.getName())
.authorizationGrantType(CaptchaAuthenticationToken.CAPTCHA)
@ -150,6 +154,7 @@ public class CaptchaAuthenticationProvider implements AuthenticationProvider {
}
OAuth2Authorization authorization = authorizationBuilder.build();
// 持久化令牌发放记录到数据库
this.authorizationService.save(authorization);
additionalParameters = Collections.EMPTY_MAP;
return new OAuth2AccessTokenAuthenticationToken(registeredClient, clientPrincipal, accessToken, refreshToken, additionalParameters);

View File

@ -128,14 +128,14 @@ public class PasswordAuthenticationProvider implements AuthenticationProvider {
throw new OAuth2AuthenticationException(error);
}
OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
generatedAccessToken.getTokenValue(), generatedAccessToken.getIssuedAt(),
generatedAccessToken.getExpiresAt(), tokenContext.getAuthorizedScopes());
// 权限数据(perms)比较多通过反射移除不随令牌一起持久化至数据库
ReflectUtil.setFieldValue(usernamePasswordAuthentication.getPrincipal(), "perms", null);
// 持久化令牌发放记录到数据库
OAuth2Authorization.Builder authorizationBuilder = OAuth2Authorization.withRegisteredClient(registeredClient)
.principalName(usernamePasswordAuthentication.getName())
.authorizationGrantType(AuthorizationGrantType.PASSWORD)
@ -166,10 +166,12 @@ public class PasswordAuthenticationProvider implements AuthenticationProvider {
authorizationBuilder.refreshToken(refreshToken);
}
OAuth2Authorization authorization = authorizationBuilder.build();
// 持久化令牌发放记录到数据库
this.authorizationService.save(authorization);
additionalParameters = Collections.emptyMap();
return new OAuth2AccessTokenAuthenticationToken(registeredClient, clientPrincipal, accessToken, refreshToken, additionalParameters);
}

View File

@ -87,7 +87,7 @@ public class AuthorizationServerConfig {
private final OAuth2TokenCustomizer<JwtEncodingContext> jwtCustomizer;
/**
* 授权配置
* 授权服务器端点配置
*/
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
@ -106,6 +106,7 @@ public class AuthorizationServerConfig {
tokenEndpoint
.accessTokenRequestConverters(
authenticationConverters ->// <1>
// 自定义授权模式转换器(Converter)
authenticationConverters.addAll(
List.of(
new PasswordAuthenticationConverter(),
@ -116,6 +117,7 @@ public class AuthorizationServerConfig {
)
)
.authenticationProviders(authenticationProviders ->// <2>
// 自定义授权模式提供者(Provider)
authenticationProviders.addAll(
List.of(
new PasswordAuthenticationProvider(authenticationManager, authorizationService, tokenGenerator),
@ -126,7 +128,7 @@ public class AuthorizationServerConfig {
)
)
.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>
public JWKSource<SecurityContext> jwkSource() {
KeyPair keyPair = generateRsaKey();
@ -261,8 +254,11 @@ public class AuthorizationServerConfig {
String clientSecret = "123456";
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);
RegisteredClient registeredMallAdminClient = registeredClientRepository.findByClientId(clientId);

View File

@ -1,9 +1,5 @@
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.Configuration;
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.web.SecurityFilterChain;
import java.util.List;
@EnableWebSecurity
@Configuration(proxyBeanMethods = false)
public class DefaultSecurityConfig {
/**
* 读取 配置中的白名单
*/
@Setter
private List<String> ignoreUrls;
/**
* Spring Security 安全过滤器链配置
*
@ -36,12 +25,7 @@ public class DefaultSecurityConfig {
SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(requestMatcherRegistry ->
{
if (CollectionUtil.isNotEmpty(ignoreUrls)) {
requestMatcherRegistry.requestMatchers(Convert.toStrArray(ignoreUrls)).permitAll();
}
requestMatcherRegistry.anyRequest().authenticated();
}
requestMatcherRegistry.anyRequest().authenticated()
)
.csrf(AbstractHttpConfigurer::disable)
.formLogin(Customizer.withDefaults());

View File

@ -5,7 +5,6 @@ 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;
@ -13,26 +12,26 @@ 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
* @since 3.0.0
*/
@Slf4j
public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler {
/**
* MappingJackson2HttpMessageConverter Spring 框架提供的一个 HTTP 消息转换器用于将 HTTP 请求和响应的 JSON 数据与 Java 对象之间进行转换
*/
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);
Result result = Result.failed(error.getErrorCode());

View File

@ -25,21 +25,26 @@ import java.util.Map;
* 认证成功处理器
*
* @author haoxr
* @since 2023/7/3
* @since 3.0.0
*/
public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
/**
* MappingJackson2HttpMessageConverter Spring 框架提供的一个 HTTP 消息转换器用于将 HTTP 请求和响应的 JSON 数据与 Java 对象之间进行转换
*/
private final HttpMessageConverter<Object> accessTokenHttpResponseConverter = new MappingJackson2HttpMessageConverter();
private Converter<OAuth2AccessTokenResponse, Map<String, Object>> accessTokenResponseParametersConverter = new DefaultOAuth2AccessTokenResponseMapConverter();
/**
* 自定义认证成功响应数据结构
*
* @param request the request which caused the successful authentication
* @param response the response
* @param authentication the <tt>Authentication</tt> object which was created during
* the authentication process.
* @throws IOException
* @throws ServletException
* @see org.springframework.security.oauth2.server.authorization.web.OAuth2TokenEndpointFilter#sendAccessTokenResponse
*/
@Override
@ -68,7 +73,7 @@ public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHand
Map<String, Object> tokenResponseParameters = this.accessTokenResponseParametersConverter
.convert(accessTokenResponse);
ServletServerHttpResponse httpResponse = new ServletServerHttpResponse(response);
// 自定义认证成功响应数据结构
this.accessTokenHttpResponseConverter.write(Result.success(tokenResponseParameters), null, httpResponse);
}
}

View File

@ -39,10 +39,9 @@ class SysUserDeserializer extends JsonDeserializer<SysUserDetails> {
* @param ctxt the DeserializationContext
* @return the user
* @throws IOException if a exception during IO occurs
* @throws JsonProcessingException if an error during JSON processing occurs
*/
@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();
JsonNode jsonNode = mapper.readTree(jp);
Set<? extends GrantedAuthority> authorities = mapper.convertValue(jsonNode.get("authorities"),

View File

@ -29,18 +29,19 @@ public class PasswordAuthenticationTests {
* 测试密码模式登录
*/
@Test
void testPasswordAuthentication() throws Exception {
void testPasswordLogin() throws Exception {
HttpHeaders headers = new HttpHeaders();
// 客户端ID和密钥
headers.setBasicAuth("mall-admin", "123456");
this.mvc.perform(post("/oauth2/token")
.param(OAuth2ParameterNames.GRANT_TYPE, "password")
.param(OAuth2ParameterNames.USERNAME, "admin")
.param(OAuth2ParameterNames.PASSWORD, "123456")
.param(OAuth2ParameterNames.GRANT_TYPE, "password") // 密码模式
.param(OAuth2ParameterNames.USERNAME, "admin") // 用户名
.param(OAuth2ParameterNames.PASSWORD, "123456") // 密码
.headers(headers))
.andDo(print())
.andExpect(status().isOk())
.andExpect(jsonPath("$.access_token").isNotEmpty());
.andExpect(jsonPath("$.data.access_token").isNotEmpty());
}

View File

@ -108,7 +108,7 @@ public class SysMenuServiceImpl extends ServiceImpl<SysMenuMapper, SysMenu> impl
* 路由列表
*/
@Override
@Cacheable(cacheNames = "system", key = "'routes'")
//@Cacheable(cacheNames = "system", key = "'routes'")
public List<RouteVO> listRoutes() {
List<RouteBO> menuList = this.baseMapper.listRoutes();
return recurRoutes(SystemConstants.ROOT_NODE_ID, menuList);