17

I currently log users in programmatically (like when they login through Facebook or other means than using my login form) with:

SecurityContextHolder.getContext().setAuthentication(
  new UsernamePasswordAuthenticationToken(user, "", authorities)
);

What I want to do instead is log the user in as if they set the remember-me option on in the login form. So I'm guessing I need to use the RememberMeAuthenticationToken instead of the UsernamePasswordAuthenticationToken? But what do I put for the key argument of the constructor?

RememberMeAuthenticationToken(String key, Object principal, Collection<? extends GrantedAuthority> authorities) 

UPDATE: I'm using the Persistent Token Approach described here. So there is no key like in the Simple Hash-Based Token Approach.

at.
  • 50,922
  • 104
  • 292
  • 461
  • Do you mean you just want to set the Authentication in the SecurityContextHolder? Or do you need set the remember-me cookie too? – sourcedelica Oct 18 '11 at 15:15
  • I want to log the person in and my app remembers them so they don't have to login again. – at. Oct 18 '11 at 16:52

2 Answers2

14

I assume you already have <remember-me> set in your configuration.

The way remember-me works is it sets a cookie that is recognized when the user comes back to the site after their session has expired.

You'll have to subclass the RememberMeServices (TokenBased or PersistentTokenBased) you are using and make the onLoginSuccess() public. For example:

public class MyTokenBasedRememberMeServices extends PersistentTokenBasedRememberMeServices {
    @Override
    public void onLoginSuccess(HttpServletRequest request, HttpServletResponse response, Authentication successfulAuthentication) {
        super.onLoginSuccess(request, response, successfulAuthentication);
    }   
} 

<remember-me services-ref="rememberMeServices"/>

<bean id="rememberMeServices" class="foo.MyTokenBasedRememberMeServices">
    <property name="userDetailsService" ref="myUserDetailsService"/>
    <!-- etc -->
</bean>

Inject your RememberMeServices into the bean where you are doing the programmatic login. Then call onLoginSuccess() on it, using the UsernamePasswordAuthenticationToken that you created. That will set the cookie.

UsernamePasswordAuthenticationToken auth = 
    new UsernamePasswordAuthenticationToken(user, "", authorities);
SecurityContextHolder.getContext().setAuthentication(auth);
getRememberMeServices().onLoginSuccess(request, response, auth);  

UPDATE

@at improved upon this, with no subclassing of RememberMeServices:

UsernamePasswordAuthenticationToken auth = 
    new UsernamePasswordAuthenticationToken(user, "", authorities);
SecurityContextHolder.getContext().setAuthentication(auth);

// This wrapper is important, it causes the RememberMeService to see
// "true" for the "_spring_security_remember_me" parameter.
HttpServletRequestWrapper wrapper = new HttpServletRequestWrapper(request) {
    @Override public String getParameter(String name) { return "true"; }            
};

getRememberMeServices().loginSuccess(wrapper, response, auth);  
JBCP
  • 13,109
  • 9
  • 73
  • 111
