Spring Webflux集成Spring Security权限验证异常,疑上下文设置问题
Spring Webflux 集成 Spring Security 权限验证异常问题
环境版本
- spring-boot-starter-webflux 3.3.4
- spring-security-web 6.3.3
- Java 21
问题场景
我在验证权限逻辑时,刻意忽略认证过滤器,通过Token访问接口并缩减用户权限,预期触发权限拒绝异常,但实际抛出AuthenticationCredentialsNotFoundException,提示未认证。
相关代码
JwtValidationWebFilter 实现
@Component public class JwtValidationWebFilter implements WebFilter { @Autowired private JwtTokenProvider jwtTokenProvider; @Autowired private ObjectMapper objectMapper; @Override public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) { String token = extractToken(exchange.getRequest()); if (!StringUtils.hasText(token)) { return chain.filter(exchange); } LogUtils.info("从请求头中提取 token: {}", token); boolean validated = jwtTokenProvider.validateToken(token); if (!validated) { LogUtils.info("验证 token 失败"); return chain.filter(exchange); } Authentication authentication = jwtTokenProvider.getAuthentication(token); try { LogUtils.info("提取的权限节点: {}", objectMapper.writeValueAsString(authentication.getAuthorities())); } catch (JsonProcessingException e) { throw new RuntimeException(e); } // 设置上下文 Context context = ReactiveSecurityContextHolder.withAuthentication(authentication); return chain.filter(exchange).contextWrite(context); } private String extractToken(ServerHttpRequest request) { String bearerToken = request.getHeaders().getFirst(HttpHeaders.AUTHORIZATION); if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) { return bearerToken.substring(7); } return null; } }
SecurityConfig 配置
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http, ServerAuthenticationEntryPoint serverAuthenticationEntryPoint, ServerAccessDeniedHandler serverAccessDeniedHandler, ReactiveAuthenticationManager reactiveAuthenticationManager, ServerAuthenticationConverter serverAuthenticationConverter, ServerAuthenticationSuccessHandler successHandler, ServerAuthenticationFailureHandler failureHandler, JwtValidationWebFilter jwtValidationWebFilter) { AuthenticationWebFilter authenticationWebFilter = authenticationWebFilter( reactiveAuthenticationManager, serverAuthenticationConverter, successHandler, failureHandler); SecurityWebFilterChain chain = http .csrf(ServerHttpSecurity.CsrfSpec::disable) .formLogin(ServerHttpSecurity.FormLoginSpec::disable) .httpBasic(ServerHttpSecurity.HttpBasicSpec::disable) .authenticationManager(reactiveAuthenticationManager) .securityContextRepository(NoOpServerSecurityContextRepository.getInstance()) .logout(ServerHttpSecurity.LogoutSpec::disable) .authorizeExchange(exchanges -> exchanges .pathMatchers("/api/auth/**").permitAll() .pathMatchers("/api/sys-user/**").hasAnyAuthority("PERM_ABC") .anyExchange().authenticated() ) .addFilterBefore(jwtValidationWebFilter, SecurityWebFiltersOrder.AUTHENTICATION) .addFilterAt(authenticationWebFilter, SecurityWebFiltersOrder.AUTHENTICATION) .exceptionHandling(exceptionHandlingSpec -> exceptionHandlingSpec .accessDeniedHandler(serverAccessDeniedHandler) .authenticationEntryPoint(serverAuthenticationEntryPoint) ) .build(); return chain; }
异常信息
org.springframework.security.authentication.AuthenticationCredentialsNotFoundException: Not Authenticated at org.springframework.security.web.server.authorization.ExceptionTranslationWebFilter.commenceAuthentication(ExceptionTranslationWebFilter.java:96) ~[spring-security-web-6.3.3.jar:6.3.3] Suppressed: org.springframework.security.access.AccessDeniedException: Access Denied at org.springframework.security.authorization.ReactiveAuthorizationManager.lambda$verify$0(ReactiveAuthorizationManager.java:53) ~[spring-security-core-6.3.3.jar:6.3.3] Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException: Assembly trace from producer [reactor.core.publisher.MonoError] : reactor.core.publisher.Mono.error(Mono.java:299) org.springframework.security.authorization.ReactiveAuthorizationManager.lambda$verify$0(ReactiveAuthorizationManager.java:53) Caused by: org.springframework.security.authentication.InsufficientAuthenticationException: Full authentication is required to access this resource .....
核心问题点
定位到ExceptionTranslationWebFilter的逻辑:
chain.filter(exchange) .onErrorResume(AccessDeniedException.class, (denied) -> exchange.getPrincipal() .filter((principal) -> (!(principal instanceof Authentication) || (principal instanceof Authentication && (this.authenticationTrustResolver.isAuthenticated((Authentication) principal))))) .switchIfEmpty(commenceAuthentication(exchange, new InsufficientAuthenticationException( "Full authentication is required to access this resource"))) .flatMap((principal) -> this.accessDeniedHandler.handle(exchange, denied)) .then());
预期AccessDeniedException会进入flatMap的权限拒绝处理,但实际进入了switchIfEmpty分支,抛出未认证异常,怀疑上下文设置存在问题。
问题分析与解决方案
根源
- SecurityContextRepository 配置错误:使用
NoOpServerSecurityContextRepository不会保存SecurityContext,后续权限过滤器无法从上下文获取已设置的Authentication。 - 冗余的AuthenticationWebFilter:同时添加了自定义Jwt过滤器和默认认证过滤器,后者可能覆盖或清除你设置的上下文。
- Authentication 对象状态:如果
authentication.isAuthenticated()返回false,AuthenticationTrustResolver会判定为未认证,触发未认证分支。
修复步骤
- 替换SecurityContextRepository:用
WebSessionServerSecurityContextRepository替代NoOpServerSecurityContextRepository,确保上下文能被后续过滤器获取:.securityContextRepository(new WebSessionServerSecurityContextRepository()) - 移除冗余的AuthenticationWebFilter:自定义Jwt过滤器已完成认证上下文设置,不需要重复的认证过滤器:
// 移除以下两行 // AuthenticationWebFilter authenticationWebFilter = authenticationWebFilter(...); // .addFilterAt(authenticationWebFilter, SecurityWebFiltersOrder.AUTHENTICATION) - 确保Authentication已认证:创建Authentication对象时设置
authenticated = true,例如使用UsernamePasswordAuthenticationToken的三参数构造:Authentication authentication = new UsernamePasswordAuthenticationToken( userDetails, null, userDetails.getAuthorities());
修改后的SecurityConfig关键片段:
SecurityWebFilterChain chain = http .csrf(ServerHttpSecurity.CsrfSpec::disable) .formLogin(ServerHttpSecurity.FormLoginSpec::disable) .httpBasic(ServerHttpSecurity.HttpBasicSpec::disable) .securityContextRepository(new WebSessionServerSecurityContextRepository()) .logout(ServerHttpSecurity.LogoutSpec::disable) .authorizeExchange(exchanges -> exchanges .pathMatchers("/api/auth/**").permitAll() .pathMatchers("/api/sys-user/**").hasAnyAuthority("PERM_ABC") .anyExchange().authenticated() ) .addFilterBefore(jwtValidationWebFilter, SecurityWebFiltersOrder.AUTHENTICATION) .exceptionHandling(exceptionHandlingSpec -> exceptionHandlingSpec .accessDeniedHandler(serverAccessDeniedHandler) .authenticationEntryPoint(serverAuthenticationEntryPoint) ) .build();
内容的提问来源于stack exchange,提问作者L Jackie
相关产品推荐
相关产品推荐

