首先我要评论,我已经签入堆栈溢出等问题,并根据答案来实现我自己的方法:https://stackoverflow.com/a/14425801/2487263和https://stackoverflow.com/a/16101649/2487263如何在Spring Security 3.1中处理不同的认证异常?
我试图使用Spring安全3.1中,以确保一个REST API春季3.2,Spring MVC的应用程序,我使用一个简单的配置基本身份验证的方法:
<http create-session="stateless" entry-point-ref="authenticationFailedEntryPoint">
<intercept-url pattern="/**" access="ROLE_USER"/>
<http-basic />
</http>
正如你可以看到我用我自定义的切入点,我有我自己的错误响应的对象,我会添加到HTTP以json格式回复,请参阅下面的代码:
@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));
}
}
我试图接近具有两个测试情况:
- 尝试使用一个服务,而不提供基本认证头:
这是请求/响应:
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.-尝试使用相同的服务,但现在发送基本的真实通货膨胀头用了错误的密码:
这是请求/响应:
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>
正如你在第一种情况下看到的入口点达到并动工方法用的正确处理执行异常和json响应返回。但是,当密码错误时并不会发生这种情况。
在日志我发现,这两种情况下都产生不同的流量:
对于情况1(无AUTH报头):
...
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.sprin[email protected]9055e4a6: Principal: anonymousUser; Credentials: [PROTECTED]; Authenticated: true; Details: org.sprin[email protected]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: [email protected], returned: -1
2013-10-25 13:11:15,831 DEBUG tomcat-http--13 org.springframework.security.access.vote.AffirmativeBased - Voter: [email protected]ef7, 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
...
对于壳体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
...
第一个案例抛出一个AccessDeniedException,它被捕获并发送到我的入口点的begin方法,但抛出BadCredentialsException的第二个案例不会发生o入口点。
奇怪的这里就是动工方法应该收到的AuthenticationException,但AccessDeniedException异常不是的AuthenticationException但BadCredentialsException是,看到了继承树从Spring安全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
为什么启动方法调用的异常不是正确的类型,为什么在具有正确类型的BadCredentialsException时不调用它?
编辑---实施的由@Luke
两个描述的解决方案中使用的问题中所示的自定义的AuthenticationEntryPoint答案,该配置需要修改选择的以下两个选项中的一个:
添加自定义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的基本元素,IMO是干净的解决方案:
<http create-session="stateless" entry-point-ref="authenticationFailedEntryPoint"> <intercept-url pattern="/**" access="ROLE_USER"/> <http-basic entry-point-ref="authenticationFailedEntryPoint" /> </http>
关于异常问题,在问题的最后注释,似乎AccesDeniedException被ExceptionTranlationFilter转换为AuthenticationException。这就是为什么自定义入口点的开始方法被称为 – raspacorp
raspacorp - 如何执行“开始”方法,虽然我们有@ExceptionalHandler? – Prateek
@pashtika这是我几乎两年前遇到的一个问题,通过使用该自定义入口点来解决。我记得异常处理程序没有捕捉到这种异常,因为它发生在早期阶段。但是我不知道这个问题是否在更新版本的框架中得到解决,在这种情况下,只需添加一个适当的异常处理程序就可以实现。 – raspacorp