8

I'm using Spring 3.2.0 and the same version of Spring security. On successful login, a user is redirected to one of the protected pages as follows.

public final class LoginSuccessHandler implements AuthenticationSuccessHandler
{
    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException
    {
        Set<String> roles = AuthorityUtils.authorityListToSet(authentication.getAuthorities());
        if (roles.contains("ROLE_ADMIN"))
        {
            response.sendRedirect("admin_side/Home.htm");
            return;
        }
    }
}

I'm using Hibernate. How can I update the login date-time (Last Login) in the database on successful login? I have a submit button on the login page whose POST request doesn't seem to map to a method in its corresponding login controller. The login form's action is actually mapped to the Servlet - j_spring_security_check.


The entire spring-security.xml file is as follows, if it is required.

<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/security"
  xmlns:beans="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
           http://www.springframework.org/schema/security
           http://www.springframework.org/schema/security/spring-security-3.1.xsd">

    <http pattern="/Login.htm*" security="none"></http>    

    <http auto-config='true'>
    <!--<remember-me key="myAppKey"/>-->
        <session-management session-fixation-protection="newSession">
            <concurrency-control max-sessions="1" error-if-maximum-exceeded="true" />
        </session-management>

        <intercept-url pattern="/admin_side/**" access="ROLE_ADMIN" requires-channel="any"/>
        <form-login login-page="/" default-target-url="/admin_side/Home.htm" authentication-failure-url="/LoginFailed.htm" authentication-success-handler-ref="loginSuccessHandler"/>
        <logout logout-success-url="/Login.htm" invalidate-session="true" delete-cookies="JSESSIONID"/>
    </http>

    <authentication-manager>
       <authentication-provider>
            <jdbc-user-service data-source-ref="dataSource"
               users-by-username-query="select email_id, password, enabled from user_table where lower(email_id)=lower(?)"
               authorities-by-username-query="select ut.email_id, ur.authority from user_table ut, user_roles ur where ut.user_id=ur.user_id and lower(ut.email_id)=lower(?)"/>
       </authentication-provider>
    </authentication-manager>

    <beans:bean id="loginSuccessHandler" class="loginsuccesshandler.LoginSuccessHandler"/>

    <global-method-security>
        <protect-pointcut expression="execution(* dao.*.*(..))" access="ROLE_ADMIN"/>
    </global-method-security>

    <!--<global-method-security secured-annotations="enabled" />-->
</beans:beans>
Tiny
  • 27,221
  • 105
  • 339
  • 599

3 Answers3

24

An other way is to register an handler for the AuthenticationSuccessEvent.

    @Service
    public class UserService implements
                             ApplicationListener<AuthenticationSuccessEvent> {

        @Override
        public void onApplicationEvent(AuthenticationSuccessEvent event) {
           String userName = ((UserDetails) event.getAuthentication().
                                                  getPrincipal()).getUsername();
           User user = this.userDao.findByLogin(userName);
           user.setLastLoginDate(new Date());
        }
   }
riddle_me_this
  • 8,575
  • 10
  • 55
  • 80
