refactor(common): security 增加配置项,如果设置 skipPublicUrl true 如果接口对外设置,即使携带token 也不鉴权

This commit is contained in:
冷冷 committed 2024-08-16 15:28:22 +08:00
1 parent d6fd5ad960
commit 3c659c86bd
3 files changed
+137 -139

No files matched your search

@@ -42,37 +42,46 @@ import java.util.regex.Pattern;
@ConfigurationProperties(prefix = "security.oauth2.client")
public class PermitAllUrlProperties implements InitializingBean {
private static final Pattern PATTERN = Pattern.compile("\\{(.*?)\\}");
private static final Pattern PATTERN = Pattern.compile("\\{(.*?)\\}");
private static final String[] DEFAULT_IGNORE_URLS = new String[] { "/actuator/**", "/error", "/v3/api-docs" };
private static final String[] DEFAULT_IGNORE_URLS = new String[]{"/actuator/**", "/error", "/v3/api-docs"};
@Getter
@Setter
private List<String> ignoreUrls = new ArrayList<>();
@Getter
@Setter
private List<String> ignoreUrls = new ArrayList<>();
@Override
public void afterPropertiesSet() {
ignoreUrls.addAll(Arrays.asList(DEFAULT_IGNORE_URLS));
RequestMappingHandlerMapping mapping = SpringContextHolder.getBean("requestMappingHandlerMapping");
Map<RequestMappingInfo, HandlerMethod> map = mapping.getHandlerMethods();
/**
* 跳过对外 URL
* <p>
* 比如某个接口是对外开放的,即使携带 token,也不鉴权
*/
@Getter
@Setter
private boolean skipPublicUrl = false;
map.keySet().forEach(info -> {
HandlerMethod handlerMethod = map.get(info);
@Override
public void afterPropertiesSet() {
ignoreUrls.addAll(Arrays.asList(DEFAULT_IGNORE_URLS));
RequestMappingHandlerMapping mapping = SpringContextHolder.getBean("requestMappingHandlerMapping");
Map<RequestMappingInfo, HandlerMethod> map = mapping.getHandlerMethods();
// 获取方法上边的注解 替代path variable 为 *
Inner method = AnnotationUtils.findAnnotation(handlerMethod.getMethod(), Inner.class);
Optional.ofNullable(method)
.ifPresent(inner -> Objects.requireNonNull(info.getPathPatternsCondition())
.getPatternValues()
.forEach(url -> ignoreUrls.add(ReUtil.replaceAll(url, PATTERN, "*"))));
map.keySet().forEach(info -> {
HandlerMethod handlerMethod = map.get(info);
// 获取类上边的注解, 替代path variable 为 *
Inner controller = AnnotationUtils.findAnnotation(handlerMethod.getBeanType(), Inner.class);
Optional.ofNullable(controller)
.ifPresent(inner -> Objects.requireNonNull(info.getPathPatternsCondition())
.getPatternValues()
.forEach(url -> ignoreUrls.add(ReUtil.replaceAll(url, PATTERN, "*"))));
});
}
// 获取方法上边的注解 替代path variable 为 *
Inner method = AnnotationUtils.findAnnotation(handlerMethod.getMethod(), Inner.class);
Optional.ofNullable(method)
.ifPresent(inner -> Objects.requireNonNull(info.getPathPatternsCondition())
.getPatternValues()
.forEach(url -> ignoreUrls.add(ReUtil.replaceAll(url, PATTERN, "*"))));
// 获取类上边的注解, 替代path variable 为 *
Inner controller = AnnotationUtils.findAnnotation(handlerMethod.getBeanType(), Inner.class);
Optional.ofNullable(controller)
.ifPresent(inner -> Objects.requireNonNull(info.getPathPatternsCondition())
.getPatternValues()
.forEach(url -> ignoreUrls.add(ReUtil.replaceAll(url, PATTERN, "*"))));
});
}
}
@@ -18,7 +18,6 @@ package com.pig4cloud.pigx.common.security.component;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.server.resource.BearerTokenError;
import org.springframework.security.oauth2.server.resource.BearerTokenErrors;
@@ -36,86 +35,84 @@ import java.util.regex.Pattern;
*/
public class PigxBearerTokenExtractor implements BearerTokenResolver {
private static final Pattern authorizationPattern = Pattern.compile("^Bearer (?<token>[a-zA-Z0-9-:._~+/]+=*)$",
Pattern.CASE_INSENSITIVE);
private static final Pattern authorizationPattern = Pattern.compile("^Bearer (?<token>[a-zA-Z0-9-:._~+/]+=*)$",
Pattern.CASE_INSENSITIVE);
private boolean allowFormEncodedBodyParameter = false;
private boolean allowUriQueryParameter = true;
private String bearerTokenHeaderName = HttpHeaders.AUTHORIZATION;
private String bearerTokenHeaderName = HttpHeaders.AUTHORIZATION;
private final PathMatcher pathMatcher = new AntPathMatcher();
private final PathMatcher pathMatcher = new AntPathMatcher();
private final PermitAllUrlProperties urlProperties;
private final PermitAllUrlProperties urlProperties;
public PigxBearerTokenExtractor(PermitAllUrlProperties urlProperties) {
this.urlProperties = urlProperties;
}
public PigxBearerTokenExtractor(PermitAllUrlProperties urlProperties) {
this.urlProperties = urlProperties;
}
/**
* 解析令牌
*
* @param request 请求
* @return {@link String }
*/
@Override
public String resolve(HttpServletRequest request) {
String requestUri = request.getRequestURI();
String relativePath = requestUri.substring(request.getContextPath().length());
@Override
public String resolve(HttpServletRequest request) {
String requestUri = request.getRequestURI();
String relativePath = requestUri.substring(request.getContextPath().length());
if (urlProperties.isSkipPublicUrl()) {
boolean match = urlProperties.getIgnoreUrls().stream().anyMatch(url -> pathMatcher.match(url, relativePath));
boolean match = urlProperties.getIgnoreUrls().stream().anyMatch(url -> pathMatcher.match(url, relativePath));
if (match) {
return null;
}
}
if (match) {
return null;
}
final String authorizationHeaderToken = resolveFromAuthorizationHeader(request);
final String parameterToken = isParameterTokenSupportedForRequest(request)
? resolveFromRequestParameters(request) : null;
if (authorizationHeaderToken != null) {
if (parameterToken != null) {
final BearerTokenError error = BearerTokenErrors
.invalidRequest("Found multiple bearer tokens in the request");
throw new OAuth2AuthenticationException(error);
}
return authorizationHeaderToken;
}
if (parameterToken != null && isParameterTokenEnabledForRequest(request)) {
return parameterToken;
}
return null;
}
final String authorizationHeaderToken = resolveFromAuthorizationHeader(request);
private String resolveFromAuthorizationHeader(HttpServletRequest request) {
String authorization = request.getHeader(this.bearerTokenHeaderName);
if (!StringUtils.startsWithIgnoreCase(authorization, "bearer")) {
return null;
}
Matcher matcher = authorizationPattern.matcher(authorization);
if (!matcher.matches()) {
BearerTokenError error = BearerTokenErrors.invalidToken("Bearer token is malformed");
throw new OAuth2AuthenticationException(error);
}
return matcher.group("token");
}
if (authorizationHeaderToken != null) {
return authorizationHeaderToken;
}
private static String resolveFromRequestParameters(HttpServletRequest request) {
String[] values = request.getParameterValues("access_token");
if (values == null || values.length == 0) {
return null;
}
if (values.length == 1) {
return values[0];
}
BearerTokenError error = BearerTokenErrors.invalidRequest("Found multiple bearer tokens in the request");
throw new OAuth2AuthenticationException(error);
}
return resolveFromRequestParameters(request);
}
private boolean isParameterTokenSupportedForRequest(final HttpServletRequest request) {
return (("POST".equals(request.getMethod())
&& MediaType.APPLICATION_FORM_URLENCODED_VALUE.equals(request.getContentType()))
|| "GET".equals(request.getMethod()));
}
/**
* 从授权标头解析
*
* @param request 请求
* @return {@link String }
*/
private String resolveFromAuthorizationHeader(HttpServletRequest request) {
String authorization = request.getHeader(this.bearerTokenHeaderName);
if (!StringUtils.startsWithIgnoreCase(authorization, "bearer")) {
return null;
}
Matcher matcher = authorizationPattern.matcher(authorization);
if (!matcher.matches()) {
BearerTokenError error = BearerTokenErrors.invalidToken("Bearer token is malformed");
throw new OAuth2AuthenticationException(error);
}
return matcher.group("token");
}
private boolean isParameterTokenEnabledForRequest(final HttpServletRequest request) {
return ((this.allowFormEncodedBodyParameter && "POST".equals(request.getMethod())
&& MediaType.APPLICATION_FORM_URLENCODED_VALUE.equals(request.getContentType()))
|| (this.allowUriQueryParameter && "GET".equals(request.getMethod())));
}
/**
* 从请求参数解析
*
* @param request 请求
* @return {@link String }
*/
private static String resolveFromRequestParameters(HttpServletRequest request) {
String[] values = request.getParameterValues("access_token");
if (values == null || values.length == 0) {
return null;
}
if (values.length == 1) {
return values[0];
}
BearerTokenError error = BearerTokenErrors.invalidRequest("Found multiple bearer tokens in the request");
throw new OAuth2AuthenticationException(error);
}
}
@@ -23,8 +23,6 @@ import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
import org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector;
import org.springframework.security.oauth2.server.resource.web.BearerTokenResolver;
import org.springframework.security.oauth2.server.resource.web.DefaultBearerTokenResolver;
/**
* @author lengleng
@@ -34,51 +32,45 @@ import org.springframework.security.oauth2.server.resource.web.DefaultBearerToke
@EnableConfigurationProperties(PermitAllUrlProperties.class)
public class PigxResourceServerAutoConfiguration {
/**
* 鉴权具体的实现逻辑
*
* @return (#pms.xxx)
*/
@Bean("pms")
public PermissionService permissionService() {
return new PermissionService();
}
/**
* 鉴权具体的实现逻辑
* @return (#pms.xxx)
*/
@Bean("pms")
public PermissionService permissionService() {
return new PermissionService();
}
/**
* 请求令牌的抽取逻辑
*
* @return BearerTokenExtractor
*/
@Bean
public BearerTokenResolver pigBearerTokenExtractor() {
DefaultBearerTokenResolver defaultBearerTokenResolver = new DefaultBearerTokenResolver();
defaultBearerTokenResolver.setAllowUriQueryParameter(true);
defaultBearerTokenResolver.setAllowFormEncodedBodyParameter(true);
return defaultBearerTokenResolver;
}
/**
* 请求令牌的抽取逻辑
* @param urlProperties 对外暴露的接口列表
* @return BearerTokenExtractor
*/
@Bean
public PigxBearerTokenExtractor pigBearerTokenExtractor(PermitAllUrlProperties urlProperties) {
return new PigxBearerTokenExtractor(urlProperties);
}
/**
* 资源服务器异常处理
*
* @param objectMapper jackson 输出对象
* @param securityMessageSource 自定义国际化处理器
* @return ResourceAuthExceptionEntryPoint
*/
@Bean
public ResourceAuthExceptionEntryPoint resourceAuthExceptionEntryPoint(ObjectMapper objectMapper,
MessageSource securityMessageSource) {
return new ResourceAuthExceptionEntryPoint(objectMapper, securityMessageSource);
}
/**
* 资源服务器异常处理
* @param objectMapper jackson 输出对象
* @param securityMessageSource 自定义国际化处理器
* @return ResourceAuthExceptionEntryPoint
*/
@Bean
public ResourceAuthExceptionEntryPoint resourceAuthExceptionEntryPoint(ObjectMapper objectMapper,
MessageSource securityMessageSource) {
return new ResourceAuthExceptionEntryPoint(objectMapper, securityMessageSource);
}
/**
* 资源服务器toke内省处理器
*
* @param authorizationService token 存储实现
* @return TokenIntrospector
*/
@Bean
public OpaqueTokenIntrospector opaqueTokenIntrospector(OAuth2AuthorizationService authorizationService) {
return new PigxCustomOpaqueTokenIntrospector(authorizationService);
}
/**
* 资源服务器toke内省处理器
* @param authorizationService token 存储实现
* @return TokenIntrospector
*/
@Bean
public OpaqueTokenIntrospector opaqueTokenIntrospector(OAuth2AuthorizationService authorizationService) {
return new PigxCustomOpaqueTokenIntrospector(authorizationService);
}
}