401 instead of 403 with Spring Boot 2

11,490

Solution 1

Heads up

By default Spring Boot 2 will return 401 when spring-boot-starter-security is added as a dependency and an unauthorized request is performed.

This may change if you place some custom configurations to modify the security mechanism behavior. If that's the case and you truly need to force the 401 status, then read the below original post.

Original Post

The class org.springframework.boot.autoconfigure.security.Http401AuthenticationEntryPoint was removed in favor of org.springframework.security.web.authentication.HttpStatusEntryPoint.

In my case the code would go like this:

public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //...
        http.exceptionHandling()
            .authenticationEntryPoint(new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED));
        //...
    }
}

Bonus

If you need to return some information in the response body or customize the response somehow you can do something like this:

1- Extend AuthenticationEntryPoint

public class MyEntryPoint implements AuthenticationEntryPoint {
    private final HttpStatus httpStatus;
    private final Object responseBody;

    public MyEntryPoint(HttpStatus httpStatus, Object responseBody) {
        Assert.notNull(httpStatus, "httpStatus cannot be null");
        Assert.notNull(responseBody, "responseBody cannot be null");
        this.httpStatus = httpStatus;
        this.responseBody = responseBody;
    }

    @Override
    public final void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException {
        response.setStatus(httpStatus.value());

        try (PrintWriter writer = response.getWriter()) {
            writer.print(new ObjectMapper().writeValueAsString(responseBody));
        }
    }
}

2- Provide an instance of MyEntryPoint to the security configuration

public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // customize your response body as needed
        Map<String, String> responseBody = new HashMap<>();
        responseBody.put("error", "unauthorized");

        //...
        http.exceptionHandling()
            .authenticationEntryPoint(new MyEntryPoint(HttpStatus.UNAUTHORIZED, responseBody));
        //...
    }
}

Solution 2

Just to elaborate @lealceldeiro's answer:

Before Spring Boot 2 my Securiy Configuration class looked like this:

@Configuration
public class MyConfig extends WebSecurityConfigurerAdapter {

    @Bean
    public Http401AuthenticationEntryPoint securityException401EntryPoint() {
      return new Http401AuthenticationEntryPoint("Bearer realm=\"webrealm\"");
    }

    @Autowired
    private Http401AuthenticationEntryPoint authEntrypoint;

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

      // some http configuration ...

      // Spring Boot 1.5.x style
      http.exceptionHandling().authenticationEntryPoint(authEntrypoint);
    }
//...
}

And now in Spring Boot 2 it looks like this:

@Configuration
public class MyConfig extends WebSecurityConfigurerAdapter {

    //Bean configuration for Http401AuthenticationEntryPoint can be removed

    //Autowiring also removed

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

      // some http configuration ...

      // Spring Boot 2 style
      http.exceptionHandling().authenticationEntryPoint(new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED));
    }
//...
}

See also this comment in Spring Boot Github Repo > PR Remove Http401AuthenticationEntryPoint.

Solution 3

Http401AuthenticationEntryPoint was removed.

See Spring Boot Github Repo > Issue #10715 (Remove Http401AuthenticationEntryPoint):

Remove Http401AuthenticationEntryPoint

rwinch commented on 20 Oct 2017
As far as I can tell it is not being used in the Spring Boot code base, so it might be good to remove Http401AuthenticationEntryPoint.

Depending on your requirements, you could use:

Solution 4

For reactive (WebFlux) stack you can override the returned status code by adding such @Bean to catch some specific exceptions:

@Component
class MyErrorAttributes : DefaultErrorAttributes() {
override fun getErrorAttributes(
    request: ServerRequest,
    options: ErrorAttributeOptions
): MutableMap<String, Any> {
    val cause = super.getError(request)

    val errorAttributes = super.getErrorAttributes(request, options)

    when (cause) {
        is TokenExpiredException -> {
            errorAttributes["status"] = HttpStatus.UNAUTHORIZED.value()
            errorAttributes["error"] = HttpStatus.UNAUTHORIZED.reasonPhrase
        }
    }

    return errorAttributes
}
}
Share:
11,490

Related videos on Youtube

lealceldeiro
Author by

lealceldeiro

Asiel Leal Celdeiro The more I learn, the more I realize how much I don't know. From time to time you'll see me on StackOverflow helping others to get great code and learning a bit more. LinkedIn | GitHub | Twitter When I have some spare time I read books and take some notes Good posts in the anonymity: How do I internationalize an OSGi application using Eclipse? How to install JDK under Ubuntu?

Updated on June 04, 2022

Comments

  • lealceldeiro
    lealceldeiro almost 2 years

    With Spring Boot 1.5.6.RELEASE I was able to send HTTP Status code 401 instead of 403 as described in How let spring security response unauthorized(http 401 code) if requesting uri without authentication, by doing this:

    public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            //...
            http.exceptionHandling()
                    .authenticationEntryPoint(new Http401AuthenticationEntryPoint("myHeader"));
            //...
        }
    }
    

    using the org.springframework.boot.autoconfigure.security.Http401AuthenticationEntryPoint class.

    I just upgraded to Spring Boot 2.0.0.RELEASE and found there is not such class any more (at least in that package).

    Questions:

    • Does this class (Http401AuthenticationEntryPoint) exist yet in Spring Boot?

    • If no, what could be a good alternative for keeping the same behavior in an existing project in order to keep consistency with other implementations which depend on this status code (401) instead of 403?

  • Nelio Alves
    Nelio Alves about 6 years
    Now bad credential requests are returning 401, but empty-body response. Also, unauthorized requests, which should return 403, are also returning 401 with empty-body response.
  • lealceldeiro
    lealceldeiro over 5 years
    Thanks, very useful link from spring boot git repo. In the answer I provided future readers can see how I used HttpStatusEntryPoint according to my requirements.
  • Planky
    Planky over 4 years
    This returns 401s with no body. That's fine for js, but when viewed in Firefox it's a blank page and when viewed in Chrome it says "This page isn't working". It's easy to make your own replacement of the Htp401AuthenticationEntryPoint though and use that. Just implement AuthenticationEntryPoint and set whatever status and message you want.
  • lealceldeiro
    lealceldeiro about 4 years
    @Planky thank you very much for pointing that out! I just put a possible approach others might follow based on your comment :)
  • dube
    dube over 3 years
    The solution looks nice and clean, but it is missing the mandatory WWW-Authenticate Header, which would tell the client how he can attempt to authenticate
  • lealceldeiro
    lealceldeiro over 2 years
    Nice! What's the syntax from?
  • Roman T
    Roman T over 2 years
    it is Kotlin language