5

I'm using Spring Boot + Spring Security (java config). My question is the old one, but all info which I've found is partially outdated and mostly contains xml-config (which difficult or even impossible to adapt some time)

I'm trying to do stateless authentication with a token (which doesn't stored on server side). Long story short - it is a simple analogue for JSON Web Tokens authentication format. I'm using two custom filters before default one:

  • TokenizedUsernamePasswordAuthenticationFilter which creates token after successful authentication on entry point ("/myApp/login")

  • TokenAuthenticationFilter which tries to authenticate the user using token (if provided) for all restricted URLs.

I do not understand how properly handle custom exceptions(with custom message or redirect) if I want some... Exceptions in filters are not relevant to exceptions in controllers, so they will not be handled by same handlers...

If I've understood right, I can not use

.formLogin()

                .defaultSuccessUrl("...")
                .failureUrl("...")
                .successHandler(myAuthenticationSuccessHandler)
                .failureHandler(myAthenticationFailureHandler)

to customize exceptions, because I use custom filters... So what the way to do it?

My config:

@Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .csrf().disable()
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
        .and()  .anonymous()

        .and()  .authorizeRequests()                      
                .antMatchers("/").permitAll()
                ...
                .antMatchers(HttpMethod.POST, "/login").permitAll()                    
        .and()                    
                .addFilterBefore(new TokenizedUsernamePasswordAuthenticationFilter("/login",...), UsernamePasswordAuthenticationFilter.class)                      
                .addFilterBefore(new TokenAuthenticationFilter(...), UsernamePasswordAuthenticationFilter.class)

    }

1 Answer 1

3

We can set AuthenticationSuccessHandler and AuthenticationFailureHandler in your custom filter as well.

Well in your case,

// Constructor of TokenizedUsernamePasswordAuthenticationFilter class
public TokenizedUsernamePasswordAuthenticationFilter(String path, AuthenticationSuccessHandler successHandler, AuthenticationFailureHandler failureHandler) {
    setAuthenticationSuccessHandler(successHandler);
    setAuthenticationFailureHandler(failureHandler);
}

Now to use these handlers just invoke onAuthenticationSuccess() or onAuthenticationFailure() methods.

@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response,
                                          FilterChain chain, Authentication authentication) throws IOException, ServletException {

    getSuccessHandler().onAuthenticationSuccess(request, response, authentication);
}

@Override
protected void unsuccessfulAuthentication(HttpServletRequest request,
                                            HttpServletResponse response,
                                            AuthenticationException failed)
          throws IOException, ServletException {

    getFailureHandler().onAuthenticationFailure(request, response, failed);
}

You can create your custom authentication handler classes to handle the success or failure cases. For example,

public class LoginSuccessHandler implements AuthenticationSuccessHandler {

  @Override
  public void onAuthenticationSuccess(HttpServletRequest httpServletRequest,
                                      HttpServletResponse httpServletResponse,
                                      Authentication authentication)
          throws IOException, ServletException {

    SecurityContextHolder.getContext().setAuthentication(authentication);
    // Do your stuff, eg. Set token in response header, etc.
  }
}

Now for handling the exception,

public class LoginFailureHandler implements AuthenticationFailureHandler {

  @Override
  public void onAuthenticationFailure(HttpServletRequest httpServletRequest,
                                      HttpServletResponse httpServletResponse,
                                      AuthenticationException e)
          throws IOException, ServletException {

    String errorMessage = ExceptionUtils.getMessage(e);

    sendError(httpServletResponse, HttpServletResponse.SC_UNAUTHORIZED, errorMessage, e);
  }


  private void sendError(HttpServletResponse response, int code, String message, Exception e) throws IOException {
    SecurityContextHolder.clearContext();

    Response<String> exceptionResponse =
            new Response<>(Response.STATUES_FAILURE, message, ExceptionUtils.getStackTrace(e));

    exceptionResponse.send(response, code);
  }
}

My custom response class to generate desired JSON response,

public class Response<T> {

  public static final String STATUES_SUCCESS = "success";
  public static final String STATUES_FAILURE = "failure";

  private String status;
  private String message;
  private T data;

  private static final Logger logger = Logger.getLogger(Response.class);

  public Response(String status, String message, T data) {
    this.status = status;
    this.message = message;
    this.data = data;
  }

  public String getStatus() {
    return status;
  }

  public String getMessage() {
    return message;
  }

  public T getData() {
    return data;
  }

  public String toJson() throws JsonProcessingException {
    ObjectWriter ow = new ObjectMapper().writer().withDefaultPrettyPrinter();
    try {
      return ow.writeValueAsString(this);
    } catch (JsonProcessingException e) {
      logger.error(e.getLocalizedMessage());
      throw e;
    }
  }

  public void send(HttpServletResponse response, int code) throws IOException {
    response.setStatus(code);
    response.setContentType("application/json");
    String errorMessage;

    errorMessage = toJson();

    response.getWriter().println(errorMessage);
    response.getWriter().flush();
  }
}

I hope this helps.

Sign up to request clarification or add additional context in comments.

1 Comment

This will send one general error message on failure. How to send different error messages based on different cases

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.