Spring自定义AuthenticationFailureHandler

时间:2013-04-11 13:09:39

标签: spring spring-mvc spring-security

我已经尝试了一整天,让我的自定义身份验证失败处理程序与Spring 3.1.3一起使用。

我认为它配置正确

<http use-expressions="true" disable-url-rewriting="true">
    <intercept-url pattern="/rest/login" access="permitAll" />
    <intercept-url pattern="/rest/**" access="isAuthenticated()" />
    <intercept-url pattern="/index.html" access="permitAll" />
    <intercept-url pattern="/js/**" access="permitAll" />
    <intercept-url pattern="/**" access="denyAll" />
    <form-login username-parameter="user" password-parameter="pass" login-page="/rest/login"
        authentication-failure-handler-ref="authenticationFailureHandler"  />
</http>
<beans:bean id="authenticationFailureHandler" class="LoginFailureHandler" />

我的实现就是这个

public class LoginFailureHandler implements AuthenticationFailureHandler {
    private static final Logger log = LoggerFactory.getLogger(LoginFailureHandler.class);

    public LoginFailureHandler() {
        log.debug("I am");
    }

    @Autowired
    private ObjectMapper customObjectMapper;

    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
            AuthenticationException exception) throws IOException, ServletException {
        log.debug("invalid login");
        User user = new User();
        user.setUsername("invalid");
        try (OutputStream out = response.getOutputStream()) {
            customObjectMapper.writeValue(out, user);
        }
    }

}

在控制台中我看到了

2013-04-11 14:52:29,478 DEBUG LoginFailureHandler - I am

所以它被加载了。

如果用户名或密码错误,当抛出BadCredentialsException时,我看不到无效登录。

永远不会调用Method onAuthenticationFailure。

相反,服务会一次又一次地将浏览器重定向到/ rest / login ...

修改

2013-04-11 15:47:26,411 DEBUG de.pentos.spring.LoginController - Incomming login chuck.norris, norris
2013-04-11 15:47:26,412 DEBUG o.s.s.a.ProviderManager - Authentication attempt using org.springframework.security.authentication.dao.DaoAuthenticationProvider
2013-04-11 15:47:26,415 DEBUG o.s.s.a.d.DaoAuthenticationProvider - Authentication failed: password does not match stored value
2013-04-11 15:47:26,416 DEBUG o.s.w.s.m.m.a.ExceptionHandlerExceptionResolver - Resolving exception from handler [public de.pentos.spring.User de.pentos.spring.LoginController.login(de.pentos.spring.User)]: org.springframework.security.authentication.BadCredentialsException: Bad credentials
2013-04-11 15:47:26,419 DEBUG o.s.w.s.m.a.ResponseStatusExceptionResolver - Resolving exception from handler [public de.pentos.spring.User de.pentos.spring.LoginController.login(de.pentos.spring.User)]: org.springframework.security.authentication.BadCredentialsException: Bad credentials
2013-04-11 15:47:26,419 DEBUG o.s.w.s.m.s.DefaultHandlerExceptionResolver - Resolving exception from handler [public de.pentos.spring.User de.pentos.spring.LoginController.login(de.pentos.spring.User)]: org.springframework.security.authentication.BadCredentialsException: Bad credentials
2013-04-11 15:47:26,426 DEBUG o.s.web.servlet.DispatcherServlet - Could not complete request
org.springframework.security.authentication.BadCredentialsException: Bad credentials

这在调试模式中发生

我的错误在哪里?

4 个答案:

答案 0 :(得分:3)

根据您附加的日志判断我认为您在实施登录过程时犯了一个错误。我不能完全确定,但我猜您在ProviderManager.authenticate()中致电LoginController。该方法抛出一个BadCredentialsException导致Spring MVC的异常处理机制启动,当然它不知道为Spring Security配置的AuthenticationFailureHandler

通过登录控制器,您通常只需提供一个包含action="j_spring_security_check" method="post"的简单登录表单。当用户提交该表单时,配置的安全筛选器(即UsernamePasswordAuthenticationFilter)将拦截该请求并处理身份验证。您不必在控制器方法中自己实现该逻辑。


回复你的评论:

您确实使用ProviderManager(它是自动连接的AuthenticationManager接口的实现)。您犯的错误是您尝试重写已经实现的逻辑并在auth过滤器中进行彻底测试。这本身就很糟糕,但即使这样做也是错误的。您只需从复杂的逻辑中选择几行,除此之外您还会忘记例如调用会话策略(以防止会话固定攻击和处理并发会话)。原始实现调用AuthenticationFailureHandler 同样,你也忘记了你的方法,这就是你原来问题的问题的原因。

