401 instead of 403 with Spring Boot 2
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 removeHttp401AuthenticationEntryPoint
.
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
}
}
Related videos on Youtube
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, 2022Comments
-
lealceldeiro almost 2 years
With Spring Boot 1.5.6.RELEASE I was able to send HTTP Status code
401
instead of403
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 of403
?
-
Nelio Alves about 6 yearsNow 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 over 5 yearsThanks, 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 over 4 yearsThis 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 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 over 3 yearsThe 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 over 2 yearsNice! What's the syntax from?
-
Roman T over 2 yearsit is Kotlin language