简体   繁体   中英

AuthenticationEntryPoint is never called in Spring Boot

I have a Spring Security implementation for custom token, I tried many ways to implement a custom response for auth exceptions but I could not find the solution, it is never called.

I have this configuration for security:

@Override
protected void configure(final HttpSecurity http) throws Exception {
    http
            .sessionManagement()
            .sessionCreationPolicy(STATELESS)
            .and()
            .exceptionHandling().authenticationEntryPoint(new AuthenticationExceptionHandler())
            // this entry point handles when you request a protected page and you are not yet
            // authenticated
            .defaultAuthenticationEntryPointFor(forbiddenEntryPoint(), PROTECTED_URLS)
            .and()
            .authenticationProvider(provider)
            .addFilterBefore(restAuthenticationFilter(), AnonymousAuthenticationFilter.class)
            .authorizeRequests()
            .requestMatchers(PROTECTED_URLS)
            .authenticated()
            .and()
            .csrf().disable()
            .formLogin().disable()
            .httpBasic().disable()
            .logout().disable()
    ;
}

This is the TokenAuthenticationProvider:

@Override
protected UserDetails retrieveUser(final String username, final 
UsernamePasswordAuthenticationToken authentication) throws 
AuthenticationException {
    final String token = (String) authentication.getCredentials();
    logger.info("Retrieving user details from the token.");
    FirebaseToken decodedToken;
    UserAuth user = new UserAuth();
    try {
        decodedToken = FirebaseAuth.getInstance().verifyIdToken(token);
        user.setId(decodedToken.getUid());
        user.setEmail(decodedToken.getEmail());
        user.setName(decodedToken.getName());
        user.setClaims(decodedToken.getClaims());
    } catch (FirebaseAuthException e) {
        e.printStackTrace();
        throw new CredentialsExpiredException("Fail getting the idUser 
        maybe token expired.");
    }

    return user;
}

I am throwing the CredentialsExpiredException from org.springframework.security.authentication when a Firebase token is invalid, but I still receiving this answer:

{
"timestamp": "2019-01-16T16:51:54.696+0000",
"status": 401,
"error": "Unauthorized",
"message": "Unauthorized",
"path": "/employer"
}

This is the AuthenticationEntryPoint:

@Component
public class AuthenticationExceptionHandler implements 
AuthenticationEntryPoint, Serializable {

@Override
public void commence(HttpServletRequest request, HttpServletResponse 
response, AuthenticationException authException) throws IOException, 
ServletException {

    response.setStatus(HttpStatus.UNAUTHORIZED.value());

    Map<String, Object> error = new HashMap<>();
    error.put("domain", "global");
    error.put("reason", "required");
    error.put("message", "Invalid credentials.");
    ArrayList<Map<String, Object>> errorsList = new ArrayList<>();
    errorsList.add(error);
    Map<String, Object> errors = new HashMap<>();
    errors.put("errors", errorsList);
    errors.put("code", 401);
    errors.put("message", "Invalid credentials.");

    Map<String, Object> data = new HashMap<>();
    data.put("error", errors);

    ObjectMapper mapper = new ObjectMapper();
    String responseMsg = mapper.writeValueAsString(data);
    response.getWriter().write(responseMsg);
   }
}

I solved this problem implementing a AuthenticationFailureHandler that is called by unsuccessfulAuthentication method that was implemented in the AbstractAuthenticationProcessingFilter... This is the config code:

@Override
protected void configure(final HttpSecurity http) throws Exception {
    http
            .sessionManagement()
            .sessionCreationPolicy(STATELESS)
            .and()
            .exceptionHandling()
            // this entry point handles when you request a protected page and you are not yet
            // authenticated
            .defaultAuthenticationEntryPointFor(forbiddenEntryPoint(), PROTECTED_URLS)
            .and()
            .authenticationProvider(provider)
            .addFilterBefore(restAuthenticationFilter(), AnonymousAuthenticationFilter.class)
            .authorizeRequests()
            .requestMatchers(PROTECTED_URLS)
            .authenticated()
            .and()
            .csrf().disable()
            .formLogin().disable()
            .httpBasic().disable()
            .logout().disable()
    ;
}


@Bean
TokenAuthenticationFilter restAuthenticationFilter() throws Exception {
    final TokenAuthenticationFilter filter = new TokenAuthenticationFilter(PROTECTED_URLS);
    filter.setAuthenticationManager(authenticationManager());
    filter.setAuthenticationSuccessHandler(successHandler());
    filter.setAuthenticationFailureHandler(new CustomAuthenticationFailureHandler());
    return filter;
}

This is the AuthenticationFailureHandler:

public class CustomAuthenticationFailureHandler implements AuthenticationFailureHandler {

private ObjectMapper objectMapper = new ObjectMapper();

@Override
public void onAuthenticationFailure(
        HttpServletRequest request,
        HttpServletResponse response,
        AuthenticationException exception)
        throws IOException {

    response.setStatus(HttpStatus.UNAUTHORIZED.value());
    response.setContentType("application/json");

    Map<String, Object> error = new HashMap<>();
    error.put("domain", "global");
    error.put("reason", "required");
    error.put("message", "Invalid credentials.");
    ArrayList<Map<String, Object>> errorsList = new ArrayList<>();
    errorsList.add(error);
    Map<String, Object> errors = new HashMap<>();
    errors.put("errors", errorsList);
    errors.put("code", 401);
    errors.put("message", "Invalid credentials.");

    Map<String, Object> data = new HashMap<>();
    data.put(
            "error", errors);

    response.getOutputStream()
            .println(objectMapper.writeValueAsString(data));
    }
}

In the authentication flow, when I throw a CredentialsExpiredException, BadCredentialsException or any authentication Exception will call unsuccessfulAuthentication method from the AbstractAuthenticationProcessingFilter, and will be execute the given AuthenticationFailureHandler:

public final class TokenAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
private static final String BEARER = "Bearer";

public TokenAuthenticationFilter(final RequestMatcher requiresAuth) {
    super(requiresAuth);
}

@Override
public Authentication attemptAuthentication(
        final HttpServletRequest request,
        final HttpServletResponse response) {
    final String param = ofNullable(request.getHeader(AUTHORIZATION))
            .orElse(request.getParameter("t"));

    final String token = ofNullable(param)
            .map(value -> removeStart(value, BEARER))
            .map(String::trim)
            .orElseThrow(() -> new BadCredentialsException("Missing Authentication Token"));

    final Authentication auth = new UsernamePasswordAuthenticationToken(null, token);
    return getAuthenticationManager().authenticate(auth);
}

@Override
protected void successfulAuthentication(
        final HttpServletRequest request,
        final HttpServletResponse response,
        final FilterChain chain,
        final Authentication authResult) throws IOException, ServletException {
    super.successfulAuthentication(request, response, chain, authResult);
    chain.doFilter(request, response);
}

@Override
protected void unsuccessfulAuthentication(HttpServletRequest request,
                                          HttpServletResponse response,
                                          AuthenticationException failed)
        throws IOException, ServletException {
    getFailureHandler().onAuthenticationFailure(request, response, failed);
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM