mirror of
http://git.pig4cloud.com/pig/pigx.git
synced 2026-11-08 12:39:05 +08:00
feat(security): 新增单体一键 CORS跨域配置支持 security.cors.enabled
- 新增PigxBootCorsProperties配置类用于管理CORS跨域参数 - 在AuthorizationServer和ResourceServer中集成CORS配置逻辑 - 支持通过security.cors.enabled开关控制跨域功能 - 提供allowedOriginPatterns等细粒度跨域参数配置
This commit is contained in:
1 parent
470f5e7fdb
commit
5123e3f116
4 files changed
+204
-93
No files matched your search
+113
-93
@@ -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());
|
||||
|
||||
/**
|
||||
* 令牌生成规则实现 </br>
|
||||
* 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()));
|
||||
}
|
||||
/**
|
||||
* 令牌生成规则实现 </br>
|
||||
* client:username:uuid
|
||||
*
|
||||
* @return OAuth2TokenGenerator
|
||||
*/
|
||||
@Bean
|
||||
public OAuth2TokenGenerator oAuth2TokenGenerator() {
|
||||
CustomeOAuth2AccessTokenGenerator accessTokenGenerator = new CustomeOAuth2AccessTokenGenerator();
|
||||
// 注入Token 增加关联用户信息
|
||||
accessTokenGenerator.setAccessTokenCustomizer(new CustomeOAuth2TokenCustomizer());
|
||||
return new DelegatingOAuth2TokenGenerator(accessTokenGenerator, new OAuth2RefreshTokenGenerator());
|
||||
}
|
||||
|
||||
/**
|
||||
* 注入授权模式实现提供方
|
||||
* <p>
|
||||
* 1. 密码模式 </br>
|
||||
* 2. 短信登录 </br>
|
||||
*/
|
||||
@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);
|
||||
}
|
||||
/**
|
||||
* 注入授权模式实现提供方
|
||||
* <p>
|
||||
* 1. 密码模式 </br>
|
||||
* 2. 短信登录 </br>
|
||||
*/
|
||||
@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);
|
||||
}
|
||||
|
||||
}
|
||||
+70
@@ -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
|
||||
* <p>
|
||||
* CORS 跨域资源共享配置属性
|
||||
*/
|
||||
@Data
|
||||
@ConfigurationProperties(prefix = "security.cors")
|
||||
public class PigxBootCorsProperties {
|
||||
|
||||
/**
|
||||
* 是否启用CORS跨域支持
|
||||
*/
|
||||
private Boolean enabled = false;
|
||||
|
||||
/**
|
||||
* 允许的源模式列表,支持通配符
|
||||
* 例如:http://localhost:*, https://*.example.com
|
||||
*/
|
||||
private List<String> allowedOriginPatterns = new ArrayList<>(List.of("*"));
|
||||
|
||||
/**
|
||||
* 允许的请求头列表
|
||||
* 默认允许所有请求头
|
||||
*/
|
||||
private List<String> allowedHeaders = new ArrayList<>(List.of("*"));
|
||||
|
||||
/**
|
||||
* 允许的HTTP方法列表
|
||||
* 默认允许常用的HTTP方法
|
||||
*/
|
||||
private List<String> allowedMethods = new ArrayList<>(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH"));
|
||||
|
||||
/**
|
||||
* 是否允许携带凭证(如Cookie)
|
||||
* 当设置为true时,allowedOriginPatterns不能使用通配符*
|
||||
*/
|
||||
private Boolean allowCredentials = true;
|
||||
|
||||
/**
|
||||
* 应用CORS配置的路径模式
|
||||
* 默认应用到所有路径
|
||||
*/
|
||||
private String pathPattern = "/**";
|
||||
|
||||
}
|
||||
+19
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
+2
@@ -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
|
||||
|
||||
Reference in new issue
Block a user