diff --git a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/component/PermitAllUrlProperties.java b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/component/PermitAllUrlProperties.java index 81c8398cc..c71fd7e3a 100755 --- a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/component/PermitAllUrlProperties.java +++ b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/component/PermitAllUrlProperties.java @@ -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 ignoreUrls = new ArrayList<>(); + @Getter + @Setter + private List ignoreUrls = new ArrayList<>(); - @Override - public void afterPropertiesSet() { - ignoreUrls.addAll(Arrays.asList(DEFAULT_IGNORE_URLS)); - RequestMappingHandlerMapping mapping = SpringContextHolder.getBean("requestMappingHandlerMapping"); - Map map = mapping.getHandlerMethods(); + /** + * 跳过对外 URL + *

+ * 比如某个接口是对外开放的,即使携带 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 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, "*")))); + }); + } } diff --git a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/component/PigxBearerTokenExtractor.java b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/component/PigxBearerTokenExtractor.java index 0b20244c2..353a7343c 100644 --- a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/component/PigxBearerTokenExtractor.java +++ b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/component/PigxBearerTokenExtractor.java @@ -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 (?[a-zA-Z0-9-:._~+/]+=*)$", - Pattern.CASE_INSENSITIVE); + private static final Pattern authorizationPattern = Pattern.compile("^Bearer (?[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); + } } diff --git a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/component/PigxResourceServerAutoConfiguration.java b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/component/PigxResourceServerAutoConfiguration.java index bdf9b1f4e..51950ee1d 100644 --- a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/component/PigxResourceServerAutoConfiguration.java +++ b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/component/PigxResourceServerAutoConfiguration.java @@ -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); + } }