4

I already try the whole day, to get my custom authentication failure handler to work with 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.

The Method onAuthenticationFailure is never invoked.

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

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?

4 Answers 4

3

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 BadCredentialsException that causes Spring MVC's exception handling mechanism to kick in, which of course has no knowledge about the AuthenticationFailureHandler configured for Spring 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.


Reply to your comment:

You do use ProviderManager (it's the implementation of the autowired AuthenticationManager interface). 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 AuthenticationFailureHandler as well, which you also forgot in your method, this is the very reason of the problem your original question is about.

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.

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 once in 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.

Sign up to request clarification or add additional context in comments.

7 Comments

I want to, cause Login should not be available using POST on a form. The Idea is, to allow Login only when PUT a Credentials object on the REST resource /rest/login. I think the order of xml definitions was the reason.
The config you posted in your answer shows that requests sent to /rest/login will be intercepted by the UsernamePasswordAuthenticationFilter (because of the filterProcessesUrl attribute). It's irrelevant if the request is a form POST sent by a browser, or a PUT sent by some other REST client. For sure they won't hit any handler method mapped to the same URI, but will be processed by the above mentioned filter. This is why your config works now (previously your handler method was in control), and not because the ordering of some beans in xml, which doesn't make any difference.
I don't use ProviderManager as you can see here pastebin.com/N1m02Cyx Can you please explain from this class, where my mistake is?
My reply would have been too long, please check my updated answer.
I'm totally lost in trying to implement this. I read so many different solutions trying to find my way to this jungle. Is it possible to discus the solution in more detail in a chat or so?
|
1

It's a problem with the order in the 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

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

Comments

0

Does not look bad to me. Did you try to use the debug mode of your 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.

2 Comments

Stupid question, is the login handled by the LoginController ?
Did you put some breakpoints in UsernamePasswordAuthenticationFilter and AbstractAuthenticationProcessingFilter ?
0

(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.

 <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:

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"

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.