4

我已经尝试了一整天,让我的自定义身份验证失败处理程序与 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 时,我看不到无效登录。

永远不会调用方法 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

4 回答 4

3

从您附加的日志来看,我认为您在实施登录过程时犯了一个错误。我不能绝对肯定,但我猜你打电话给ProviderManager.authenticate()你的LoginController. 该方法抛出一个BadCredentialsException导致 Spring MVC 的异常处理机制启动的方法,这当然不知道AuthenticationFailureHandler为 Spring Security 配置的内容。

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


回复您的评论:

您确实使用ProviderManager(它是自动装配AuthenticationManager 接口的实现)。您犯的错误是您尝试重写已在身份验证过滤器中实现和彻底测试的逻辑。这本身就是不好的,但即使这样做也是错误的。您从一个复杂的逻辑中只选择了几行,除此之外,您还忘记了其他一些事情,例如调用会话策略(以防止会话固定攻击和处理并发会话)。原始实现也调用了AuthenticationFailureHandler ,您在方法中也忘记了它,这就是您的原始问题所涉及的问题的原因。

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

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

于 2013-04-11T14:32:04.783 回答
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>
于 2013-04-11T14:26:48.313 回答
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"/>

如果您在 Authentication Provider 中很好地处理了异常:

示例逻辑:

    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.");
    }

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

    <%                      
                    Exception error = (Exception) request.getSession().getAttribute("SPRING_SECURITY_LAST_EXCEPTION");
                   if (error != null)
                    out.write(error.getMessage());
     %>
于 2014-08-06T11:39:02.843 回答
0

在我看来并不坏。您是否尝试使用 IDE 的调试模式?

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

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

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

于 2013-04-11T13:34:15.320 回答