11

I have a problem with Spring Security authentication failure handler redirect with parameter.

In security config when I use

failureUrl("/login.html?error=true")

it works. But when I use custom authentication failure handler (as shown below), it always returns: url/login.html

getRedirectStrategy().sendRedirect(request, response, "/login.html?error=true");

or

response.sendRedirect(request.getContextPath() + "/login.html?error=true");

I don't know whats wrong. Why does it not show the parameter ?error=true?

Info: I am using Spring + JSF + Hibernate + Spring Security

@Override
protected void configure(HttpSecurity http) throws Exception {

    http
        .authorizeRequests()
            .anyRequest().authenticated()
            .and()
        .formLogin()
            .loginPage("/login.html")
            .usernameParameter("j_username")
            .passwordParameter("j_password")
            .loginProcessingUrl("/j_spring_security_check")
            .failureHandler(customAuthenticationFailureHandler)// .failureUrl("/login.html?error=true")//.successHandler(authSuccsessHandler)
            .defaultSuccessUrl("/dashboard.html")
            .permitAll()
            .and()
        .logout()
            .invalidateHttpSession(true)
            .logoutSuccessUrl("/")
            .permitAll()
            .and()
        .exceptionHandling()
            .accessDeniedPage("/access.html")
            .and()
        .headers()
            .defaultsDisabled()
            .frameOptions()
            .sameOrigin()
            .cacheControl();

    http
        .csrf().disable();
}

This is custom authentication failure handler:

@Component
public class CustomAuthFailureHandler extends SimpleUrlAuthenticationFailureHandler {

    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
            AuthenticationException exception) throws IOException, ServletException {
        getRedirectStrategy().sendRedirect(request, response, "/login.html?error=true");

    }
}

I will change parameter for some cases.

fmw42
  • 46,825
  • 10
  • 62
  • 80
mstykt
  • 160
  • 1
  • 3
  • 14
  • This answer might be helpful http://stackoverflow.com/questions/17199423/spring-security-3-1-custom-authentication-failure-url-with-url-parameters – hrdkisback May 08 '17 at 06:11

2 Answers2

16

You didn't allow anonymous access to URL /login.html?error=true, so you are redirected to the login page (/login.html).

AbstractAuthenticationFilterConfigurer#permitAll allows access (for anyone) to failure URL but not for custom failure handler:

Ensures the urls for failureUrl(String) as well as for the HttpSecurityBuilder, the getLoginPage() and getLoginProcessingUrl() are granted access to any user.

You have to allow access explicitly with AbstractRequestMatcherRegistry#antMatchers:

Maps a List of AntPathRequestMatcher instances that do not care which HttpMethod is used.

and ExpressionUrlAuthorizationConfigurer.AuthorizedUrl#permitAll:

Specify that URLs are allowed by anyone.

You don't have to allow the exact URL /login.html?error=true, because AntPathRequestMatcher ignores the query string:

Matcher which compares a pre-defined ant-style pattern against the URL ( servletPath + pathInfo) of an HttpServletRequest. The query string of the URL is ignored and matching is case-insensitive or case-sensitive depending on the arguments passed into the constructor.

Your modified configuration:

@Override
protected void configure(HttpSecurity http) throws Exception {

    http
        .authorizeRequests()
            .antMatchers("/login.html").permitAll()
            .anyRequest().authenticated()
            .and()
        .formLogin()
            .loginPage("/login.html")
            .usernameParameter("j_username")
            .passwordParameter("j_password")
            .loginProcessingUrl("/j_spring_security_check")
            .failureHandler(customAuthenticationFailureHandler)// .failureUrl("/login.html?error=true")//.successHandler(authSuccsessHandler)
            .defaultSuccessUrl("/dashboard.html")
            .permitAll()
            .and()
        .logout()
            .invalidateHttpSession(true)
            .logoutSuccessUrl("/")
            .permitAll()
            .and()
        .exceptionHandling()
            .accessDeniedPage("/access.html")
            .and()
        .headers()
            .defaultsDisabled()
            .frameOptions()
            .sameOrigin()
            .cacheControl();

    http
        .csrf().disable();
}
Community
  • 1
  • 1
dur
  • 15,689
  • 25
  • 79
  • 125
  • May I ask what is the advantage of calling getRedirectStrategy().sendRedirect(request, response, "/login.html?error=true") over response.sendRedirect(request.getContextPath() + "/login.html?error=true") – Andrey M. Stepanov Apr 06 '19 at 21:24
  • @AndreyM.Stepanov It is just a way to support different strategies. The default is [DefaultRedirectStrategy](https://docs.spring.io/spring-security/site/docs/4.2.11.RELEASE/apidocs/org/springframework/security/web/DefaultRedirectStrategy.html) It calculates the right URL. – dur Apr 06 '19 at 21:30
0

In the case of OAuth token failure, I am getting below response, which is inconsistent with app response style.

    {
    "error": "invalid_token",
    "error_description": "Invalid access token: 4cbc6f1c-4d47-44bd-89bc-92a8c86d88dbsdfsdfs"
}

I just wanted to use common response object for the consistency. Following approach worked for me. Build your resource server with your custom entry-point object

@Override
  public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
    resources.authenticationEntryPoint(new CustomOAuth2AuthenticationEntryPoint());
  } 

and here is your custom entry point

 public class CustomOAuth2AuthenticationEntryPoint extends OAuth2AuthenticationEntryPoint{

  public CustomOAuth2AuthenticationEntryPoint() {
    super.setExceptionTranslator(new CustomOAuth2WebResponseExceptionTranslator());
  }
}

here is your custom WebResponseExceptionTranslator, In my case I have just used a replica of DefaultWebResponseExceptionTranslator and rewritten handleOAuth2Exception method.

CustomOAuth2WebResponseExceptionTranslator implements WebResponseExceptionTranslator<Response> {
....
.....
 private ResponseEntity<Response> handleOAuth2Exception(OAuth2Exception e) throws IOException {

        int status = e.getHttpErrorCode();
        HttpHeaders headers = new HttpHeaders();
        headers.set("Cache-Control", "no-store");
        headers.set("Pragma", "no-cache");
        if (status == HttpStatus.UNAUTHORIZED.value() || (e instanceof InsufficientScopeException)) {
            headers.set("WWW-Authenticate", String.format("%s %s", OAuth2AccessToken.BEARER_TYPE, e.getSummary()));
        }

        ResponseEntity<Response> response =new ResponseEntity<>(new Response().message(e.getMessage()).status(StatusEnum.ERROR)
            .errorType(e.getClass().getName()), HttpStatus.UNAUTHORIZED);
        return response;

    }

Result looks like

    {
    "status": "error",
    "message": "Invalid access token: 4cbc6f1c-4d47-44bd-89bc-92a8c86d88dbsdfsdfs",
    "error_type": "org.springframework.security.oauth2.common.exceptions.InvalidTokenException"
}