First of all, I would like to point out that I do not know Spring Security very well, in fact I know quite a bit about its interfaces and classes, but I need not a simple task, This is understandable. My code is based on the following post on the Spring Security Forum (I don't have the same problem as the post owner): http://forum.spring.io/forum/spring-projects/security/747178-security-filter-chain- is-always-calling-authenticationmanager-twice-per-request
I am programming a Spring MVC system that will serve HTTP content, but it has a preliminary check for this (which I am currently using RequestHeaderAuthenticationFilter with a custom AuthenticationManager ).
To authorize the user, I will check the token for two sources, the Redis database and the Oracle database. If the token is not found in any of these sources, the authentication method of my custom AuthenticationManager throws a BadCredentialsException (which, I believe, matches the AuthenticationManager contract).
Now I would like to return in the HTTP 401 - Unauthorized response, but Spring continues to return 500 - Server Error. Can I configure my installation to return only 401 not 500?
Here is the relevant code:
SecurityConfig - Spring's main security configuration
package br.com.oiinternet.imoi.web.config; import javax.validation.constraints.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.boot.autoconfigure.security.SecurityProperties; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.annotation.Order; import org.springframework.http.HttpMethod; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.security.web.access.AccessDeniedHandler; import org.springframework.security.web.access.AccessDeniedHandlerImpl; import org.springframework.security.web.authentication.Http403ForbiddenEntryPoint; import org.springframework.security.web.authentication.logout.LogoutFilter; import org.springframework.security.web.authentication.preauth.RequestHeaderAuthenticationFilter; @Configuration @EnableWebSecurity @Order(SecurityProperties.ACCESS_OVERRIDE_ORDER) public class SecurityConfig extends WebSecurityConfigurerAdapter { private static final Logger LOG = LoggerFactory.getLogger(SecurityConfig.class); public static final String X_AUTH_TOKEN = "X-Auth-Token"; private AccessDeniedHandler accessDeniedHandler = new AccessDeniedHandlerImpl(); @Bean public AuthenticationManager authenticationManager() { return new TokenBasedAuthenticationManager(); } @Bean public AuthenticationEntryPoint authenticationEntryPoint() { return new Http403ForbiddenEntryPoint(); } @Bean public RequestHeaderAuthenticationFilter requestHeaderAuthenticationFilter( final AuthenticationManager authenticationManager) { RequestHeaderAuthenticationFilter filter = new RequestHeaderAuthenticationFilter(); filter.setAuthenticationManager(authenticationManager); filter.setExceptionIfHeaderMissing(false); filter.setPrincipalRequestHeader(X_AUTH_TOKEN); filter.setInvalidateSessionOnPrincipalChange(true); filter.setCheckForPrincipalChanges(true); filter.setContinueFilterChainOnUnsuccessfulAuthentication(false); return filter; } @Override protected void configure(final HttpSecurity http) throws Exception { RequestHeaderAuthenticationFilter requestHeaderAuthenticationFilter = fromContext(http, RequestHeaderAuthenticationFilter.class); AuthenticationEntryPoint authenticationEntryPoint = fromContext(http, AuthenticationEntryPoint.class); http.authorizeRequests() .antMatchers(HttpMethod.GET, "/auth").permitAll() .antMatchers(HttpMethod.GET, "/**").authenticated() .antMatchers(HttpMethod.POST, "/**").authenticated() .antMatchers(HttpMethod.HEAD, "/**").authenticated() .and() .sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and().securityContext() .and().exceptionHandling() .authenticationEntryPoint(authenticationEntryPoint) .accessDeniedHandler(accessDeniedHandler) .and() .addFilterBefore(requestHeaderAuthenticationFilter, LogoutFilter.class); } private <T> T fromContext(@NotNull final HttpSecurity http, @NotNull final Class<T> requiredType) { @SuppressWarnings("SuspiciousMethodCalls") ApplicationContext ctx = (ApplicationContext) http.getSharedObjects().get(ApplicationContext.class); return ctx.getBean(requiredType); } }
TokenBasedAuthenticationManager - my custom AuthenticationManager
package br.com.oiinternet.imoi.web.config; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import br.com.oi.oicommons.lang.message.Messages; import br.com.oiinternet.imoi.service.AuthService; import br.com.oiinternet.imoi.web.security.auth.AuthenticationAuthorizationToken; public class TokenBasedAuthenticationManager implements AuthenticationManager { @Autowired private AuthService authService; @Autowired private Messages messages; @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { final String token = (String) authentication.getPrincipal(); if (authService.isAuthorized(token) || authService.authenticate(token)) { return new AuthenticationAuthorizationToken(token); } throw new BadCredentialsException(messages.getMessage("access.bad.credentials")); } }
Example request / response loop using curl:
user@user-note :curl --header "X-Auth-Token: 2592cd35124dc3d79bdd82407220a6ea7fad9b8b313a1205cf1824a5ce726aa8dd763cde8c05faadae48b47252de95b0" http://localhost:8081/test/auth -v * Hostname was NOT found in DNS cache * Trying 127.0.0.1... * Connected to localhost (127.0.0.1) port 8081 (#0) > GET /test/auth HTTP/1.1 > User-Agent: curl/7.35.0 > Host: localhost:8081 > Accept: */* > X-Auth-Token: 2592cd35124dc3d79bdd82407220a6ea7fad9b8b313a1205cf1824a5ce726aa8dd763cde8c05faadae48b47252de95b0 > < HTTP/1.1 500 Server Error < X-Content-Type-Options: nosniff < X-XSS-Protection: 1; mode=block < Pragma: no-cache < X-Frame-Options: DENY < Content-Type: application/json;charset=UTF-8 < Connection: close * Server Jetty(9.1.0.v20131115) is not blacklisted < Server: Jetty(9.1.0.v20131115) < * Closing connection 0 {"timestamp":1414513379405,"status":500,"error":"Internal Server Error","exception":"org.springframework.security.authentication.BadCredentialsException","message":"access.bad.credentials","path":"/test/auth"}