Spring Boot 2 - 403 вместо 401 в реализации JWT Spring Security на основе фильтров

Я пытаюсь сделать мой Spring Boot 1.5.x REST API проект 2.xx-совместимым, не ломая много кода. Я застрял в реализации JWT Spring Security на основе фильтров, которую я использовал:

Чтобы аутентифицировать учетные данные через конечную точку "/login", я использовал UsernamePasswordAuthenticationFilter следующее:

public class JWTAuthenticationFilter extends UsernamePasswordAuthenticationFilter {

    private AuthenticationManager authenticationManager;

    private JWTUtil jwtUtil;

    public JWTAuthenticationFilter(AuthenticationManager authenticationManager, JWTUtil jwtUtil) {
        this.authenticationManager = authenticationManager;
        this.jwtUtil = jwtUtil;
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest req,
                                                HttpServletResponse res) throws AuthenticationException {

        try {
            CredenciaisDTO creds = new ObjectMapper().readValue(req.getInputStream(), CredenciaisDTO.class);
            UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(creds.getEmail(), creds.getSenha(), new ArrayList<>());
            Authentication auth = authenticationManager.authenticate(authToken);
            return auth;
        }
        catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    protected void successfulAuthentication(HttpServletRequest req,
                                            HttpServletResponse res,
                                            FilterChain chain,
                                            Authentication auth) throws IOException, ServletException {
        String username = ((UserSS) auth.getPrincipal()).getUsername();
        String token = jwtUtil.generateToken(username);
        res.addHeader("Authorization", "Bearer " + token);
        res.addHeader("access-control-expose-headers", "Authorization");
    }
}

Кроме того, чтобы авторизовать запросы токен-маркеров, я использовал для расширения BasicAuthenticationFilter следующее:

public class JWTAuthorizationFilter extends BasicAuthenticationFilter {

    private JWTUtil jwtUtil;

    private UserDetailsService userDetailsService;

    public JWTAuthorizationFilter(AuthenticationManager authenticationManager, JWTUtil jwtUtil, UserDetailsService userDetailsService) {
        super(authenticationManager);
        this.jwtUtil = jwtUtil;
        this.userDetailsService = userDetailsService;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain chain) throws IOException, ServletException {

        String header = request.getHeader("Authorization");
        if (header != null && header.startsWith("Bearer ")) {
            UsernamePasswordAuthenticationToken auth = getAuthentication(header.substring(7));
            if (auth != null) {
                SecurityContextHolder.getContext().setAuthentication(auth);
            }
        }
        chain.doFilter(request, response);
    }

    private UsernamePasswordAuthenticationToken getAuthentication(String token) {
        if (jwtUtil.isValid(token)) {
            String username = jwtUtil.getUsername(token);
            UserDetails user = userDetailsService.loadUserByUsername(username);
            return new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities());
        }
        return null;
    }
}

Все заработало как положено:

  • Запросы / вход в систему с неверными учетными данными, которые использовались для возврата 401 со стандартным объектом "Неавторизованная аутентификация / сбой аутентификации: неверные учетные данные".

  • Запросы на основе токенов не авторизованы BasicAuthenticationFilterиспользуется для возврата 403 со стандартным объектом "Запрещено / Доступ запрещен".

Однако, если я использую этот код в проекте Spring Boot 2.0.0, запросы do /login возвращали 403 с пустым ответом.

Этот пост рекомендуется включать http.exceptionHandling().authenticationEntryPoint(new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED)); в configure метод из класса конфигурации безопасности. Таким образом, я мог бы получить 401 для запросов / входа в систему, но он по-прежнему возвращает пустой запрос (вместо стандартного объекта ошибки "Неавторизованный"). Кроме того, теперь все запросы не разрешены BasicAuthenticationFilter также возвращают 401 с ответом пустого тела (в то время как правильный должен возвращать 403 с этим стандартным "Запрещенным" объектом ошибки внутри тела).

Как я могу вернуть то желаемое поведение, которое у меня было раньше?

1 ответ

Понял. Ответ отсюда не был необходим. Чтобы решить мою проблему, я реализовал AuthenticationFailureHandler:

public class JWTAuthenticationFailureHandler implements AuthenticationFailureHandler {

    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception)
            throws IOException, ServletException {
        response.setStatus(401);
        response.setContentType("application/json"); 
        response.getWriter().append(json());
    }

    private String json() {
        long date = new Date().getTime();
        return "{\"timestamp\": " + date + ", "
            + "\"status\": 401, "
            + "\"error\": \"Unauthorized\", "
            + "\"message\": \"Authentication failed: bad credentials\", "
            + "\"path\": \"/login\"}";
    }
}

А затем ввести его в UsernamePasswordAuthenticationFilter:

public class JWTAuthenticationFilter extends UsernamePasswordAuthenticationFilter {

    (...)

    public JWTAuthenticationFilter(AuthenticationManager authenticationManager, JWTUtil jwtUtil) {
        super.setAuthenticationFailureHandler(new JWTAuthenticationFailureHandler());
        (...)
Другие вопросы по тегам