sourcedelica
  • 23,940
  • 7
  • 66
  • 74
  • In my security XML configuration, I simply have `` within the `` element. Where is the `TokenBasedRememberMeServices` bean defined? Using this mechanism, do I not need to mess with the SecurityContextHolder anymore? Sounds like a much better solution in general to programmatically logging someone in. – at. Oct 18 '11 at 21:05
  • I assume that I need the `PersistentTokenBasedRememberMeServices` instead of `TokenBasedRememberMeServices`. But either way and if I can inject it into the controller where I'm trying to log someone in with remember-me functionality, `loginSuccess()` doesn't seem what I want. Javadocs say, "Examines the incoming request and checks for the presence of the configured "remember me" parameter. If it's present, or if alwaysRemember is set to true, calls onLoginSucces." – at. Oct 18 '11 at 22:52
  • Which version of Spring Security are you using? I was referring to org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices for 3.0. – sourcedelica Oct 18 '11 at 23:22
  • I mentioned in the title of my question, 3.1. I think it's the same `TokenBasedRememberMeServices` for hash-based tokens. But As I mentioned in my question, I'm using the Persistent token-based approach. – at. Oct 18 '11 at 23:44
  • Sorry, I meant onLoginSuccess(), not loginSuccess(). – sourcedelica Oct 19 '11 at 02:54
  • 1
    You can do that for either the Persistent or regular versions of the `TokenBasedRememberMeServices`. I just checked the 3.1 source (sorry, I missed that) and it is the same. – sourcedelica Oct 19 '11 at 03:01
  • `onLoginSuccess()` is a protected method. And it takes an `Authentication` object, do I just pass a `RememberMeAuthenticationToken`? Then I'm right back to where I started... – at. Oct 19 '11 at 08:02
  • Pass the Authentication object that you created in your controller. Subclass `PersistentTokenBasedRememberMeServices` and make `onLoginSuccess()` public. – sourcedelica Oct 19 '11 at 13:20
  • What authentication object I created in the controller? How do I create this? That's my whole original question! – at. Oct 19 '11 at 16:45
  • The UsernamePasswordAuthentiationToken. SecurityContextHolder.getContext().setAuthentication( new UsernamePasswordAuthenticationToken(user, "", authorities) ); – sourcedelica Oct 19 '11 at 21:14
  • Got everything to work, but by using loginSuccess instead and passing an overridden HttpServletRequestWrapper which returns "true" for getParameter("_spring_security_remember_me"). Spring Security certainly doesn't make this seemingly very common functionality intuitive. – at. Oct 23 '11 at 06:02
  • How do you get a handle on the HTTPServletRequest and response? Is this code part of the FilterChain? – pkrish Apr 16 '13 at 18:08
  • Yes, they are parameters to the `doFilter` method. – sourcedelica Apr 16 '13 at 18:18
  • Thanks! I am doing the setAuthentication as part of a POJO class, where I do not have a handle on the HTTPServletRequest and Response. How do I deal with this issue? I have asked this question here: http://stackoverflow.com/questions/16041685/set-spring-security-remember-me-cookie-after-login-via-facebook – pkrish Apr 16 '13 at 19:08
  • What class/method are you in when you need to access the request/response? – sourcedelica Apr 16 '13 at 20:32
  • See http://stackoverflow.com/questions/3320674/spring-how-do-i-inject-an-httpservletrequest-into-a-request-scoped-bean – sourcedelica Apr 16 '13 at 20:34
  • The control is in the class that gets called-back once Facebook.com finishes auth and returns the access_token. At this point, I create the UsernamePasswordAuthenticationToken and set auth. But don't know how to do the last step getRememberMeServices().loginSuccess(wrapper, response, auth); – pkrish Apr 17 '13 at 19:54
  • I can get the request, but not the ServletResponse. – pkrish Apr 17 '13 at 19:55
  • 1
    No need to extend class or use wrapper. Just set alwaysRemember=true on PersistentTokenBasedRememberMeServices. Unfortunately it seems that it can't be done via XML element, so you would need to define bean PersistentTokenBasedRememberMeServices and set that property to true. After that normal rememberMeServices.loginSuccess(req, res, auth) creates cookie and persistent record – maximdim Aug 13 '15 at 03:43
  • Спасибо, 2 дня пытался натсроить remember-me. – Bleser Jan 08 '17 at 12:06
2

This is the source for the constructor.

public RememberMeAuthenticationToken(String key, Object principal, Collection<? extends GrantedAuthority> authorities) {
    super(authorities);

    if ((key == null) || ("".equals(key)) || (principal == null) || "".equals(principal)) {
        throw new IllegalArgumentException("Cannot pass null or empty values to constructor");
    }

    this.keyHash = key.hashCode();
    this.principal = principal;
    setAuthenticated(true);
}

The key is hashed and its used to determine whether the authentication used for this user in the security context is not a 'forged' one.

Have a look at the RememberMeAuthenicationProvider source.

public Authentication authenticate(Authentication authentication) throws AuthenticationException {
    if (!supports(authentication.getClass())) {
        return null;
    }

    if (this.key.hashCode() != ((RememberMeAuthenticationToken) authentication).getKeyHash()) {
        throw new BadCredentialsException(messages.getMessage("RememberMeAuthenticationProvider.incorrectKey",
                "The presented RememberMeAuthenticationToken does not contain the expected key"));
    }

    return authentication;
}

So to answer your question, you need to pass the hash code of the key field of the Authentication representing the user.

Simeon
  • 7,582
  • 15
  • 64
  • 101
  • I still have no idea what to do.. how do I get the `Authentication` representing the user. And then what exactly do I pass as the key argument? – at. Oct 18 '11 at 16:52