因此,您最终会得到一个未经测试的脆弱解决方案,而不是与框架很好地集成,以充分利用其抢夺性和全部容量。正如我所说,您在答案中发布的配置是一个明确的改进,因为它使用框架提供的过滤器进行身份验证。保留该配置并删除LoginController.login(),发送给/rest/login的请求无论如何都不会调用它。

一个更基本的问题是,如果您实施RESTful服务,它是否真的是一个使用会话和基于表单的登录机制的好解决方案。 (在基于表单的登录时,我的意思是客户端以任何格式发送其凭据一次,然后通过有状态会话对后续请求进行身份验证。)使用REST服务,保持所有无状态更为普遍,并通过http标头携带的信息重新验证每个新请求。

答案 1 :(得分:1)

这是security-app-context.xml中的顺序问题。

如果我先定义所有的bean,那么所有其余的都可以。 我尝试了很多,所以不要怀疑,它现在看起来与问题

有点不同
<beans:bean id="authenticationProcessingFilterEntryPoint" class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint">
    <beans:property name="loginFormUrl" value="/rest/login" />
</beans:bean>

<beans:bean id="authenticationFilter" class="org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter">
    <beans:property name="authenticationManager" ref="authenticationManager" />
    <beans:property name="filterProcessesUrl" value="/rest/login" />
    <beans:property name="authenticationSuccessHandler" ref="authenticationSuccessHandler" />
    <beans:property name="authenticationFailureHandler" ref="authenticationFailureHandler" />
</beans:bean>

<beans:bean id="authenticationSuccessHandler" class="de.pentos.spring.LoginSuccessHandler" />
<beans:bean id="authenticationFailureHandler" class="de.pentos.spring.LoginFailureHandler" />

<http use-expressions="true" disable-url-rewriting="true" entry-point-ref="authenticationProcessingFilterEntryPoint"
    create-session="ifRequired">
    <intercept-url pattern="/rest/login" access="permitAll" />
    <intercept-url pattern="/rest/**" access="isAuthenticated()" />
    <intercept-url pattern="/index.html" access="permitAll" />
    <intercept-url pattern="/js/**" access="permitAll" />
    <intercept-url pattern="/**" access="denyAll" />
    <custom-filter position="FORM_LOGIN_FILTER" ref="authenticationFilter" />
</http>

<authentication-manager alias="authenticationManager">
    <authentication-provider>
        <user-service>
            <user name="chuck.norris" password="cnorris" authorities="ROLE_ADMIN" />
            <user name="user" password="user" authorities="ROLE_USER" />
        </user-service>
    </authentication-provider>
</authentication-manager>

答案 2 :(得分:0)

对我来说不好看。您是否尝试使用IDE的调试模式?

您是否在日志中看到了类似的内容:

Authentication request failed: ...
Updated SecurityContextHolder to contain null Authentication
Delegating to authentication failure handler ...

只有在其中一个身份验证过滤器中完成身份验证时,才会自动调用AuthenticationFailureHandler:通常情况下是UsernamePasswordAuthenticationFilter。

答案 3 :(得分:0)

(查看您的要求),您不需要使用自定义AuthenticationFailureHandler作为Spring的默认SimpleUrlAuthenticationFailureHandler,并且正确实现AuthenticationProvider应该可以实现此目的。

 <form-login login-page="/login" login-processing-url="/do/login" authentication-  failure-url ="/login?authfailed=true" authentication-success-handler-ref ="customAuthenticationSuccessHandler"/>

如果您已在身份验证提供程序中处理好例外:

示例逻辑:

    String loginUsername = (String) authentication.getPrincipal();
    if (loginUsername == null)
        throw new UsernameNotFoundException("User not found");

    String loginPassword = (String) authentication.getCredentials();

    User user = getUserByUsername(loginUsername);
    UserPassword password = getPassword(user.getId());

    if (!password.matches(loginPassword)) {
        throw new BadCredentialsException("Invalid password.");
    }

如果我们希望抛出的异常反映在客户端界面上,请在JSP上添加以下scriplet,以响应 authentication-failure-url =“/ login?authfailed = true”

    <%                      
                    Exception error = (Exception) request.getSession().getAttribute("SPRING_SECURITY_LAST_EXCEPTION");
                   if (error != null)
                    out.write(error.getMessage());
     %>