I'm trying to make my Spring Boot 1.5.x REST API project 2.x.x-compatible, without breaking lots of code. I'm stuck in a filter-based JWT Spring Security implementation I used to use:
In order to authenticate credentials through "/login" endpoint, I used to extend UsernamePasswordAuthenticationFilter
as follows:
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");
}
}
Also, in order to authorize beared-token requests, I used to extend BasicAuthenticationFilter
as follows:
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;
}
}
Everything worked as expected:
Requests to /login with bad credentials used to return 401 with a standand "Unauthorized/Authentication failed: bad credentials" object.
Beared-token requests not authorized by BasicAuthenticationFilter
used to return 403 with a standard "Forbidden/Access denied" object.
However, if I use that code in a Spring Boot 2.0.0 project, requests do /login were returning 403 with empty-body response.
This post adviced to include http.exceptionHandling().authenticationEntryPoint(new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED));
in configure
method from the security configuration class. That way I could get 401 for /login requests, but it's still returning an empty-body response (instead of that standard "Unauthorized" error object). Besides, now all requests not authorized by BasicAuthenticationFilter
are also returning 401 with empty-body response (while the correct should be returning 403 with that standard "Forbidden" error object inside body).
How can I get back that desirable behavior I had before?
Got it. The answer from here was not necessary. In order to solve my problem I've implemented an 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\"}";
}
}
And then inject it in UsernamePasswordAuthenticationFilter
:
public class JWTAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
(...)
public JWTAuthenticationFilter(AuthenticationManager authenticationManager, JWTUtil jwtUtil) {
super.setAuthenticationFailureHandler(new JWTAuthenticationFailureHandler());
(...)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With