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());
(...)