【问题标题】:Reactive Spring Security throws 500 and console stacktrace instead of 401反应式 Spring Security 抛出 500 和控制台堆栈跟踪而不是 401
【发布时间】: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


【解决方案1】:

因此解决方案是创建新的身份验证管理器类,扩展内置的一个,如下所示:

public class CustomReactiveAuthManager extends UserDetailsRepositoryReactiveAuthenticationManager {

    public CustomReactiveAuthManager(ReactiveUserDetailsService userDetailsService) {
        super(userDetailsService);
    }

    @Override
    public Mono<Authentication> authenticate(Authentication authentication) {
        return super
                .authenticate(authentication)
                ;
    }
}

并在安全配置中使用它var authenticationManager = new CustomReactiveAuthManager(userDetailsService);

或者正如@Toerktumlare 所说,在 authAPI 中提供令牌之前处理错误:

                        .authenticate(new UsernamePasswordAuthenticationToken(login.getUsername(), login.getPassword()))
                        .onErrorMap(BadCredentialsException.class, err ->
                                CustomError.authError())
                        .map(tokenProvider::createToken)

【讨论】:

    猜你喜欢
    • 2020-11-04
    • 1970-01-01
    • 2017-10-24
    • 1970-01-01
    • 1970-01-01
    • 2011-12-19
    • 2015-06-07
    • 2011-12-04
    • 1970-01-01
    相关资源
    最近更新 更多