如何在Spring Security 3.1中处理不同的身份验证异常?

时间:2013-10-25 18:19:38

标签: java spring spring-mvc spring-security basic-authentication

首先,我想评论一下,我已经检查了Stack Overflow中的其他问题,并根据答案实施了我自己的方法:https://stackoverflow.com/a/14425801/2487263https://stackoverflow.com/a/16101649/2487263

我试图在Spring 3.2,Spring MVC应用程序中使用Spring安全3.1来保护REST API,我使用简单配置的基本身份验证方法:

<http create-session="stateless" entry-point-ref="authenticationFailedEntryPoint">
    <intercept-url pattern="/**" access="ROLE_USER"/>
    <http-basic />
</http>

正如您所看到的,我正在使用自定义入口点,我有自己的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));   
    }
}

我尝试了两种测试用例的方法:

  1. 尝试使用一项服务而不提供基本身份验证标头:
  2. 这是请求/回复:

    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>
    

    正如您在第一种情况下所看到的那样,达到了入口点,并且正确处理了异常并执行了begin方法,并返回了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.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
    ...
    

    对于案例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的第二种情况不会进入入口点。

    奇怪的是这里是开始方法应该接收一个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
    

    为什么使用不正确类型的异常调用begin方法,以及为什么在具有正确类型的BadCredentialsException时不调用?

    已编辑---已实施@Luke

    的答案

    所描述的两个解决方案使用问题中显示的自定义AuthenticationEntryPoint,需要选择以下两个选项之一来修改配置:

    1. 添加自定义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>
      
    2. 或者将入口点添加到http-basic元素,IMO是最干净的解决方案:

      <http create-session="stateless" entry-point-ref="authenticationFailedEntryPoint">
          <intercept-url pattern="/**" access="ROLE_USER"/>
          <http-basic entry-point-ref="authenticationFailedEntryPoint" />
      </http>
      

1 个答案:

答案 0 :(得分:4)

我认为问题在于,在基本身份验证失败后,对入口点的调用直接来自BasicAuthenticationFilter,默认情况下是内置实现。

您还需要设置entry-point-ref attribute on the http-basic元素来解决此问题。

或者,您可以将基本身份验证筛选器定义为bean,并完全避免命名空间。