Java Spring security 3.1 如何处理不同的认证异常?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/19596872/
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
How to handle different authentication exceptions in Spring security 3.1?
提问by raspacorp
First I want to comment that I already checked the other questions in Stack Overflow and implemented my own approach based on the answers: https://stackoverflow.com/a/14425801/2487263and https://stackoverflow.com/a/16101649/2487263
首先,我想评论一下,我已经检查了 Stack Overflow 中的其他问题,并根据答案实施了我自己的方法:https: //stackoverflow.com/a/14425801/2487263和https://stackoverflow.com/a/16101649 /2487263
I am trying to secure a REST API using Spring security 3.1 in a Spring 3.2, Spring MVC application, I am using a basic authentication approach with a simple configuration:
我正在尝试在 Spring 3.2、Spring MVC 应用程序中使用 Spring security 3.1 来保护 REST API,我正在使用具有简单配置的基本身份验证方法:
<http create-session="stateless" entry-point-ref="authenticationFailedEntryPoint">
<intercept-url pattern="/**" access="ROLE_USER"/>
<http-basic />
</http>
As you can see I am using my custom entry point I have my own ErrorResponse object that I will add to the http response in json format, see the code below:
如您所见,我正在使用我的自定义入口点,我有自己的 ErrorResponse 对象,我将以 json 格式将其添加到 http 响应中,请参阅以下代码:
@Component
public class AuthenticationFailedEntryPoint implements AuthenticationEntryPoint {
static Logger log = Logger.getLogger(AuthenticationFailedEntryPoint.class);
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
log.error(ExceptionUtils.getStackTrace(authException));
ErrorResponse errorResponse = new ErrorResponse();
... here I fill my errorResponse object ...
ObjectMapper jsonMapper = new ObjectMapper();
response.setContentType("application/json;charset=UTF-8");
response.setStatus(status);
PrintWriter out = response.getWriter();
out.print(jsonMapper.writeValueAsString(errorResponse));
}
}
I tried the approach with two testing cases:
我用两个测试用例尝试了这种方法:
- Try to consume one service without providing basic authentication headers:
- 尝试在不提供基本身份验证标头的情况下使用一项服务:
This is the request/response:
这是请求/响应:
GET http://localhost:8081/accounts/accounts?accountNumber=1013
-- response --
401 Unauthorized
Server: Apache-Coyote/1.1
Content-Type: application/json;charset=UTF-8
Content-Length: 320
Date: Fri, 25 Oct 2013 17:11:15 GMT
Proxy-Connection: Keep-alive
{"status":401,"messages":[{"code":"000011","message":"You are not authorized to reach this endpoint"}]}
2.- Try to consume the same service but now sending basic authentication headers with a wrong password:
2.- 尝试使用相同的服务,但现在使用错误的密码发送基本身份验证标头:
This is the request/response:
这是请求/响应:
GET http://localhost:8081/accounts/accounts?accountNumber=1013
Authorization: Basic bXl1c2VyOmdvb2RieWU=
-- response --
401 Unauthorized
Server: Apache-Coyote/1.1
WWW-Authenticate: Basic realm="Spring Security Application"
Content-Type: text/html;charset=utf-8
Content-Length: 1053
Date: Fri, 25 Oct 2013 17:03:09 GMT
Proxy-Connection: Keep-alive
<html> ... ugly html generated by tc server ... </html>
As you can see in the first case the entry point was reached and the commence method was executed with the proper handling of the exception and the json response was returned. But that did not happen when the password was a wrong one.
正如您在第一种情况中看到的那样,到达了入口点,并且在正确处理异常的情况下执行了 begin 方法,并返回了 json 响应。但是当密码错误时就不会发生这种情况。
In the logs I found that both cases are producing a different flow:
在日志中,我发现两种情况都产生了不同的流程:
For case 1 (no auth headers):
对于案例 1(无身份验证标头):
...
2013-10-25 13:11:15,830 DEBUG tomcat-http--13 org.springframework.security.web.access.intercept.FilterSecurityInterceptor - Secure object: FilterInvocation: URL: /accounts?accountNumber=1013; Attributes: [ROLE_USER]
2013-10-25 13:11:15,830 DEBUG tomcat-http--13 org.springframework.security.web.access.intercept.FilterSecurityInterceptor - Previously Authenticated: org.springframework.security.authentication.AnonymousAuthenticationToken@9055e4a6: Principal: anonymousUser; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@957e: RemoteIpAddress: 127.0.0.1; SessionId: null; Granted Authorities: ROLE_ANONYMOUS
2013-10-25 13:11:15,830 DEBUG tomcat-http--13 org.springframework.security.access.vote.AffirmativeBased - Voter: org.springframework.security.access.vote.RoleVoter@11da1f99, returned: -1
2013-10-25 13:11:15,831 DEBUG tomcat-http--13 org.springframework.security.access.vote.AffirmativeBased - Voter: org.springframework.security.access.vote.AuthenticatedVoter@7507ef7, returned: 0
2013-10-25 13:11:15,831 DEBUG tomcat-http--13 org.springframework.security.web.access.ExceptionTranslationFilter - Access is denied (user is anonymous); redirecting to authentication entry point
org.springframework.security.access.AccessDeniedException: Access is denied
...
For the case 2 (wrong password):
对于情况 2(密码错误):
...
2013-10-25 13:03:08,941 DEBUG tomcat-http--11 org.springframework.security.web.authentication.www.BasicAuthenticationFilter - Basic Authentication Authorization header found for user 'myuser'
2013-10-25 13:03:08,941 DEBUG tomcat-http--11 org.springframework.security.authentication.ProviderManager - Authentication attempt using org.springframework.security.authentication.dao.DaoAuthenticationProvider
2013-10-25 13:03:09,544 DEBUG tomcat-http--11 org.springframework.security.authentication.dao.DaoAuthenticationProvider - Authentication failed: password does not match stored value
2013-10-25 13:03:09,545 DEBUG tomcat-http--11 org.springframework.security.web.authentication.www.BasicAuthenticationFilter - Authentication request for failed: org.springframework.security.authentication.BadCredentialsException: Bad credentials
2013-10-25 13:00:30,136 DEBUG tomcat-http--9 org.springframework.security.web.context.SecurityContextPersistenceFilter - SecurityContextHolder now cleared, as request processing completed
...
The first case throws an AccessDeniedException which is being caught and sent to the commence method in my entry point, but the second case which throws a BadCredentialsException is not going to the entry point.
第一种情况抛出一个 AccessDeniedException,它被捕获并发送到我的入口点中的开始方法,但第二种情况抛出 BadCredentialsException 不会进入入口点。
The weird thinghere is that the commence method is supposed to receive an AuthenticationException, but AccessDeniedException is not an AuthenticationException but BadCredentialsException is, see the inheritance tree from the Spring security 3.1 API documentation:
奇怪的是,开始方法应该接收一个 AuthenticationException,但 AccessDeniedException 不是一个 AuthenticationException 而 BadCredentialsException 是,请参阅 Spring security 3.1 API 文档中的继承树:
java.lang.Object
extended by java.lang.Throwable
extended by java.lang.Exception
extended by java.lang.RuntimeException
extended by org.springframework.security.access.AccessDeniedException
java.lang.Object
extended by java.lang.Throwable
extended by java.lang.Exception
extended by java.lang.RuntimeException
extended by org.springframework.security.core.AuthenticationException
extended by org.springframework.security.authentication.BadCredentialsException
Why is the commence method called with an exception that is not the correct type and why is not called when having the BadCredentialsException that is of the correct type ?
为什么使用类型不正确的异常调用开始方法,为什么在具有正确类型的 BadCredentialsException 时不调用?
Edited --- Implemented the answer by @Luke
编辑 --- 由@Luke 实现了答案
The two described solutions use the custom AuthenticationEntryPoint shown in the question, the configuration needs to be modified choosing one of the two following options:
所描述的两个解决方案使用问题中显示的自定义 AuthenticationEntryPoint,需要修改配置,选择以下两个选项之一:
Adding a custom BASIC_AUTH_FILTER:
<http create-session="stateless" entry-point-ref="authenticationFailedEntryPoint"> <intercept-url pattern="/**" access="ROLE_USER"/> <custom-filter position="BASIC_AUTH_FILTER" ref="authenticationFilter" /> </http> <beans:bean id="authenticationFilter" class="org.springframework.security.web.authentication.www.BasicAuthenticationFilter"> <beans:constructor-arg name="authenticationManager" ref="authenticationManager" /> <beans:constructor-arg name="authenticationEntryPoint" ref="authenticationFailedEntryPoint" /> </beans:bean>
OR Adding the entry point to the http-basic element, IMO is the cleanest solution:
<http create-session="stateless" entry-point-ref="authenticationFailedEntryPoint"> <intercept-url pattern="/**" access="ROLE_USER"/> <http-basic entry-point-ref="authenticationFailedEntryPoint" /> </http>
添加自定义 BASIC_AUTH_FILTER:
<http create-session="stateless" entry-point-ref="authenticationFailedEntryPoint"> <intercept-url pattern="/**" access="ROLE_USER"/> <custom-filter position="BASIC_AUTH_FILTER" ref="authenticationFilter" /> </http> <beans:bean id="authenticationFilter" class="org.springframework.security.web.authentication.www.BasicAuthenticationFilter"> <beans:constructor-arg name="authenticationManager" ref="authenticationManager" /> <beans:constructor-arg name="authenticationEntryPoint" ref="authenticationFailedEntryPoint" /> </beans:bean>
或将入口点添加到 http-basic 元素,IMO 是最干净的解决方案:
<http create-session="stateless" entry-point-ref="authenticationFailedEntryPoint"> <intercept-url pattern="/**" access="ROLE_USER"/> <http-basic entry-point-ref="authenticationFailedEntryPoint" /> </http>
采纳答案by Shaun the Sheep
I think the problem is that, after a failed basic authentication, the call to the entry point is made directly from the BasicAuthenticationFilter, which by default will be the built-in implementation.
我认为问题在于,在基本身份验证失败后,直接从BasicAuthenticationFilter调用入口点,默认情况下它将是内置实现。
You need to also set the entry-point-ref
attribute on the http-basic
element to fix this.
您还需要entry-point-ref
在http-basic
元素上设置属性来解决这个问题。
Alternatively you can define the basic authentication filter as a bean and avoid the namespace completely.
或者,您可以将基本身份验证过滤器定义为 bean 并完全避免命名空间。