diff --git a/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/config/AuthorizationServerConfiguration.java b/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/config/AuthorizationServerConfiguration.java index 54a5c99fb..e7f454003 100644 --- a/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/config/AuthorizationServerConfiguration.java +++ b/pigx-auth/src/main/java/com/pig4cloud/pigx/auth/config/AuthorizationServerConfiguration.java @@ -30,6 +30,7 @@ import com.pig4cloud.pigx.auth.support.password.OAuth2ResourceOwnerPasswordAuthe import com.pig4cloud.pigx.auth.support.sms.OAuth2ResourceOwnerSmsAuthenticationConverter; import com.pig4cloud.pigx.auth.support.sms.OAuth2ResourceOwnerSmsAuthenticationProvider; import com.pig4cloud.pigx.common.core.constant.SecurityConstants; +import com.pig4cloud.pigx.common.security.component.PigxBootCorsProperties; import com.pig4cloud.pigx.common.security.handler.FormAuthenticationFailureHandler; import com.pig4cloud.pigx.common.security.handler.SsoLogoutSuccessHandler; import lombok.RequiredArgsConstructor; @@ -56,6 +57,8 @@ import org.springframework.security.web.authentication.AuthenticationFailureHand import org.springframework.security.web.authentication.DelegatingAuthenticationConverter; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import java.util.Arrays; @@ -69,114 +72,131 @@ import java.util.Arrays; @RequiredArgsConstructor public class AuthorizationServerConfiguration { - private final OAuth2AuthorizationService authorizationService; + private final OAuth2AuthorizationService authorizationService; - private final PasswordDecoderFilter passwordDecoderFilter; + private final PasswordDecoderFilter passwordDecoderFilter; - private final ValidateCodeFilter validateCodeFilter; + private final ValidateCodeFilter validateCodeFilter; - @Bean - @Order(Ordered.HIGHEST_PRECEDENCE) - public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http, - PigxAuthenticationSuccessEventHandler successEventHandler, - PigxAuthenticationFailureEventHandler failureEventHandler) throws Exception { - // 配置授权服务器的安全策略,只有/oauth2/**的请求才会走如下的配置 - http.securityMatcher("/oauth2/**"); - OAuth2AuthorizationServerConfigurer authorizationServerConfigurer = new OAuth2AuthorizationServerConfigurer(); + private final PigxBootCorsProperties corsProperties; - // 增加验证码过滤器 - http.addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class); - // 增加密码解密过滤器 - http.addFilterBefore(passwordDecoderFilter, UsernamePasswordAuthenticationFilter.class); + @Bean + @Order(Ordered.HIGHEST_PRECEDENCE) + public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http, + PigxAuthenticationSuccessEventHandler successEventHandler, + PigxAuthenticationFailureEventHandler failureEventHandler) throws Exception { + // 配置授权服务器的安全策略,只有/oauth2/**的请求才会走如下的配置 + http.securityMatcher("/oauth2/**"); + OAuth2AuthorizationServerConfigurer authorizationServerConfigurer = new OAuth2AuthorizationServerConfigurer(); - http.with(authorizationServerConfigurer.tokenEndpoint((tokenEndpoint) -> {// 个性化认证授权端点 - tokenEndpoint.accessTokenRequestConverter(accessTokenRequestConverter()) // 注入自定义的授权认证Converter - .accessTokenResponseHandler(successEventHandler) // 登录成功处理器 - .errorResponseHandler(failureEventHandler);// 登录失败处理器 - }).clientAuthentication(oAuth2ClientAuthenticationConfigurer -> // 个性化客户端认证 - oAuth2ClientAuthenticationConfigurer.errorResponseHandler(failureEventHandler))// 处理客户端认证异常 - .authorizationEndpoint(authorizationEndpoint -> authorizationEndpoint// 授权码端点个性化confirm页面 - .consentPage(SecurityConstants.CUSTOM_CONSENT_PAGE_URI)), Customizer.withDefaults()) - .authorizeHttpRequests(authorizeRequests -> authorizeRequests.anyRequest().authenticated()); + // 增加验证码过滤器 + http.addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class); + // 增加密码解密过滤器 + http.addFilterBefore(passwordDecoderFilter, UsernamePasswordAuthenticationFilter.class); - // 设置 Token 存储的策略 - http.with(authorizationServerConfigurer.authorizationService(authorizationService)// redis存储token的实现 - .authorizationServerSettings( - AuthorizationServerSettings.builder().issuer(SecurityConstants.PIGX_LICENSE).build()), - Customizer.withDefaults()); + http.with(authorizationServerConfigurer.tokenEndpoint((tokenEndpoint) -> {// 个性化认证授权端点 + tokenEndpoint.accessTokenRequestConverter(accessTokenRequestConverter()) // 注入自定义的授权认证Converter + .accessTokenResponseHandler(successEventHandler) // 登录成功处理器 + .errorResponseHandler(failureEventHandler);// 登录失败处理器 + }).clientAuthentication(oAuth2ClientAuthenticationConfigurer -> // 个性化客户端认证 + oAuth2ClientAuthenticationConfigurer.errorResponseHandler(failureEventHandler))// 处理客户端认证异常 + .authorizationEndpoint(authorizationEndpoint -> authorizationEndpoint// 授权码端点个性化confirm页面 + .consentPage(SecurityConstants.CUSTOM_CONSENT_PAGE_URI)), Customizer.withDefaults()) + .authorizeHttpRequests(authorizeRequests -> authorizeRequests.anyRequest().authenticated()); - // 设置授权码模式登录页面 - http.with(new FormIdentityLoginConfigurer(), Customizer.withDefaults()); - DefaultSecurityFilterChain securityFilterChain = http.build(); + // 设置 Token 存储的策略 + http.with(authorizationServerConfigurer.authorizationService(authorizationService)// redis存储token的实现 + .authorizationServerSettings( + AuthorizationServerSettings.builder().issuer(SecurityConstants.PIGX_LICENSE).build()), + Customizer.withDefaults()); - // 注入自定义授权模式实现 - addCustomOAuth2GrantAuthenticationProvider(http); - return securityFilterChain; - } + // 设置授权码模式登录页面 + http.with(new FormIdentityLoginConfigurer(), Customizer.withDefaults()); - /** - * 令牌生成规则实现
- * client:username:uuid - * - * @return OAuth2TokenGenerator - */ - @Bean - public OAuth2TokenGenerator oAuth2TokenGenerator() { - CustomeOAuth2AccessTokenGenerator accessTokenGenerator = new CustomeOAuth2AccessTokenGenerator(); - // 注入Token 增加关联用户信息 - accessTokenGenerator.setAccessTokenCustomizer(new CustomeOAuth2TokenCustomizer()); - return new DelegatingOAuth2TokenGenerator(accessTokenGenerator, new OAuth2RefreshTokenGenerator()); - } + // 单体版本:根据配置启用CORS跨域支持(仅在security.cors.enabled=true时生效) + if (Boolean.TRUE.equals(corsProperties.getEnabled())) { + http.cors(cors -> cors.configurationSource(request -> { + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + CorsConfiguration corsConfiguration = new CorsConfiguration(); + corsProperties.getAllowedOriginPatterns().forEach(corsConfiguration::addAllowedOriginPattern); + corsProperties.getAllowedHeaders().forEach(corsConfiguration::addAllowedHeader); + corsProperties.getAllowedMethods().forEach(corsConfiguration::addAllowedMethod); + corsConfiguration.setAllowCredentials(corsProperties.getAllowCredentials()); + source.registerCorsConfiguration(corsProperties.getPathPattern(), corsConfiguration); + return corsConfiguration; + })); + } - @Bean - public AuthenticationFailureHandler authenticationFailureHandler() { - return new FormAuthenticationFailureHandler(); - } + DefaultSecurityFilterChain securityFilterChain = http.build(); - @Bean - public LogoutSuccessHandler logoutSuccessHandler() { - return new SsoLogoutSuccessHandler(); - } + // 注入自定义授权模式实现 + addCustomOAuth2GrantAuthenticationProvider(http); + return securityFilterChain; + } - /** - * request -> xToken 注入请求转换器 - * - * @return DelegatingAuthenticationConverter - */ - @Bean - public AuthenticationConverter accessTokenRequestConverter() { - return new DelegatingAuthenticationConverter( - Arrays.asList(new OAuth2ResourceOwnerPasswordAuthenticationConverter(), - new OAuth2ResourceOwnerSmsAuthenticationConverter(), - new PigxOAuth2RefreshTokenAuthenticationConverter(), - new OAuth2ClientCredentialsAuthenticationConverter(), - new OAuth2AuthorizationCodeAuthenticationConverter(), - new OAuth2AuthorizationCodeRequestAuthenticationConverter())); - } + /** + * 令牌生成规则实现
+ * client:username:uuid + * + * @return OAuth2TokenGenerator + */ + @Bean + public OAuth2TokenGenerator oAuth2TokenGenerator() { + CustomeOAuth2AccessTokenGenerator accessTokenGenerator = new CustomeOAuth2AccessTokenGenerator(); + // 注入Token 增加关联用户信息 + accessTokenGenerator.setAccessTokenCustomizer(new CustomeOAuth2TokenCustomizer()); + return new DelegatingOAuth2TokenGenerator(accessTokenGenerator, new OAuth2RefreshTokenGenerator()); + } - /** - * 注入授权模式实现提供方 - *

- * 1. 密码模式
- * 2. 短信登录
- */ - @SuppressWarnings("unchecked") - private void addCustomOAuth2GrantAuthenticationProvider(HttpSecurity http) { - AuthenticationManager authenticationManager = http.getSharedObject(AuthenticationManager.class); - OAuth2AuthorizationService authorizationService = http.getSharedObject(OAuth2AuthorizationService.class); + @Bean + public AuthenticationFailureHandler authenticationFailureHandler() { + return new FormAuthenticationFailureHandler(); + } - OAuth2ResourceOwnerPasswordAuthenticationProvider resourceOwnerPasswordAuthenticationProvider = new OAuth2ResourceOwnerPasswordAuthenticationProvider( - authenticationManager, authorizationService, oAuth2TokenGenerator()); + @Bean + public LogoutSuccessHandler logoutSuccessHandler() { + return new SsoLogoutSuccessHandler(); + } - OAuth2ResourceOwnerSmsAuthenticationProvider resourceOwnerSmsAuthenticationProvider = new OAuth2ResourceOwnerSmsAuthenticationProvider( - authenticationManager, authorizationService, oAuth2TokenGenerator()); + /** + * request -> xToken 注入请求转换器 + * + * @return DelegatingAuthenticationConverter + */ + @Bean + public AuthenticationConverter accessTokenRequestConverter() { + return new DelegatingAuthenticationConverter( + Arrays.asList(new OAuth2ResourceOwnerPasswordAuthenticationConverter(), + new OAuth2ResourceOwnerSmsAuthenticationConverter(), + new PigxOAuth2RefreshTokenAuthenticationConverter(), + new OAuth2ClientCredentialsAuthenticationConverter(), + new OAuth2AuthorizationCodeAuthenticationConverter(), + new OAuth2AuthorizationCodeRequestAuthenticationConverter())); + } - // 处理 UsernamePasswordAuthenticationToken - http.authenticationProvider(new PigxDaoAuthenticationProvider()); - // 处理 OAuth2ResourceOwnerPasswordAuthenticationToken - http.authenticationProvider(resourceOwnerPasswordAuthenticationProvider); - // 处理 OAuth2ResourceOwnerSmsAuthenticationToken - http.authenticationProvider(resourceOwnerSmsAuthenticationProvider); - } + /** + * 注入授权模式实现提供方 + *

+ * 1. 密码模式
+ * 2. 短信登录
+ */ + @SuppressWarnings("unchecked") + private void addCustomOAuth2GrantAuthenticationProvider(HttpSecurity http) { + AuthenticationManager authenticationManager = http.getSharedObject(AuthenticationManager.class); + OAuth2AuthorizationService authorizationService = http.getSharedObject(OAuth2AuthorizationService.class); + + OAuth2ResourceOwnerPasswordAuthenticationProvider resourceOwnerPasswordAuthenticationProvider = new OAuth2ResourceOwnerPasswordAuthenticationProvider( + authenticationManager, authorizationService, oAuth2TokenGenerator()); + + OAuth2ResourceOwnerSmsAuthenticationProvider resourceOwnerSmsAuthenticationProvider = new OAuth2ResourceOwnerSmsAuthenticationProvider( + authenticationManager, authorizationService, oAuth2TokenGenerator()); + + // 处理 UsernamePasswordAuthenticationToken + http.authenticationProvider(new PigxDaoAuthenticationProvider()); + // 处理 OAuth2ResourceOwnerPasswordAuthenticationToken + http.authenticationProvider(resourceOwnerPasswordAuthenticationProvider); + // 处理 OAuth2ResourceOwnerSmsAuthenticationToken + http.authenticationProvider(resourceOwnerSmsAuthenticationProvider); + } } diff --git a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/component/PigxBootCorsProperties.java b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/component/PigxBootCorsProperties.java new file mode 100644 index 000000000..204d82d65 --- /dev/null +++ b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/component/PigxBootCorsProperties.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.pig4cloud.pigx.common.security.component; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author lengleng + * @date 2025-11-12 + *

+ * CORS 跨域资源共享配置属性 + */ +@Data +@ConfigurationProperties(prefix = "security.cors") +public class PigxBootCorsProperties { + + /** + * 是否启用CORS跨域支持 + */ + private Boolean enabled = false; + + /** + * 允许的源模式列表,支持通配符 + * 例如:http://localhost:*, https://*.example.com + */ + private List allowedOriginPatterns = new ArrayList<>(List.of("*")); + + /** + * 允许的请求头列表 + * 默认允许所有请求头 + */ + private List allowedHeaders = new ArrayList<>(List.of("*")); + + /** + * 允许的HTTP方法列表 + * 默认允许常用的HTTP方法 + */ + private List allowedMethods = new ArrayList<>(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH")); + + /** + * 是否允许携带凭证(如Cookie) + * 当设置为true时,allowedOriginPatterns不能使用通配符* + */ + private Boolean allowCredentials = true; + + /** + * 应用CORS配置的路径模式 + * 默认应用到所有路径 + */ + private String pathPattern = "/**"; + +} diff --git a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/component/PigxResourceServerConfiguration.java b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/component/PigxResourceServerConfiguration.java index 0ce1e1d03..7ea3506ad 100644 --- a/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/component/PigxResourceServerConfiguration.java +++ b/pigx-common/pigx-common-security/src/main/java/com/pig4cloud/pigx/common/security/component/PigxResourceServerConfiguration.java @@ -32,6 +32,8 @@ import org.springframework.security.core.annotation.AnnotationTemplateExpression import org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; /** * @author lengleng @@ -52,6 +54,8 @@ public class PigxResourceServerConfiguration { @Lazy private final PermitAllUrlProperties permitAllUrl; + private final PigxBootCorsProperties corsProperties; + @Bean @Order(Ordered.HIGHEST_PRECEDENCE) SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { @@ -71,6 +75,20 @@ public class PigxResourceServerConfiguration { .headers(headers -> headers.frameOptions(HeadersConfigurer.FrameOptionsConfig::disable)) .csrf(AbstractHttpConfigurer::disable); + // 仅支持单体版本:根据配置启用CORS跨域支持(仅在security.cors.enabled=true时生效) + if (Boolean.TRUE.equals(corsProperties.getEnabled())) { + http.cors(cors -> cors.configurationSource(request -> { + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + CorsConfiguration corsConfiguration = new CorsConfiguration(); + corsProperties.getAllowedOriginPatterns().forEach(corsConfiguration::addAllowedOriginPattern); + corsProperties.getAllowedHeaders().forEach(corsConfiguration::addAllowedHeader); + corsProperties.getAllowedMethods().forEach(corsConfiguration::addAllowedMethod); + corsConfiguration.setAllowCredentials(corsProperties.getAllowCredentials()); + source.registerCorsConfiguration(corsProperties.getPathPattern(), corsConfiguration); + return corsConfiguration; + })); + } + return http.build(); } @@ -83,4 +101,5 @@ public class PigxResourceServerConfiguration { AnnotationTemplateExpressionDefaults prePostTemplateDefaults() { return new AnnotationTemplateExpressionDefaults(); } + } diff --git a/pigx-common/pigx-common-security/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/pigx-common/pigx-common-security/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports index a5ea77dac..947141ae7 100644 --- a/pigx-common/pigx-common-security/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ b/pigx-common/pigx-common-security/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -4,7 +4,9 @@ com.pig4cloud.pigx.common.security.service.PigxMobileUserDetailServiceImpl com.pig4cloud.pigx.common.security.service.PigxTocMobileUserDetailsServiceImpl com.pig4cloud.pigx.common.security.service.PigxRedisOAuth2AuthorizationService com.pig4cloud.pigx.common.security.service.PigxRedisOAuth2AuthorizationConsentService +com.pig4cloud.pigx.common.security.component.PigxBootCorsProperties com.pig4cloud.pigx.common.security.component.PigxSecurityInnerAspect com.pig4cloud.pigx.common.security.component.PigxInnerHeaderRemoveFilter com.pig4cloud.pigx.common.security.component.PigxSecurityMessageSourceConfiguration com.pig4cloud.pigx.common.security.service.PigxRemoteRegisteredClientRepository +