Java Spring-Security:当 AuthenticationManager 抛出 BadCredentialsException 时返回状态 401
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/26613865/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Spring-Security: Return Status 401 When AuthenticationManager Throws BadCredentialsException
提问by resilva87
First of all, I'd like to point that I don't know Spring Security very much, actually I know quite little about its interfaces and classes, but I got a not so simple task to do and can't quite figure it out. My code is based in the following post in the Spring Security Forum (I'm not having 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
首先,我想指出我不太了解Spring Security,实际上我对它的接口和类知之甚少,但我有一个不那么简单的任务要做,无法弄清楚. 我的代码基于 Spring Security Forum 中的以下帖子(我与帖子所有者没有相同的问题):http: //forum.spring.io/forum/spring-projects/security/747178-security- filter-chain-is-always-calling-authenticationmanager-twice-per-request
I'm programming a Spring MVC system which will serve HTTP content but, in order to do so, it has a preauth check (which I'm currently using RequestHeaderAuthenticationFilterwith a custom AuthenticationManager).
我正在编写一个 Spring MVC 系统,它将提供 HTTP 内容,但为了这样做,它有一个 preauth 检查(我目前正在使用RequestHeaderAuthenticationFilter和自定义AuthenticationManager)。
To authorize the user, I'll check the token against two sources, a Redis cache "database" and Oracle. If the token is not found in any of those sources, the authenticate method of my custom AuthenticationManager throws a BadCredentialsException (which I believe honours the AuthenticationManager contract).
为了授权用户,我将根据两个来源检查令牌,Redis 缓存“数据库”和 Oracle。如果在这些来源中的任何一个中都找不到令牌,则我的自定义 AuthenticationManager 的身份验证方法会抛出 BadCredentialsException (我相信它符合 AuthenticationManager 合同)。
Now I'd like to return in the HTTP response 401 - Unauthorized, but Spring keeps returning 500 - Server Error. Is it possible to customize my setup to return only 401 not 500?
现在我想在 HTTP 响应中返回 401 - 未经授权,但 Spring 不断返回 500 - 服务器错误。是否可以自定义我的设置以仅返回 401 而不是 500?
Here's the relevant code:
这是相关的代码:
SecurityConfig - main spring security config
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 - my custom AuthenticationManager
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"));
}
}
Example of request/response cycle using curl:
使用 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"}
采纳答案by Alan Hay
I have had a look at the source. It would seem that you could achieve this fairly easily by subclassing RequestHeaderAuthenticationFilter and overriding the unsuccessfulAuthentication(...) method which is called just after a failed authentication is detected and just before a new RuntimeException is thrown:
我看过源码。似乎您可以通过继承 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);
}
}
Then just point your Filter Config to an instance of this.
然后只需将您的过滤器配置指向此实例。