24

We use RequestHeaderAuthenticationFilter as to implement pre-authentication strategy and PreAuthenticatedAuthenticationProvider as the authentication provider. One of the requirements is to store all successful logins to the database with following information. As user IP address and other request related info is not available in UserDetailsService class, what is the best strategy to retrieve this info and store in db?

stivlo
  • 83,644
  • 31
  • 142
  • 199
RKodakandla
  • 3,318
  • 13
  • 59
  • 79

2 Answers2

41

All the information is available through HttpServletRequest. You can obtain it by:

Dependency injection

The easiest way would be to inject servlet request directly into your UserDetailsService: class:

public MyDetailsService implements UserDetailsService {

  @Autowired
  private HttpServletRequest request;

  //...

}

(as suggested by OP) Remember to add the following listener to your web.xml:

<listener>
    <listener-class>
        org.springframework.web.context.request.RequestContextListener
    </listener-class>
</listener>

UPDATE: This works because Spring injects special scoped proxy implementing HttpServletRequest, so you are able to access request-scoped request "bean" from singleton-scoped MyDetailsService. Under the hood every call to request's parameters is routed to org.springframework.web.context.request.RequestContextHolder#requestAttributesHolder ThreadLocal which you can also access directly. As you can see Spring is very flexible when it comes to scoping rules. It just works.

RequestContextHolder

Another approach is to use RequestContextHolder:

HttpServletRequest request = 
  ((ServletRequestAttributes) RequestContextHolder.
    currentRequestAttributes()).
    getRequest();

Further reading:

Community
  • 1
  • 1
Tomasz Nurkiewicz
  • 334,321
  • 69
  • 703
  • 674
  • thanks for the reply Tomasz, I haven't tried your solution yet but one question.. Isn't custom UserDetailsService a sesion-scoped bean? I thought an object of this type gets created only when there is a new session or the existing session expires. If it is indeed a session-scoped bean then how can we inject HttpServletRequest which is a request-scoped? If UserDetailsService is request based then how is that call to the loadUser..() method is not made for every request? – RKodakandla Oct 20 '11 at 17:04
  • @Tomasz, would this work? Is UserDetailsService typically a request-scoped bean? – Peter Mularien Oct 20 '11 at 17:22
  • 1
    *@rrkwells*, *@Peter Mularien*: `UserDetailsService ` should always be a singleton and `HttpServletRequest` is request scoped. This works thanks to extra Spring `scoped-proxy` magic applied transparently. Also see my update. Don't worry and just try it :-). – Tomasz Nurkiewicz Oct 21 '11 at 06:58
  • @Tomasz.. sorry for not replying earlier.. I just tested your first solution today and it is working as expected. I just had to add the request context listener in the web.xml file. Thanks a lot for your help. – RKodakandla Oct 27 '11 at 08:15
  • Glad I could help, I added `RequestContextListener` to my answer to be complete, thanks! – Tomasz Nurkiewicz Oct 27 '11 at 08:19
5

This might be a good approach:

1) Create a class that extends SavedRequestAwareAuthenticationSuccessHandler

public class MyCustomSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
            Authentication authentication) throws ServletException, IOException {

2) Assign the "success handler" to your security filter:

<beans:bean id="myFilter" class="org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter">
        <beans:property name="authenticationManager" ref="authenticationManager"/>
        <beans:property name="authenticationFailureHandler" ref="failureHandler" />
               <beans:property name="authenticationSuccessHandler" ref="successHandler" />
    </beans:bean>

<beans:bean id="successHandler" class="yourPackage.MyCustomSuccessHandler" >
        <beans:property name="defaultTargetUrl" value="/index.html" /> 
        <beans:property name="alwaysUseDefaultTargetUrl" value="true"/> 
    </beans:bean>
Dani Cricco
  • 8,829
  • 8
  • 31
  • 35