Spring Security 响应式
概述
Spring Security 提供了完整的响应式(Reactive)安全支持,与 Servlet 栈对应,使用 Reactor 的 Flux/Mono 驱动安全过滤器链。
核心差异
| 维度 | Servlet 栈 | WebFlux 栈 |
|---|---|---|
| 过滤器链 | SecurityFilterChain + Filter | SecurityWebFilterChain + WebFilter |
| 安全上下文 | SecurityContextHolder(ThreadLocal) | ReactiveSecurityContextHolder(Reactor Context) |
| 认证管理器 | AuthenticationManager | ReactiveAuthenticationManager |
| 用户详情 | UserDetailsService | ReactiveUserDetailsService |
| 注解驱动 | @EnableWebSecurity | @EnableWebFluxSecurity |
一、启用响应式安全
java
@Configuration
@EnableWebFluxSecurity
public class ReactiveSecurityConfig {
@Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
http
.authorizeExchange(exchanges -> exchanges
.pathMatchers("/public/**").permitAll()
.pathMatchers("/admin/**").hasRole("ADMIN")
.anyExchange().authenticated()
)
.httpBasic(Customizer.withDefaults())
.formLogin(Customizer.withDefaults())
.csrf(ServerHttpSecurity.CsrfSpec::disable);
return http.build();
}
}二、ReactiveUserDetailsService
java
@Component
public class ReactiveUserDetailsServiceImpl implements ReactiveUserDetailsService {
@Autowired
private UserRepository userRepository;
@Override
public Mono<UserDetails> findByUsername(String username) {
return userRepository.findByUsername(username)
.map(user -> User.withUsername(user.getUsername())
.password(user.getPassword())
.roles(user.getRoles().toArray(new String[0]))
.build()
);
}
}2.1 ReactiveAuthenticationManager
java
@Component
public class JwtReactiveAuthenticationManager implements ReactiveAuthenticationManager {
@Autowired
private JwtDecoder jwtDecoder;
@Autowired
private ReactiveUserDetailsService userDetailsService;
@Override
public Mono<Authentication> authenticate(Authentication authentication) {
BearerTokenAuthenticationToken token =
(BearerTokenAuthenticationToken) authentication;
return Mono.fromCallable(() -> jwtDecoder.decode(token.getToken()))
.flatMap(jwt -> {
String username = jwt.getSubject();
return userDetailsService.findByUsername(username)
.map(userDetails -> {
JwtAuthenticationToken auth =
new JwtAuthenticationToken(jwt, userDetails.getAuthorities());
auth.setDetails(token.getDetails());
return (Authentication) auth;
});
})
.onErrorMap(JwtException.class, e ->
new BadCredentialsException("Invalid JWT", e));
}
@Override
public boolean supports(Class<?> authentication) {
return BearerTokenAuthenticationToken.class.isAssignableFrom(authentication);
}
}三、SecurityWebFilterChain 与 WebFilter
java
@Configuration
@EnableWebFluxSecurity
public class ReactiveSecurityConfig {
@Autowired
private JwtReactiveAuthenticationManager jwtAuthManager;
@Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
http
.authorizeExchange(exchanges -> exchanges
.pathMatchers(HttpMethod.GET, "/api/public/**").permitAll()
.pathMatchers("/api/admin/**").hasRole("ADMIN")
.anyExchange().authenticated()
)
// 使用 JWT Bearer Token 认证
.oauth2ResourceServer(oauth2 -> oauth2
.jwt(jwt -> jwt.authenticationManager(jwtAuthManager))
)
// 添加自定义 WebFilter
.addFilterBefore(new RequestLoggingWebFilter(), SecurityWebFiltersOrder.AUTHENTICATION)
.csrf(ServerHttpSecurity.CsrfSpec::disable)
.cors(Customizer.withDefaults());
return http.build();
}
}3.1 自定义 WebFilter
java
@Component
@Order(Ordered.HIGHEST_PRECEDENCE + 10)
public class RequestLoggingWebFilter implements WebFilter {
private static final Logger log = LoggerFactory.getLogger("REQUEST_LOG");
@Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
long start = System.currentTimeMillis();
return chain.filter(exchange)
.doFinally(signalType -> {
long duration = System.currentTimeMillis() - start;
log.info("{} {} {}ms -> {}",
request.getMethod(), request.getURI().getPath(),
duration, exchange.getResponse().getStatusCode());
});
}
}
// 签名验证 Filter
@Component
public class ApiSignatureWebFilter implements WebFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
String signature = request.getHeaders().getFirst("X-Signature");
String timestamp = request.getHeaders().getFirst("X-Timestamp");
if (!isValidSignature(request, signature, timestamp)) {
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete();
}
return chain.filter(exchange);
}
}四、ReactiveSecurityContextHolder
4.1 获取当前用户
java
@Service
public class ReactiveSecurityService {
// 在 Controller 或 Service 中获取
public Mono<String> getCurrentUsername() {
return ReactiveSecurityContextHolder.getContext()
.map(SecurityContext::getAuthentication)
.map(Authentication::getName);
}
// 获取当前用户的详细信息
public Mono<UserDetails> getCurrentUser() {
return ReactiveSecurityContextHolder.getContext()
.map(SecurityContext::getAuthentication)
.map(auth -> (UserDetails) auth.getPrincipal());
}
// 检查是否有权限
public Mono<Boolean> hasRole(String role) {
return ReactiveSecurityContextHolder.getContext()
.map(ctx -> ctx.getAuthentication().getAuthorities()
.stream()
.anyMatch(a -> a.getAuthority().equals("ROLE_" + role)))
.defaultIfEmpty(false);
}
}4.2 在 Controller 中使用
java
@RestController
@RequestMapping("/api/orders")
public class OrderController {
@GetMapping("/current")
public Mono<Order> getMyOrder() {
return ReactiveSecurityContextHolder.getContext()
.map(ctx -> ctx.getAuthentication().getName())
.flatMap(orderService::findLatestByUsername);
}
@GetMapping
public Flux<Order> getAllOrders() {
return ReactiveSecurityContextHolder.getContext()
.flatMapMany(ctx -> {
String username = ctx.getAuthentication().getName();
boolean isAdmin = ctx.getAuthentication().getAuthorities()
.stream().anyMatch(a -> a.getAuthority().equals("ROLE_ADMIN"));
return isAdmin
? orderService.findAll()
: orderService.findByUsername(username);
});
}
}4.3 单元测试
java
@SpringBootTest
class ReactiveSecurityServiceTest {
@Autowired
private ReactiveSecurityService securityService;
@Test
void testGetCurrentUsername() {
// 设置响应式安全上下文
SecurityContext context = new SecurityContextImpl(
new UsernamePasswordAuthenticationToken("user1", null,
AuthorityUtils.createAuthorityList("ROLE_USER"))
);
StepVerifier.create(
// 通过 Reactor Context 注入 SecurityContext
securityService.getCurrentUsername()
.contextWrite(ctx -> ctx.put(SecurityContext.class, context))
)
.expectNext("user1")
.verifyComplete();
}
}五、实战:WebFlux 短链服务安全
5.1 JWT 无状态认证
java
@Configuration
@EnableWebFluxSecurity
public class ShortLinkSecurityConfig {
@Bean
public SecurityWebFilterChain filterChain(ServerHttpSecurity http) {
http
.authorizeExchange(exchanges -> exchanges
.pathMatchers("/api/public/**").permitAll()
.pathMatchers("/api/short-links/**").authenticated()
.anyExchange().permitAll()
)
.oauth2ResourceServer(oauth2 -> oauth2
.jwt(jwt -> jwt
.jwkSetUri("https://auth.example.com/.well-known/jwks.json")
.jwtAuthenticationConverter(reactiveJwtAuthConverter())
)
)
.csrf(ServerHttpSecurity.CsrfSpec::disable)
.cors(cors -> cors.configurationSource(corsConfigurationSource()));
return http.build();
}
// 限流 Filter
@Bean
public WebFilter rateLimitWebFilter() {
return (exchange, chain) -> {
String ip = exchange.getRequest().getRemoteAddress() != null
? exchange.getRequest().getRemoteAddress().getAddress().getHostAddress()
: "unknown";
return rateLimiter.isAllowed(ip)
.flatMap(allowed -> {
if (!allowed) {
exchange.getResponse().setStatusCode(HttpStatus.TOO_MANY_REQUESTS);
return exchange.getResponse().setComplete();
}
return chain.filter(exchange);
});
};
}
// CORS 配置
private CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration config = new CorsConfiguration();
config.setAllowedOrigins(List.of("https://example.com"));
config.setAllowedMethods(List.of("GET", "POST", "DELETE"));
config.setAllowedHeaders(List.of("*"));
config.setMaxAge(3600L);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/api/**", config);
return source;
}
}5.2 短链服务
java
@RestController
@RequestMapping("/api/short-links")
public class ShortLinkController {
@Autowired
private ShortLinkService service;
@PostMapping
public Mono<ShortLinkResponse> create(@RequestBody CreateShortLinkRequest request) {
return ReactiveSecurityContextHolder.getContext()
.map(ctx -> ctx.getAuthentication().getName())
.flatMap(username -> service.create(request.getUrl(), username));
}
@DeleteMapping("/{code}")
public Mono<Void> delete(@PathVariable String code) {
return service.delete(code);
}
@GetMapping
public Flux<ShortLinkResponse> listMyLinks() {
return ReactiveSecurityContextHolder.getContext()
.map(ctx -> ctx.getAuthentication().getName())
.flatMapMany(service::findByUsername);
}
}六、总结
| 知识点 | 说明 |
|---|---|
@EnableWebFluxSecurity | 启用响应式安全 |
SecurityWebFilterChain | 响应式过滤器链 |
ReactiveUserDetailsService | 响应式用户加载 |
ReactiveAuthenticationManager | 响应式认证管理器 |
ReactiveSecurityContextHolder | 基于 Reactor Context 的安全上下文 |
WebFilter | 响应式过滤器(替代 Servlet Filter) |
| JWT 认证 | oauth2ResourceServer().jwt() 配置 |
参考链接: