Spring 自定义 AuthenticationFailureHandler

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/15949665/
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

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-08 05:51:57  来源:igfitidea点击:

Spring custom AuthenticationFailureHandler

springspring-mvcspring-security

提问by Nabor

I already try the whole day, to get my custom authentication failure handler to work with Spring 3.1.3.

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

I think it is properly configured

我认为它配置正确

<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" />

My implementation is this

我的实现是这样的

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

}

In the console I see

在控制台中我看到

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

So it is loaded.

所以它被加载了。

With wrong username or passwort, when a BadCredentialsException is thrown, I don't see invalid login.

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

The Method onAuthenticationFailure is never invoked.

方法 onAuthenticationFailure 永远不会被调用。

Instead the service redirects the browser onto /rest/login again and again...

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

Edit

编辑

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

This happens in DEBUG Mode

这发生在调试模式

Where is my mistake?

我的错误在哪里?

回答by zagyi

Judged from the logs you attached I think you've made a mistake in implementing the login process. I cannot be absolutely sure, but I guess you call ProviderManager.authenticate()in your LoginController. The method throws a BadCredentialsExceptionthat causes Spring MVC's exception handling mechanism to kick in, which of course has no knowledge about the AuthenticationFailureHandlerconfigured for Spring Security.

从您所附的日志判断,我认为您在实施登录过程时犯了一个错误。我不能绝对确定,但我猜你调用ProviderManager.authenticate()了你的LoginController. 该方法抛出一个BadCredentialsException导致 Spring MVC 的异常处理机制启动的方法,这当然不了解AuthenticationFailureHandlerSpring Security的配置。

From the login controller you should normally just serve a simple login form with action="j_spring_security_check" method="post". When the user submits that form, the configured security filter (namely UsernamePasswordAuthenticationFilter) intercepts that request and handles authentication. You don't have to implement that logic yourself in a controller method.

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



Reply to your comment:

回复您的评论:

You do use ProviderManager(it's the implementation of the autowired AuthenticationManagerinterface). The mistake you make is that you try to rewrite the logic already implemented and thoroughly tested in auth filters. This is bad in itself, but even that is done in a wrong way. You select just a few lines from a complex logic, and among other things you forget e.g. invoking the session strategy (to prevent session fixation attacks, and handling concurrent sessions). The original implementation invokes the AuthenticationFailureHandleras well, which you also forgot in your method, this is the very reason of the problem your original question is about.

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

So you end up with an untested, brittle solution instead of nicely integrating with the framework to leverage its roboustness and full capacity. As I said, the config you posted in your answer is a definite improvement, because it uses the framework provided filter for authentication. Keep that config and remove LoginController.login(), it won't be called anyway by requests sent to /rest/login.

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

A more fundamental question is if it's really a good solution to use sessions and form-based login mechanism if you implement RESTful services. (On form-based login I mean that the client sends its credentials oncein whatever format, and then gets authenticated by a stateful session on subsequent requests.) With REST services it's more prevalent to keep everything stateless, and re-authenticate each new request by information carried by http headers.

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

回答by Nabor

It's a problem with the order in the security-app-context.xml.

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

If I first define all my beans and then all the rest it works. I tried a lot, so don't wonder, that it now looks a little different then in the question

如果我首先定义我的所有 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>

回答by hemantvsn

(Looking at your requirements), You don't need a custom AuthenticationFailureHandler as the with default SimpleUrlAuthenticationFailureHandler of Spring and properly implementing AuthenticationProvider should serve the purpose.

(查看您的要求),您不需要自定义 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"/>

If you have handled the Exceptions well in Authentication Provider:

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

Sample Logic:

示例逻辑:

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

If we want the exceptions thrown to be reflected at the client interface, add the following scriplet on the JSP responding to authentication-failure-url="/login?authfailed=true"

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

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

回答by David Le Moing

Does not look bad to me. Did you try to use the debug mode of your IDE ?

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

Did you see things like this in your logs :

你有没有在你的日志中看到这样的事情:

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

The AuthenticationFailureHandler will be called automatically, only if the authentication is done in one of the authentication filter : UsernamePasswordAuthenticationFilter normally in your case.

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