Spring Boot/Spring Security AuthenticationEntryPoint not getting executed
Solution 1
The difference between AuthenticationEntryPoint
and AuthenticationFailureHandler
is that the former is used to "tell" unauthenticated users where to authenticate, for example, by redirecting them to a login form. The latter is used to handle bad login attempts.
Your AuthenticationEntryPoint
is likely not called because you're throwing an exception. It would be called if a user tries to access an endpoint that requires authentication and you don't throw anything. In the case where no credentials are present, it's enough to not authenticate the user, you don't need to throw an exception.
If you're creating a application with JWT authentication, you probably don't want to redirect the user anywhere so you can just use the org.springframework.security.web.authentication.HttpStatusEntryPoint
or an entry point like yours to return a status code.
Solution 2
This is my code to implement AuthenticationEntryPoint and AccessDeniedHandler with Spring Boot / JWT. I hope it will be of help to anyone.
AuthenticationEntryPoint
@Component
public class AuthenticationEntryPointJwt implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException authenticationException) throws IOException {
httpServletResponse.setContentType(MediaType.APPLICATION_JSON_VALUE);
httpServletResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
final Map<String, Object> body = new HashMap<>();
body.put("code", HttpServletResponse.SC_UNAUTHORIZED);
body.put("payload", "You need to login first in order to perform this action.");
final ObjectMapper mapper = new ObjectMapper();
mapper.writeValue(httpServletResponse.getOutputStream(), body);
}
}
AccessDeniedHandler
@Component
public class AccessDeniedHandlerJwt implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
httpServletResponse.setContentType(MediaType.APPLICATION_JSON_VALUE);
httpServletResponse.setStatus(HttpServletResponse.SC_FORBIDDEN);
final Map<String, Object> body = new HashMap<>();
body.put("code", HttpServletResponse.SC_FORBIDDEN);
body.put("payload", "You don't have required role to perform this action.");
final ObjectMapper mapper = new ObjectMapper();
mapper.writeValue(httpServletResponse.getOutputStream(), body);
}
}
WebSecurityConfigurerAdapter
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private Environment env;
@Autowired
private SecurityUserDetailsService securityUserDetailsService;
@Autowired
private SecurityRequestFilter securityRequestFilter;
@Autowired
private AuthenticationEntryPointJwt authenticationEntryPointJwt;
@Autowired
private AccessDeniedHandlerJwt accessDeniedHandlerJwt;
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(securityUserDetailsService).passwordEncoder(passwordEncoder());
}
@Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
if (Boolean.parseBoolean(env.getRequiredProperty("security.disable.csrf")))
httpSecurity.csrf().disable();
httpSecurity
.httpBasic().disable()
.formLogin().disable()
.authorizeRequests()
.antMatchers(env.getRequiredProperty("security.uri.white-list").split(",")).permitAll()
.anyRequest().authenticated().and()
.exceptionHandling().authenticationEntryPoint(authenticationEntryPointJwt).and()
.exceptionHandling().accessDeniedHandler(accessDeniedHandlerJwt).and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
httpSecurity.addFilterBefore(securityRequestFilter, UsernamePasswordAuthenticationFilter.class);
}
}
You can check the complete code on my GitHub
Related videos on Youtube
SME
Updated on September 15, 2022Comments
-
SME almost 2 years
I have a created a class JwtAuthenticationFilter that includes this method:
@Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { Authentication authentication = null; if(hasJsonToken(request)) { JwtAuthenticationToken jwtAuthenticationToken = new JwtAuthenticationToken(getJsonToken(request)); authentication = getAuthenticationManager().authenticate(jwtAuthenticationToken); } else { throw new AuthenticationCredentialsNotFoundException(AUTHENTICATION_CREDENTIALS_NOT_FOUND_MSG); } return authentication; }
If no JWT has been supplied an AuthenticationCredentialsNotFoundException is thrown. I would expect this to trigger the commence method in my AuthenticationEntryPoint - which looks like this:
@Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException { response.sendError(HttpStatus.UNAUTHORIZED.value(),HttpStatus.UNAUTHORIZED.getReasonPhrase()); }
The commence method is not being call. This in my spring security config (or part of it):
@Bean public RestAuthenticationEntryPoint restAuthenticationEntryPoint() { return new RestAuthenticationEntryPoint(); } protected void configure(HttpSecurity http) throws Exception { http.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class) .exceptionHandling().authenticationEntryPoint(restAuthenticationEntryPoint()).and() .csrf().disable() .authorizeRequests().antMatchers(AUTHORISED_SERVICE_REQUESTS_ANT_MATCHER).authenticated() .anyRequest().permitAll(); }
Not sure what I'done wrong here and I'm hoping someone point it out to me. Thanks
My SecurityConfig class extends WebSecurityConfigurerAdapter and is annotated with @Configuration and @EnableWebSecurity
@Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { ... }
I am using spring boot.
So... eventually I got the behaviour I wanted by creating a custom AuthenticationFailureHandler and registering it in my Authentication F.ilter
jwtAuthenticationFilter.setAuthenticationFailureHandler(new JwtAuthenticationFailureHandler());
My question now is, is this the right thing to do and what is the difference between an AuthenticationEntryPoint and an AuthenticationFailureHandler?
-
silentsudo about 5 yearsCorrect, If you are just building resource server with
JWT
Authentication then useAuthenticationEntryPoint
implementer and customize default exception message which is similar to <oauth2>...somexml...</oauth2>