I want to throw AuthenticationException when BasicAuthenticationFilter authentication fails and handle it uniformly, returning json.
But ExceptionTranslationFilter not work.
In ExceptionTranslationFilter.doFilter() it will check that the response.isCommitted() and throw "Unable to handle the Spring Security Exception because the response is already committed".
So this is normal? How do I unify the handling of these AuthenticationExceptions?
dependencies : spring-security:6.1.5.
SecurityFilterChain:
@Bean
public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
return httpSecurity
.cors(AbstractHttpConfigurer::disable)
.csrf(AbstractHttpConfigurer::disable)
.sessionManagement(sessionManagement ->
sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.addFilter(new JWTAuthenticationFilter(authenticationConfiguration.getAuthenticationManager(), objectMapper, whitelist))
.addFilterAfter(new DecryptRequestFilter(), JWTAuthenticationFilter.class)
.addFilterBefore(new ExceptionTranslationFilter(unauthorizedEntryPoint), JWTAuthenticationFilter.class)
.build();
}
doFilterInternal() in JWTAuthenticationFilter extends BasicAuthenticationFilter :
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
String path = request.getRequestURI();
AntPathMatcher antPathMatcher = new AntPathMatcher();
if (whitelist.getWhitelist().stream().anyMatch(s -> antPathMatcher.match(s, path))) {
chain.doFilter(request, response);
}
String token = request.getHeader(ConstantKey.HEADER_TOKEN_KEY);
if(StringUtils.isEmpty(token)) {
throw new AuthenticationCredentialsNotFoundException("token not found");
}
String subject = Jwts.parser().setSigningKey(ConstantKey.TOKEN_SIGN_KEY)
.parseClaimsJws(token)
.getBody()
.getSubject();
if (StringUtils.isEmpty(subject)) {
throw new AuthenticationCredentialsNotFoundException("token is invalid");
}
AuthSubject authSubject;
try {
authSubject = objectMapper.readValue(subject, AuthSubject.class);
}catch (Exception e) {
throw new AuthenticationServiceException("read auth subject error", e);
}
Set<SimpleGrantedAuthority> simpleGrantedAuthorities = authSubject.getRoles().stream()
.map(SimpleGrantedAuthority::new).collect(Collectors.toSet());
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =
new UsernamePasswordAuthenticationToken(authSubject.getUsername(), null, simpleGrantedAuthorities);
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
request.setAttribute(AuthSubject.Fields.username, authSubject.getUsername());
request.setAttribute(AuthSubject.Fields.roles, authSubject.getUserId());
chain.doFilter(request, response);
}
commence() in UnauthorizedEntryPoint implements AuthenticationEntryPoint:
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException {
response.setStatus(HttpStatus.UNAUTHORIZED.value());
response.setContentType(ContentType.APPLICATION_JSON.toString());
response.getWriter().write(objectMapper.writeValueAsString(
GenericRestResponse.builder()
.status(YesOrNo.NO.getCode())
.message(authException.getMessage())
.build()
)
);
}
I tried to use AuthenticationFailureHandler but still can't catch the AuthenticationException.
I used Spring Authorization Server to implement the oauth2 authentication server.
Reference youlai-mall project to implement the password authentication method.