J'ai une API HTTP, protégée par Spring Security et JWT.
Je reçois un 401 lorsque j'essaie d'accéder à une ressource protégée.
Je reçois la ressource si je suis authentifié (JWT est valide) et j'ai le bon rôle. La ressource est protégée par @PreAuthorize("hasRole('USER')")
. Le problème que j'ai, c'est que quand je n'ai pas le bon rôle, je voudrais retourner un 403 (dans le code suivant, c'est un 401 pour le plaisir de tester). Mais sachez que je reçois un 500 en raison de l'exception AccessDeniedException qui est levée lorsque le rôle est incorrect. Ce qui est bizarre, c'est que ça passe à mon code personnalisé JwtAccessDeniedHandler mais la réponse est déjà validée (isCommitted() == true) donc quand j'essaie de définir le statut, il ne fait rien.
Avez-vous des idées sur ce qui pourrait être mal configuré ou manquant?Spring Security Access Denied Handler est appelé lorsque le HttpServletResponse est déjà validé
Config:
@Slf4j
@EnableWebSecurity
@EnableGlobalMethodSecurity(
prePostEnabled = true,
securedEnabled = true,
jsr250Enabled = true
)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private ObjectMapper objectMapper;
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.addFilterBefore(
jwtAuthenticationFilter(joseHelper(jsonWebKey())),
UsernamePasswordAuthenticationFilter.class)
.authorizeRequests()
.antMatchers("/auth/**").permitAll()
.anyRequest().authenticated()
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.csrf().disable()
.exceptionHandling()
.authenticationEntryPoint(new JwtAuthenticationEntryPoint())
.accessDeniedHandler(new JwtAccessDeniedHandler());
}
@Bean
public JwtAuthenticationFilter jwtAuthenticationFilter(JoseHelper joseHelper) {
return new JwtAuthenticationFilter(joseHelper);
}
@Bean
public JoseHelper joseHelper(PublicJsonWebKey key) {
return new JoseHelper(key);
}
@Bean
public PublicJsonWebKey jsonWebKey() throws IOException, JoseException {
return RsaJwkGenerator.generateJwk(2048);
}
private void sendUnauthorized(HttpServletResponse httpServletResponse) throws IOException {
httpServletResponse.setContentType("application/json");
httpServletResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
ApiError apiError = ApiError.builder()
.code(HttpStatus.UNAUTHORIZED.name())
.message(HttpStatus.UNAUTHORIZED.getReasonPhrase())
.httpStatus(HttpStatus.UNAUTHORIZED)
.build();
httpServletResponse.getWriter().print(objectMapper.writeValueAsString(apiError));
}
private class JwtAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
log.info("accessDeniedHandler", e);
sendUnauthorized(httpServletResponse);
}
}
private class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse,
AuthenticationException e) throws IOException, ServletException {
sendUnauthorized(httpServletResponse);
}
}
}
Filtre:
@Slf4j
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private static final String BEARER = "Bearer ";
private JoseHelper joseHelper;
@Autowired
public JwtAuthenticationFilter(JoseHelper joseHelper) {
this.joseHelper = joseHelper;
}
@Override
protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
String header = httpServletRequest.getHeader("Authorization");
if (header == null || !header.startsWith(BEARER)) {
log.error("JWT token is not valid");
filterChain.doFilter(httpServletRequest, httpServletResponse);
return;
}
final String encryptedToken = header.substring(BEARER.length());
try {
final String decryptedJwt = joseHelper.decryptJwt(encryptedToken);
final String verifiedJwt = joseHelper.verifyJwt(decryptedJwt);
final JwtClaims jwtClaims = joseHelper.parse(verifiedJwt);
List<SimpleGrantedAuthority> authorities = jwtClaims.getStringListClaimValue("userRoles")
.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList());
JwtAuthenticationToken jwtAuthenticationToken = new JwtAuthenticationToken(jwtClaims, null, authorities);
SecurityContextHolder.getContext().setAuthentication(jwtAuthenticationToken);
filterChain.doFilter(httpServletRequest, httpServletResponse);
} catch (JoseException | InvalidJwtException | MalformedClaimException e) {
log.error("JWT token is not valid", e);
filterChain.doFilter(httpServletRequest, httpServletResponse);
}
}
}