spring安全性:AuthenticationManager抛出BadCredentialsException时返回状态401
问题内容:
首先,我想指出的是,我对Spring Security并不十分了解, 实际上我对它的接口和类了解得很少
,但是我要做的不是那么简单,也无法弄清楚。我的代码基于Spring
Security论坛中的以下帖子(与帖子所有者没有相同的问题):http : //forum.spring.io/forum/spring-
projects/security/747178-security-
过滤器链总是每个请求两次调用认证管理器
我正在编程一个将提供HTTP内容的Spring MVC系统,但为此,它具有一个预身份验证检查(我目前正在使用带有自定义AuthenticationManager的RequestHeaderAuthenticationFilter)。
为了授权用户,我将针对两个来源检查令牌,即Redis缓存“数据库”和Oracle。如果在任何这些来源中均未找到令牌,则我的自定义AuthenticationManager的authenticate方法将引发BadCredentialsException(我相信该方法会兑现AuthenticationManager合同)。
现在,我想返回HTTP响应401-未经授权,但Spring会继续返回500-服务器错误。是否可以自定义设置以仅返回401而不返回500?
以下是相关代码:
SecurityConfig-主Spring安全配置
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;
}
/**
* Configures the HTTP filter chain depending on configuration settings.
*
* Note that this exception is thrown in spring security headerAuthenticationFilter chain and will not be logged as
* error. Instead the ExceptionTranslationFilter will handle it and clear the security context. Enabling DEBUG
* logging for 'org.springframework.security' will help understanding headerAuthenticationFilter chain
*/
@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-我的自定义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"));
}
}
使用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"}
问题答案:
我看了一下来源。似乎可以通过子类化RequestHeaderAuthenticationFilter并重写unsuccessfulAuthentication(…)方法来相当容易地实现这一点,该方法仅在检测到身份验证失败之后并且在抛出新的RuntimeException之前被调用:
public class MyRequestHeaderAuthenticationFilter extends
RequestHeaderAuthenticationFilter {
@Override
protected void unsuccessfulAuthentication(HttpServletRequest request,
HttpServletResponse response, AuthenticationException failed) {
super.unsuccessfulAuthentication(request, response, failed);
// see comments in Servlet API around using sendError as an alternative
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
}
}
然后,只需将您的Filter Config指向此实例即可。