【发布时间】:2021-06-21 04:36:41
【问题描述】:
我正在将响应式安全与 JWT 一起使用。这个构建工作,虽然当提供无效凭据时它返回 json:
{
"timestamp": "2021-03-24T12:44:44.540+00:00",
"path": "/auth/login",
"status": 500,
"error": "Internal Server Error",
"message": "Invalid Credentials",
"requestId": "cfa7b741-1"
}
并在控制台中打印堆栈跟踪:
org.springframework.security.authentication.BadCredentialsException: Invalid Credentials
at org.springframework.security.authentication.AbstractUserDetailsReactiveAuthenticationManager.lambda$authenticate$1(AbstractUserDetailsReactiveAuthenticationManager.java:99) ~[spring-security-core-5.4.5.jar:5.4.5]
这里是登录控制器:
@RestController
@RequestMapping("/auth")
@RequiredArgsConstructor
public class AuthAPI {
private final JwtTokenProvider tokenProvider;
private final ReactiveAuthenticationManager authenticationManager;
@PostMapping("/login")
public Mono<ResponseEntity> login(@RequestBody Mono<AuthRequest> authRequest) {
return authRequest
.flatMap(login -> authenticationManager
.authenticate(new UsernamePasswordAuthenticationToken(login.getUsername(), login.getPassword()))
.map(tokenProvider::createToken)
)
.map(jwt -> {
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.add(HttpHeaders.AUTHORIZATION, "Bearer " + jwt);
var tokenBody = Map.of("token", jwt);
return new ResponseEntity<>(tokenBody, httpHeaders, HttpStatus.OK);
}
);
}
}
安全配置:
@Configuration
public class SecurityConfig {
@Bean
SecurityWebFilterChain springWebFilterChain(ServerHttpSecurity http,
JwtTokenProvider tokenProvider,
ReactiveAuthenticationManager reactiveAuthenticationManager) {
return http
.csrf(ServerHttpSecurity.CsrfSpec::disable)
.httpBasic(ServerHttpSecurity.HttpBasicSpec::disable)
.authenticationManager(reactiveAuthenticationManager)
.securityContextRepository(NoOpServerSecurityContextRepository.getInstance())
.authorizeExchange(it -> it
.pathMatchers("/me").authenticated()
.pathMatchers("/users/{user}/**").access(this::currentUserMatchesPath)
.anyExchange().permitAll()
)
.addFilterAt(new JwtTokenAuthenticationFilter(tokenProvider), SecurityWebFiltersOrder.HTTP_BASIC)
.build();
}
private Mono<AuthorizationDecision> currentUserMatchesPath(Mono<Authentication> authentication,
AuthorizationContext context) {
return authentication
.map(a -> context.getVariables().get("user").equals(a.getName()))
.map(AuthorizationDecision::new);
}
@Bean
public ReactiveUserDetailsService userDetailsService(UserRepository users) {
return username -> users.findByUsername(username)
.map(u -> User
.withUsername(u.getUsername()).password(u.getPasswordHash())
.authorities(u.getRoles().toArray(new String[0]))
.accountExpired(!u.getIsActive())
.credentialsExpired(!u.getIsActive())
.disabled(!u.getIsActive())
.accountLocked(!u.getIsActive())
.build()
);
}
@Bean
public ReactiveAuthenticationManager reactiveAuthenticationManager(ReactiveUserDetailsService userDetailsService,
PasswordEncoder passwordEncoder) {
var authenticationManager = new UserDetailsRepositoryReactiveAuthenticationManager(userDetailsService);
authenticationManager.setPasswordEncoder(passwordEncoder);
return authenticationManager;
}
}
JWT 过滤器:
@RequiredArgsConstructor
public class JwtTokenAuthenticationFilter implements WebFilter {
public static final String HEADER_PREFIX = "Bearer ";
private final JwtTokenProvider tokenProvider;
@Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
String token = resolveToken(exchange.getRequest());
if (StringUtils.hasText(token) && this.tokenProvider.validateToken(token)) {
Authentication authentication = this.tokenProvider.getAuthentication(token);
return chain.filter(exchange)
.contextWrite(ReactiveSecurityContextHolder.withAuthentication(authentication));
}
return chain.filter(exchange);
}
private String resolveToken(ServerHttpRequest request) {
String bearerToken = request.getHeaders().getFirst(HttpHeaders.AUTHORIZATION);
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith(HEADER_PREFIX)) {
return bearerToken.substring(7);
}
return null;
}
}
我想收到正确的错误代码并编写自己的消息,但我找不到配置它的方法。
我尝试添加
.exceptionHandling()
.authenticationEntryPoint()
.accessDeniedHandler()
带有异常抛出器,但它不起作用。
提前谢谢你!
【问题讨论】:
-
当 spring 已经实现了可以自定义的 jwt 过滤器时,为什么还要自定义 JWT 过滤器?您还选择了编写自定义登录和自定义 jwt 过滤器,因此您基本上选择了退出标准 spring 安全配置及其所有好处。这意味着您需要处理自己的错误。如果登录失败,您需要处理返回并构建您自己的自定义响应以返回给客户端。
-
您正在调用
authenticate,我的猜测是reactiveAuthenticationManager返回一个您无法处理的错误,因为您直接调用.map(tokenProvider::createToken)只是公然假设登录正常,然后您创建一个令牌。 -
@Toerktumlare 我不是 Spring Security 专家,并且正在学习教程,因为文档很短,并且不包括带有 JWT 的 Webflux。此外,出于某种原因,Spring Security 正在保护登录后的每条路径,因此无法访问
/或/register,因此我寻找了自定义解决方案。 -
@Toerktumlare 是的,这就是我写这个问题的原因——我需要处理错误。尽管我找到了将在下面发布的解决方案,但您的观点也很好,因为它给了我一个想法。
标签: java spring exception spring-security spring-webflux