我有一个启用了反应式安全性的 springboot webflux 项目由于某种原因,该项目似乎调用了 ReactiveAuthenticationManager 的身份验证方法两次(一次在默认处理程序中,一次请求到达控制器)。
这是我的示例代码 WebSecurityConfig.class
@EnableWebFluxSecurity
@EnableReactiveMethodSecurity
public class WebSecurityConfig {
static final String[] AUTH_WHITELIST = {
"/swagger-resources/**",
"/swagger-ui.html",
"/v2/api-docs",
"/v3/api-docs",
"/webjars/**",
"/swagger-ui/**",
"/v1/healthcheck"
};
private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
private final AuthenticationManager authenticationManager;
private final SecurityContextRepository securityContextRepository;
public WebSecurityConfig(
JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint,
AuthenticationManager authenticationManager,
SecurityContextRepository securityContextRepository) {
this.jwtAuthenticationEntryPoint = jwtAuthenticationEntryPoint;
this.authenticationManager = authenticationManager;
this.securityContextRepository = securityContextRepository;
}
@Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
return http.securityMatcher(
new NegatedServerWebExchangeMatcher(
ServerWebExchangeMatchers.pathMatchers(AUTH_WHITELIST)))
.exceptionHandling()
.authenticationEntryPoint(jwtAuthenticationEntryPoint)
.accessDeniedHandler(new HttpStatusServerAccessDeniedHandler(HttpStatus.BAD_REQUEST))
.and()
.csrf()
.disable()
.formLogin()
.disable()
.httpBasic()
.disable()
.logout()
.disable()
.authenticationManager(authenticationManager)
.securityContextRepository(securityContextRepository)
.authorizeExchange()
.pathMatchers(HttpMethod.OPTIONS)
.permitAll()
.anyExchange()
.authenticated()
.and()
.build();
}
ServerSecurityContextRepository.class
@Slf4j
@Component
public class SecurityContextRepository implements ServerSecurityContextRepository {
private final AuthenticationService authenticationService;
private final AuthenticationManager authenticationManager;
public SecurityContextRepository(
AuthenticationService authenticationService, AuthenticationManager authenticationManager) {
this.authenticationService = authenticationService;
this.authenticationManager = authenticationManager;
}
@Override
public Mono<Void> save(ServerWebExchange swe, SecurityContext sc) {
throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public Mono<SecurityContext> load(ServerWebExchange swe) {
ServerHttpRequest request = swe.getRequest();
log.info("Parsing Authorization token from Request");
AuthToken authToken =
authenticationService.parseRequestToken(authenticationService.getHeaders(request));
Authentication auth = new UsernamePasswordAuthenticationToken(authToken, null);
return this.authenticationManager
.authenticate(auth)
.map(authentication -> (SecurityContext) new SecurityContextImpl(authentication))
ReactiveAuthenticationManager.class
@Slf4j
@Component
public class AuthenticationManager implements ReactiveAuthenticationManager {
final AuthenticationService authenticationService;
@Value("${app.auth_enable}")
private boolean isAuthEnabled;
public AuthenticationManager(AuthenticationService authenticationService) {
this.authenticationService = authenticationService;
}
@Override
public Mono<Authentication> authenticate(Authentication authentication) {
AuthToken token = (AuthToken) authentication.getPrincipal();
if (Objects.isNull(token)) {
log.error("Jwt token not provided");
return Mono.error(new AuthorizeException("Jwt token not provided"));
}
if (isAuthEnabled) {
return authenticationService
.verifyRequestToken(token)
.map(
aBoolean -> {
if (!aBoolean) {
log.warn("Jwt token not valid");
return null;
}
log.info("Jwt token is valid");
return new UsernamePasswordAuthenticationToken(token, null, null);
});
}
return Mono.just(new UsernamePasswordAuthenticationToken(token, null, null));
}
JwtAuthenticationEntryPoint.class
@Slf4j
@Component
public class JwtAuthenticationEntryPoint extends HttpBasicServerAuthenticationEntryPoint
implements Serializable {
private final ObjectMapper objectMapper;
public JwtAuthenticationEntryPoint(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
}
@Override
public Mono<Void> commence(ServerWebExchange exchange, AuthenticationException ex) {
log.info("Commencing AuthenticationEntryPoint...");
ServerHttpResponse response = exchange.getResponse();
JwtAuthenticationError error =
new JwtAuthenticationError(JwtExceptionContext.getExceptionContext());
JwtExceptionContext.clearExceptionContext();
byte[] bytes = new byte[0];
try {
bytes = objectMapper.writeValueAsString(error).getBytes(StandardCharsets.UTF_8);
} catch (JsonProcessingException e) {
log.error("JsonProcessingException on commence function : {}", e.getMessage(), e);
}
DataBuffer buffer = response.bufferFactory().wrap(bytes);
response.setStatusCode(HttpStatus.valueOf(Integer.parseInt(error.getStatusCode())));
log.warn(
"Authentication Failed: {} -> {}",
value("errorMsg", error),
keyValue(
AppConstants.STATUS_CODE, HttpStatus.valueOf(Integer.parseInt(error.getStatusCode()))));
return response.writeWith(Mono.just(buffer));
}
示例日志
2022-01-18 15:30:25.203 DEBUG 9308 --- [cTaskExecutor-1] o.s.web.client.RestTemplate : Reading to [java.lang.String] as "application/json"
2022-01-18 15:30:25.209 INFO 9308 --- [cTaskExecutor-1] c.s.p.r.n.h.s.AuthenticationServiceImpl : Validation Response 200
2022-01-18 15:30:25.209 INFO 9308 --- [oundedElastic-1] c.s.p.r.n.h.s.AuthenticationManager : **Jwt token is valid**
2022-01-18 15:30:25.211 DEBUG 9308 --- [oundedElastic-1] o.s.s.w.s.a.AuthorizationWebFilter : Authorization successful
2022-01-18 15:30:25.217 DEBUG 9308 --- [oundedElastic-1] s.w.r.r.m.a.RequestMappingHandlerMapping : [1fc32c7d-1, L:/0:0:0:0:0:0:0:1:8080 - R:/0:0:0:0:0:0:0:1:54225] Mapped to MyController#getCount(Boolean, String, UsernamePasswordAuthenticationToken)
2022-01-18 15:30:25.255 INFO 9308 --- [oundedElastic-1] c.s.p.r.n.h.s.AuthenticationServiceImpl : Validation Response 200
2022-01-18 15:30:25.256 INFO 9308 --- [oundedElastic-2] c.s.p.r.n.h.s.AuthenticationManager : **Jwt token is valid**
任何建议或指针表示赞赏。提前致谢