Spring Boot/Spring Security AuthenticationEntryPoint not getting executed

13,391

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

https://github.com/JonathanM2ndoza/Nginx-Docker-Spring-Boot/tree/master/security/src/main/java/com/jmendoza/wallet/security

Share:
13,391

Related videos on Youtube

SME
Author by

SME

Updated on September 15, 2022

Comments

  • SME
    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
    silentsudo about 5 years
    Correct, If you are just building resource server with JWT Authentication then use AuthenticationEntryPoint implementer and customize default exception message which is similar to <oauth2>...somexml...</oauth2>