Spring Security 6 与 Spring Cloud Gateway 整合指南
整合概述
Spring Security 与 Spring Cloud Gateway 的整合提供了在网关层实现统一安全控制的能力,避免了在每个微服务中重复实现安全逻辑。
核心优势
- 统一认证与授权:在网关层集中处理所有安全逻辑
- 减少冗余:下游微服务无需实现安全机制
- 灵活路由:结合安全策略进行动态路由
- 高效防护:在入口处拦截非法请求
核心依赖
<dependencies>
<!-- Spring Cloud Gateway -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
<version>4.0.6</version>
</dependency>
<!-- Spring Security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- OAuth2 资源服务器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
<!-- 响应式Web支持 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
</dependencies>
安全配置类
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.oauth2.server.resource.authentication.ReactiveJwtAuthenticationConverter;
import org.springframework.security.web.server.SecurityWebFilterChain;
import org.springframework.security.web.server.csrf.CookieServerCsrfTokenRepository;
@Configuration
@EnableWebFluxSecurity // 网关使用WebFlux安全配置
public class GatewaySecurityConfig {
@Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
http
.authorizeExchange(exchanges -> exchanges
// 公开访问端点
.pathMatchers("/auth/**", "/public/**").permitAll()
// 需要认证的端点
.pathMatchers("/user/**").hasAnyRole("USER", "ADMIN")
.pathMatchers("/admin/**").hasRole("ADMIN")
// 所有其他请求需要认证
.anyExchange().authenticated()
)
.oauth2ResourceServer(oauth2 -> oauth2
.jwt(jwt -> jwt
.jwtAuthenticationConverter(jwtAuthenticationConverter())
)
)
.csrf(csrf -> csrf
.csrfTokenRepository(CookieServerCsrfTokenRepository.withHttpOnlyFalse())
)
.formLogin(ServerHttpSecurity.FormLoginSpec::disable); // 禁用表单登录
return http.build();
}
// 自定义JWT转换器
private ReactiveJwtAuthenticationConverter jwtAuthenticationConverter() {
ReactiveJwtAuthenticationConverter converter = new ReactiveJwtAuthenticationConverter();
converter.setJwtGrantedAuthoritiesConverter(jwt -> {
// 从JWT声明中提取角色信息
List<String> roles = jwt.getClaimAsStringList("roles");
return roles.stream()
.map(role -> new SimpleGrantedAuthority("ROLE_" + role))
.collect(Collectors.toList());
});
return converter;
}
}
网关路由配置
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class GatewayRoutesConfig {
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
// 用户服务路由
.route("user-service", r -> r.path("/user/**")
.filters(f -> f
.stripPrefix(1) // 移除路径前缀
.addRequestHeader("X-User-Service", "gateway")
)
.uri("lb://user-service") // 负载均衡到用户服务
)
// 产品服务路由
.route("product-service", r -> r.path("/product/**")
.filters(f -> f
.stripPrefix(1)
.addRequestHeader("X-Product-Service", "gateway")
// 添加JWT到下游服务
.filter(new AddJwtTokenFilter())
)
.uri("lb://product-service")
)
// 认证服务路由
.route("auth-service", r -> r.path("/auth/**")
.filters(f -> f.stripPrefix(1))
.uri("lb://auth-service")
)
// WebSocket路由示例
.route("websocket-route", r -> r.path("/ws/**")
.uri("lb://notification-service")
)
.build();
}
}
自定义过滤器:传递用户信息
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.ReactiveSecurityContextHolder;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;
@Component
public class AddJwtTokenFilter extends AbstractGatewayFilterFactory<AddJwtTokenFilter.Config> {
public AddJwtTokenFilter() {
super(Config.class);
}
@Override
public GatewayFilter apply(Config config) {
return (exchange, chain) -> ReactiveSecurityContextHolder.getContext()
.map(ctx -> ctx.getAuthentication())
.filter(Authentication::isAuthenticated)
.flatMap(authentication -> {
// 添加认证信息到请求头
ServerHttpRequest request = exchange.getRequest().mutate()
.header("X-User-Id", authentication.getName())
.header("X-Authorities", getAuthoritiesString(authentication))
.build();
return chain.filter(exchange.mutate().request(request).build());
})
.switchIfEmpty(chain.filter(exchange)); // 未认证用户继续传递
}
private String getAuthoritiesString(Authentication authentication) {
return authentication.getAuthorities().stream()
.map(grantedAuthority -> grantedAuthority.getAuthority())
.collect(Collectors.joining(","));
}
public static class Config {
// 可配置的过滤器参数
}
}
限流与熔断配置
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
import reactor.core.publisher.Mono;
@Configuration
public class RateLimitConfig {
// 基于用户ID的限流
@Bean
public KeyResolver userKeyResolver() {
return exchange -> ReactiveSecurityContextHolder.getContext()
.map(ctx -> ctx.getAuthentication().getName())
.switchIfEmpty(Mono.just("anonymous"));
}
// 基于IP的限流
@Bean
public KeyResolver ipKeyResolver() {
return exchange -> Mono.just(
exchange.getRequest().getRemoteAddress().getAddress().getHostAddress()
);
}
}
在 application.yml
中配置限流规则:
spring:
cloud:
gateway:
routes:
- id: user-service
uri: lb://user-service
predicates:
- Path=/user/**
filters:
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 10 # 每秒允许的请求数
redis-rate-limiter.burstCapacity: 20 # 最大突发请求数
key-resolver: "#{@userKeyResolver}" # 使用用户ID限流
- name: CircuitBreaker
args:
name: userServiceBreaker
fallbackUri: forward:/fallback/user
整合OAuth2登录
// 在安全配置类中添加OAuth2登录支持
@Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
http
.oauth2Login(oauth2 -> oauth2
.authenticationSuccessHandler(new RedirectServerAuthenticationSuccessHandler("/login-success"))
.authenticationFailureHandler(new RedirectServerAuthenticationFailureHandler("/login-failure"))
)
// ...其他配置
return http.build();
}
跨域配置
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.reactive.CorsWebFilter;
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;
@Configuration
public class CorsConfig {
@Bean
public CorsWebFilter corsWebFilter() {
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
config.addAllowedOrigin("https://trusted-domain.com");
config.addAllowedHeader("*");
config.addAllowedMethod("*");
config.setMaxAge(3600L);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
return new CorsWebFilter(source);
}
}
整合监控与追踪
management:
endpoints:
web:
exposure:
include: health, info, metrics, gateway
metrics:
tags:
application: ${spring.application.name}
// 添加追踪ID到请求头
@Bean
public GlobalFilter traceIdFilter() {
return (exchange, chain) -> {
String traceId = MDC.get("traceId"); // 从MDC获取追踪ID
if (traceId != null) {
ServerHttpRequest request = exchange.getRequest().mutate()
.header("X-Trace-Id", traceId)
.build();
return chain.filter(exchange.mutate().request(request).build());
}
return chain.filter(exchange);
};
}
最佳实践建议
-
分层安全策略
- 网关层:基础认证、限流、CSRF防护
- 微服务层:细粒度授权、数据级权限控制
-
JWT处理策略
- 网关验证JWT有效性
- 将必要用户信息传递给下游服务
- 避免传递完整JWT以减少网络开销
-
零信任架构
// 在网关层添加额外验证 .filter((exchange, chain) -> { if (!isDeviceTrusted(exchange.getRequest())) { return Mono.error(new DeviceNotTrustedException()); } return chain.filter(exchange); })
-
动态路由更新
@RefreshScope @Bean public RouteLocator dynamicRoutes(RouteLocatorBuilder builder) { // 从配置中心动态加载路由 }
-
安全事件监控
@EventListener public void onAuthenticationFailure(AuthorizationDeniedEvent event) { log.warn("Authorization denied for {}: {}", event.getAuthentication().getName(), event.getAccessDeniedException().getMessage()); // 发送安全警报 }
常见问题解决方案
-
CSRF与POST请求冲突
- 方案:为API端点禁用CSRF
.csrf(csrf -> csrf.requireCsrfProtectionMatcher( exchange -> { String path = exchange.getRequest().getPath().value(); return !path.startsWith("/api/"); } ))
-
WebSocket安全
.authorizeExchange(exchanges -> exchanges .pathMatchers("/ws/**").permitAll() // 允许WebSocket连接 )
-
文件上传限制
spring: webflux: max-in-memory-size: 10MB max-request-size: 50MB
-
自定义认证错误响应
.exceptionHandling(handling -> handling .authenticationEntryPoint((exchange, ex) -> { exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED); return exchange.getResponse().writeWith( Mono.just(exchange.getResponse().bufferFactory() .wrap("{\"error\":\"Unauthorized\"}".getBytes())) ); }) )
Spring Security 6与Spring Cloud Gateway的整合为微服务架构提供了强大的安全网关解决方案。这种模式不仅简化了安全管理的复杂性,还通过集中式控制提高了系统的整体安全性。根据实际需求,您可以灵活调整安全策略,实现从基础认证到复杂零信任架构的各种安全场景。