Ralph
  • 118,862
  • 56
  • 287
  • 383
  • Does this require some XML configurations? The `onApplicationEvent()` method was never invoked, when I tried. Additionally, how is this `userDao` object available here? In this case, I don't know where actually the thing is to be implemented rather than how it is implemented. – Tiny Mar 19 '13 at 08:52
  • Going through [this](http://stackoverflow.com/a/182203/1391249) answer, I have registered it in the `applicationContext.xml` file as a bean and the event triggered but I'm not sure how to make `userDao` available. Using `@Autowied`? When I tried to auto-wire, I received - `An Authentication object was not found in the SecurityContext` – Tiny Mar 19 '13 at 09:42
  • how you inject your dao is not the scope of this answer, but @Autowire should work. "An Authentication object was not found in the SecurityContext" did you tryed to inject it? – Ralph Mar 19 '13 at 10:57
  • I'm trying to autowire like - `@Autowired private UserSevice userSevice;` in the class that implements `ApplicationListener`. – Tiny Mar 19 '13 at 11:04
  • When I put a debugging statement like `System.out.println("userName = "+userSevice.getUser(userName).getFirstName());`, it fails with this message - `An Authentication object was not found in the SecurityContext` as soon as the submit button is pressed. When I remove this statement, the event is triggered and it works. Autowiring doesn't seem to work. – Tiny Mar 19 '13 at 11:14
  • Add an System.out.println(event.getAuthentication()); -- if it is an shows up an anonymus user, than you have to filter out this event – Ralph Mar 19 '13 at 15:40
  • `System.out.println(event.getAuthentication());` displays - – Tiny Mar 19 '13 at 15:48
  • `org.springframework.security.authentication.UsernamePasswordAuthenticationToken@bad860a5: Principal: org.springframework.security.core.userdetails.User@586034f: Username: admin; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: ROLE_ADMIN; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@fffdaa08: RemoteIpAddress: 127.0.0.1; SessionId: AEE54A59C9181C05A176D12E27D88CB1; Granted Authorities: ROLE_ADMIN` – Tiny Mar 19 '13 at 15:49
  • Both the username and the password in my database are `admin`. – Tiny Mar 19 '13 at 15:52
  • "An Authentication object was not found in the SecurityContext" -- This must come from some other part of the program, not from this event handler. Could you please add the complete stacktrace. – Ralph Mar 19 '13 at 18:41
  • The exception is consumed under the hood. Therefore, it is not available. This messages is through `sessionScope["SPRING_SECURITY_LAST_EXCEPTION"].message`. I really don't know how to display the full exception stacktrace. Is it somehow possible to see the complete exception stacktrace? – Tiny Mar 20 '13 at 04:09
  • When I comment this section `...` in my security config file, then it works as intended. So, it has to do with the method security. Regarding this, I have several questions in my mind. Is it possible to authenticate from this event when the global method security is enabled? Should/can DAO services **not** be used from this event, when the global method security is enabled...??? and alike... – Tiny Mar 20 '13 at 04:16
  • Additionally, I originally declared all the DAO classes `final`. I have removed the `final` keyword and tried to use proxy. Accordingly, I have changed `` to `` but that didn't make any difference. The only thing that made it work is by commenting out this global method security in the security config file. Kindly, tell me the way to get around this. – Tiny Mar 20 '13 at 04:25
  • Or Is it not possible at all to use DAO services from outside the controller classes which are annotated with `@Controller`, when the global method security is enabled? I'm fully confused. – Tiny Mar 20 '13 at 04:29
  • @Tiny, please open an new question, with all relevant details. (You can additional refer this question). So every body can read it, and maybe someone else but me knows the answer. – Ralph Mar 20 '13 at 06:33
  • Thanks. I have already posted such a question [here](http://stackoverflow.com/q/15500519/1391249). – Tiny Mar 20 '13 at 06:35
  • You can invoke services, controllers, repositories in the same way with and without `` enabled. But it looks like you have some method security constraint somewhere. And I guess this protected method is invoked BEFORE the user authentication is complete don. – Ralph Mar 20 '13 at 06:36
  • I have closed this discussion by accepting your answer. Many thanks. – Tiny Mar 20 '13 at 06:38
  • Just one thing. Are you using the Spring framework 3.2.0? Does it really work with this version? Until now, I couldn't find anything wrong anywhere in my original application. I could smell it like a jira issue in the version 3.2.0. – Tiny Mar 20 '13 at 10:37
  • Just for completeness: An anonymous user suggested to use the `InteractiveAuthenticationSuccessEvent` instead of `AuthenticationSuccessEvent`, unfortunately he direct edided the answer without any notice, so his edit was rejected because it modified the answer to much. – Ralph Nov 01 '14 at 16:11
  • In my case, I was advised to use `@Component` annotation, but the reality is that `@Service` especialisation must be used. Thank you! @Ralph – joninx Jan 24 '17 at 11:34
  • https://stackoverflow.com/questions/33094174/how-to-change-update-column-name-in-table-using-mysql-workbench – Llama May 20 '20 at 22:05
  • @Llama - what is with this question? – Ralph May 21 '20 at 10:05
7

Why do not do it in authentication success handler directly?

public final class LoginSuccessHandler implements AuthenticationSuccessHandler
{
    @Autowired
    private UserService userService;

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException
    {
        String userName = authentication.getPrincipal().getName();
        this.userService.updateLastLoginDateForUserByName(userName);

        Set<String> roles = AuthorityUtils.authorityListToSet(authentication.getAuthorities());
        if (roles.contains("ROLE_ADMIN"))
        {
            response.sendRedirect("admin_side/Home.htm");
            return;
        }
    }
}
Maksym Demidas
  • 7,707
  • 1
  • 29
  • 36
  • This approach worked but it failed to work with `ApplicationListener` indicating - `An Authentication object was not found in the SecurityContext` while trying to autowire the `UserService` interface as Ralph's answer which I'm more interested in. Do you know why? – Tiny Mar 19 '13 at 11:18
  • Strange. Make brekepoints in SecurityContextHolder class (clearContext(), getContext(), etc...) and debug it – Maksym Demidas Mar 19 '13 at 11:25
  • @Tiny, same problem here with Ralph's solution. (I am a newbie!) – Eric Jan 14 '14 at 20:48
  • @Eric : I cannot clarify it but `AuthenticationSuccessHandler` is sufficient (as this answer indicates), if some operations need to be performed as soon as login is made successfully (I was more intersted in knowing why doesn't `ApplicationListener` work after successful login as [this](http://stackoverflow.com/q/15500519/1391249) question of mine indicates). – Tiny Jan 15 '14 at 12:27
2

You could also subclass the spring AuthenticationProvider interface and inject it into the <authentication-manager /> element.

The class would be something like

public class AuthenticationProvider extends DaoAuthenticationProvider {

    // inject whatever tou want

    @Override
    public Authentication authenticate(Authentication authentication)
                throws AuthenticationException {
            return super.authenticate(authentication);

            // do what ever you want here
    }

}

(supposing you're using DaoAuthenticationProvider)

Then you just need to register beans

<bean class="x.y.z.AuthenticationProvider" id="myAuthProvider" scope="singleton" />
<authentication-manager>
   <authentication-provider ref="myAuthProvider">
        <jdbc-user-service data-source-ref="dataSource"
           users-by-username-query="select email_id, password, enabled from user_table where lower(email_id)=lower(?)"
           authorities-by-username-query="select ut.email_id, ur.authority from user_table ut, user_roles ur where ut.user_id=ur.user_id and lower(ut.email_id)=lower(?)"/>
   </authentication-provider>
</authentication-manager>

(Don't trust code correctness, I wrote it on the fly. It's just meant to show my idea.)

Stefano

Stefano Cazzola
  • 1,597
  • 1
  • 20
  • 36