Log user in with remember-me functionality in Spring 3.1
Asked Answered
O

2

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.

Overtly answered 18/10, 2011 at 12:5 Comment(2)
Do you mean you just want to set the Authentication in the SecurityContextHolder? Or do you need set the remember-me cookie too?Carrico
I want to log the person in and my app remembers them so they don't have to login again.Overtly
C
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);  
Carrico answered 18/10, 2011 at 17:32 Comment(20)
In my security XML configuration, I simply have <remember-me token-validity-seconds="31536000" data-source-ref="dataSource"/> within the <http> 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.Overtly
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."Overtly
Which version of Spring Security are you using? I was referring to org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices for 3.0.Carrico
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.Overtly
Sorry, I meant onLoginSuccess(), not loginSuccess().Carrico
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.Carrico
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...Overtly
Pass the Authentication object that you created in your controller. Subclass PersistentTokenBasedRememberMeServices and make onLoginSuccess() public.Carrico
What authentication object I created in the controller? How do I create this? That's my whole original question!Overtly
The UsernamePasswordAuthentiationToken. SecurityContextHolder.getContext().setAuthentication( new UsernamePasswordAuthenticationToken(user, "", authorities) );Carrico
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.Overtly
How do you get a handle on the HTTPServletRequest and response? Is this code part of the FilterChain?Baroda
Yes, they are parameters to the doFilter method.Carrico
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: #16042185Baroda
What class/method are you in when you need to access the request/response?Carrico
See #3321174Carrico
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);Baroda
I can get the request, but not the ServletResponse.Baroda
No need to extend class or use wrapper. Just set alwaysRemember=true on PersistentTokenBasedRememberMeServices. Unfortunately it seems that it can't be done via <remember-me> 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 recordJahdal
Спасибо, 2 дня пытался натсроить remember-me.Geomancy
P
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.

Provitamin answered 18/10, 2011 at 13:38 Comment(1)
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?Overtly

© 2022 - 2024 — McMap. All rights reserved.