HttpSecurity (Servlet API)
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {http.cors(cors -> cors.configurationSource(request -> {CorsConfiguration configuration = new CorsConfiguration();configuration.setAllowedOriginPatterns(List.of("*")); // 允许所有源configuration.setAllowedMethods(List.of("*")); // 允许所有方法configuration.setAllowedHeaders(List.of("*")); // 允许所有请求头configuration.setAllowCredentials(true); // 注意:使用 * 时必须设置为 falsereturn configuration;})).csrf(AbstractHttpConfigurer::disable)//对于一些无状态的 API 服务(例如,使用 JWT 认证的 RESTful API),CSRF 保护可以被禁用,因为这些服务通常通过 HTTP Header(如 Authorization)来认证请求,而不依赖 Cookie。对于这样的服务,CSRF 攻击的风险较低,通常可以通过禁用 CSRF 来提高性能。.formLogin(AbstractHttpConfigurer::disable)// 使用新的授权配置方式.authorizeHttpRequests(authz -> authz.requestMatchers( "/auth/login").permitAll() // 使用requestMatchers替代antMatchers.anyRequest().authenticated())// 会话管理配置保持不变.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)).addFilterBefore(jwtAuthenticationFilter, AnonymousAuthenticationFilter.class) ; // 将 JwtAuthenticationFilter 加入过滤链return http.build();}
-
适用场景:Servlet 容器下(Tomcat、Jetty等)的 Spring Boot Web 项目,或者传统的 Spring MVC 应用。
-
对应的依赖:
spring-boot-starter-web
+spring-boot-starter-security
(针对 servlet)。 -
底层协议:基于
HttpServletRequest
和HttpServletResponse
(Servlet API),每个请求都在一个独立的线程里处理。 -
典型用法:在传统 MVC/Web 应用中进行登录表单、会话管理、拦截/放行等安全配置。
ServerHttpSecurity (Reactive API)
@Beanpublic SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {http// CORS 配置.cors(cors -> cors.configurationSource(request -> {CorsConfiguration configuration = new CorsConfiguration();configuration.setAllowedOriginPatterns(List.of("*")); // 允许所有源configuration.setAllowedMethods(List.of("*")); // 允许所有方法configuration.setAllowedHeaders(List.of("*")); // 允许所有请求头configuration.setAllowCredentials(true); // 注意:使用 * 时必须设置为 falsereturn configuration;}))// 禁用 CSRF 和表单登录.csrf(ServerHttpSecurity.CsrfSpec::disable).formLogin(ServerHttpSecurity.FormLoginSpec::disable) // 禁用表单登录.httpBasic(ServerHttpSecurity.HttpBasicSpec::disable) // 禁用 HTTP Basic 认证.logout(ServerHttpSecurity.LogoutSpec::disable) // 禁用默认的注销端点// 使用新的授权配置方式.authorizeExchange(authz -> authz.pathMatchers("/order/**").permitAll() // 放行登录接口.anyExchange().authenticated() // 其他请求需要认证)// 添加 JWT 认证过滤器.addFilterBefore(jwtAuthenticationWebFilter, SecurityWebFiltersOrder.AUTHENTICATION); // 将 JwtAuthenticationFilter 加入过滤链return http.build();}
-
适用场景:Reactive(反应式)Web 环境,比如使用 Spring WebFlux 或者 Spring Cloud Gateway。
-
对应的依赖:
spring-boot-starter-webflux
+spring-boot-starter-security
(针对 reactive)。 -
底层协议:基于非阻塞式(Netty 等)的反应式流处理。
-
典型用法:在使用 WebFlux Handler 或 Spring Cloud Gateway 时,通过
ServerHttpSecurity
配置响应式的安全策略。
为什么 Spring Cloud Gateway 往往会看到 ServerHttpSecurity
?
-
因为 Spring Cloud Gateway 基于 WebFlux/reactor-netty 实现,是一个典型的非阻塞、响应式的网关。Spring Security 针对这种响应式请求的链式处理方式,需要使用
ServerHttpSecurity
来配置。 -
而传统的基于 Servlet 的网关(例如 Zuul 1.x)就还是用 HttpSecurity。
ServerHttpSecurity中添加的过滤器需要实现WebFilter(与Spring MVC不同)
import com.aqian.common.enums.ResponseCodeEnum; import com.aqian.common.exception.BaseException; import com.aqian.gatewayserver.util.JwtUtil; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Component; import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.WebFilter; import org.springframework.web.server.WebFilterChain; import reactor.core.publisher.Mono; import reactor.util.annotation.NonNull;@Component public class JwtAuthenticationWebFilter implements WebFilter {@Autowiredprivate JwtUtil jwtUtil;@Overridepublic @NonNull Mono<Void> filter(@NonNull ServerWebExchange exchange, @NonNull WebFilterChain chain) {// 从请求头中获取JWTString token = getJwtFromRequest(exchange);System.out.println("经过了webfilter");// 如果JWT存在且有效,则进行验证if (token != null) {try {// token 是否有效 / 是否过期 / 是否匹配对应用户Integer userId = Integer.parseInt(jwtUtil.extractUserId(token));boolean valid = jwtUtil.validateToken(token, userId);// 如果 Token 无效或过期,直接抛出异常if (!valid) {// 这里可以区分是过期还是无效,根据业务逻辑不同抛不同的异常if (jwtUtil.isTokenExpired(token)) {throw new BaseException(ResponseCodeEnum.TOKEN_EXPIRED);}throw new BaseException(ResponseCodeEnum.TOKEN_INVALID);}// 如果 token 合法,设置到 SecurityContextUsernamePasswordAuthenticationToken authentication =new UsernamePasswordAuthenticationToken(userId, null, null);SecurityContextHolder.getContext().setAuthentication(authentication);// 继续执行过滤链return chain.filter(exchange);} catch (BaseException e) {// 捕获自定义异常,并设置响应状态码exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);return exchange.getResponse().setComplete();} catch (Exception e) {// 处理其他异常exchange.getResponse().setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR);return exchange.getResponse().setComplete();}}// 如果没有JWT,继续执行过滤链return chain.filter(exchange);}/*** 从HTTP请求头中提取JWT令牌* @param exchange ServerWebExchange* @return JWT令牌*/private String getJwtFromRequest(ServerWebExchange exchange) {String bearerToken = exchange.getRequest().getHeaders().getFirst(HttpHeaders.AUTHORIZATION); // 从Authorization头部获取JWTif (bearerToken != null && bearerToken.startsWith("Bearer ")) { // 如果以"Bearer "开头return bearerToken.substring(7); // 去掉"Bearer ",返回JWT部分}return null; // 如果没有JWT令牌,返回null} }
从表象上,许多开发者就会这么区分:“HttpSecurity 在 Spring Boot Web/MVC 场景下用;ServerHttpSecurity 多见于 Spring Cloud Gateway”。但更准确的表述是:
Servlet 模型 (Spring MVC、Tomcat、Jetty 等) →
HttpSecurity
Reactive 模型 (Spring WebFlux、Netty、Gateway 等) →
ServerHttpSecurity
Spring Boot 本身并不强行规定你用 Servlet 还是 Reactive。你可以在 Spring Boot 中使用任何一种模型,只要你的依赖是对应的
spring-boot-starter-web
(MVC) 或者spring-boot-starter-webflux
(WebFlux)。
Spring Boot Servlet 项目一般默认是
HttpSecurity
。Spring Boot WebFlux 项目则需要
ServerHttpSecurity
。Spring Cloud Gateway 是响应式的,用
ServerHttpSecurity
。如果你在写的服务最终是非响应式的(Servlet 模型),就用 HttpSecurity;如果你的服务是响应式的(Reactive 模型,如 Spring Cloud Gateway 项目),就用 ServerHttpSecurity。
Spring Security WebFlux 异常处理配置详解
在 Spring Security WebFlux 中,.exceptionHandling()
方法用于配置安全异常处理行为。下面我将详细讲解它的使用方法。
基本概念
exceptionHandling()
是 ServerHttpSecurity
配置中的一个重要部分,它允许你自定义当安全相关异常发生时(如认证失败、访问被拒绝等)的处理方式。
基本配置结构
@Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {return http// 其他配置....exceptionHandling(exceptionHandling -> exceptionHandling.authenticationEntryPoint(...).accessDeniedHandler(...))// 其他配置....build();
}
主要配置选项
(1)authenticationEntryPoint
配置认证入口点,处理未认证用户尝试访问受保护资源时的响应。
.exceptionHandling(exceptionHandling -> exceptionHandling.authenticationEntryPoint((exchange, ex) -> {// 自定义响应ServerHttpResponse response = exchange.getResponse();response.setStatusCode(HttpStatus.UNAUTHORIZED);response.getHeaders().setContentType(MediaType.APPLICATION_JSON);DataBuffer buffer = response.bufferFactory().wrap("{\"error\":\"Unauthorized\"}".getBytes());return response.writeWith(Mono.just(buffer));})
)
或者使用预定义的入口点:
.exceptionHandling(exceptionHandling -> exceptionHandling.authenticationEntryPoint(new HttpBasicServerAuthenticationEntryPoint())
)
(2)accessDeniedHandler
配置访问拒绝处理器,处理已认证但权限不足的用户尝试访问受保护资源时的响应。
.exceptionHandling(exceptionHandling -> exceptionHandling.accessDeniedHandler((exchange, ex) -> {// 自定义响应ServerHttpResponse response = exchange.getResponse();response.setStatusCode(HttpStatus.FORBIDDEN);response.getHeaders().setContentType(MediaType.APPLICATION_JSON);DataBuffer buffer = response.bufferFactory().wrap("{\"error\":\"Access Denied\"}".getBytes());return response.writeWith(Mono.just(buffer));})
)
完整示例
下面是一个完整的配置示例:
@Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {return http.authorizeExchange(exchanges -> exchanges.pathMatchers("/public/**").permitAll().pathMatchers("/admin/**").hasRole("ADMIN").anyExchange().authenticated()).httpBasic(withDefaults()).formLogin(withDefaults()).exceptionHandling(exceptionHandling -> exceptionHandling.authenticationEntryPoint((exchange, ex) -> {ServerHttpResponse response = exchange.getResponse();response.setStatusCode(HttpStatus.UNAUTHORIZED);response.getHeaders().setContentType(MediaType.APPLICATION_JSON);DataBuffer buffer = response.bufferFactory().wrap("{\"message\":\"Authentication required\"}".getBytes());return response.writeWith(Mono.just(buffer));}).accessDeniedHandler((exchange, ex) -> {ServerHttpResponse response = exchange.getResponse();response.setStatusCode(HttpStatus.FORBIDDEN);response.getHeaders().setContentType(MediaType.APPLICATION_JSON);DataBuffer buffer = response.bufferFactory().wrap("{\"message\":\"Insufficient privileges\"}".getBytes());return response.writeWith(Mono.just(buffer));})).csrf(ServerHttpSecurity.CsrfSpec::disable).build();
}
高级用法
高级用法
(1)根据请求类型返回不同响应
.exceptionHandling(exceptionHandling -> exceptionHandling.authenticationEntryPoint((exchange, ex) -> {ServerHttpResponse response = exchange.getResponse();if (exchange.getRequest().getHeaders().getAccept().contains(MediaType.APPLICATION_JSON)) {response.setStatusCode(HttpStatus.UNAUTHORIZED);response.getHeaders().setContentType(MediaType.APPLICATION_JSON);DataBuffer buffer = response.bufferFactory().wrap("{\"error\":\"Unauthorized\"}".getBytes());return response.writeWith(Mono.just(buffer));} else {response.setStatusCode(HttpStatus.FOUND);response.getHeaders().setLocation(URI.create("/login"));return response.setComplete();}})
)
(2)记录异常日志
.exceptionHandling(exceptionHandling -> exceptionHandling.accessDeniedHandler((exchange, ex) -> {log.warn("Access denied for user {} to path {}", exchange.getPrincipal().block().getName(),exchange.getRequest().getPath());ServerHttpResponse response = exchange.getResponse();response.setStatusCode(HttpStatus.FORBIDDEN);return response.setComplete();})
)
(2)组合多个处理器
.exceptionHandling(exceptionHandling -> exceptionHandling.authenticationEntryPoint(new DelegatingServerAuthenticationEntryPoint(Arrays.asList(new BearerTokenServerAuthenticationEntryPoint(),new HttpBasicServerAuthenticationEntryPoint(),new RedirectServerAuthenticationEntryPoint("/login"))))
)
注意事项
-
在响应式编程中,确保你的处理器返回的是
Mono<Void>
-
避免在处理器中进行阻塞操作
-
考虑使用
ServerWebExchange
的完整能力来构建响应 -
对于生产环境,建议将错误信息标准化