refactor(common): security 增加配置项,如果设置 skipPublicUrl true 如果接口对外设置,即使携带token 也不鉴权
This commit is contained in:
1 parent
d6fd5ad960
commit
3c659c86bd
3 files changed
+137
-139
No files matched your search
+35
-26
@@ -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, "*"))));
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
+65
-68
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
+37
-45
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in new issue
Block a user