I'm using spring security to implement a programmatic, manual user login. I have a scenario where I have positively established the user's identity, and wish to log them in. I don't know their password, and so can't use the regular login code path where you submit a form to a url, which spring intercepts via a servlet Filter, doing all of it's auth+session magic.
I've searched, and it seems most people create their own Authentication object, and then tell spring about via:
PreAuthenticatedAuthenticationToken authentication = new PreAuthenticatedAuthenticationToken(user, "", user.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authentication);
Indeed, this works. Spring even puts it into the session for me, making subsequent http requests maintain their auth status.
However, I feel like this is a dirty hack. I'll present some details that I hope will give concrete examples of the problems associated with using setAuthentication() inside a controller to achieve a manual login:
To give an idea, my config is:
httpSecurity
.authorizeRequests()
.antMatchers("/test/**").permitAll()
.antMatchers("/admin/**", "/api/admin/**").hasRole("USER_SUPER_ADMIN")
.and()
.formLogin()
.loginPage("/sign-in?sp")
.loginProcessingUrl("/api/auth/sign-in")
.successHandler(createLoginSuccessHandler())
.failureHandler(createLoginFailureHandler())
.permitAll()
.and()
.logout()
.logoutUrl("/api/auth/sign-out")
.logoutSuccessHandler(createLogoutSuccessHandler())
.and()
.sessionManagement()
.maximumSessions(1)
.maxSessionsPreventsLogin(true)
.sessionRegistry(sessionRegistry)
;
Key points in the above config:
I use custom success and failure handlers for the form login
I want to config behavior for max concurrent sessions per user
I want to maintain spring's default session fixation protection (changing session id upon login).
I want to use a session registry
... more of these session/login functionalities, had I chosen to config it.
I stepped through the code to see how spring processes a form login. As expected, Spring does all the session/login functionalities that my HttpSecurity config told it to do when I use the form login. But, when I do my own custom/manual login via SecurityContextHolder.getContext().setAuthentication(), it does NONE of those functionalities. This is because spring does all of it's session/login functionalities stuff inside of a servlet Filter, and my programmatic code can't really call a Filter. Now, I can attempt to add the missing functionalities myself, duplicating their code: I see that the spring Filter uses: ConcurrentSessionControlAuthenticationStrategy, ChangeSessionIdAuthenticationStrategy, and RegisterSessionAuthenticationStrategy. I can create these objects myself, configure them, and call them after my custom login. But, that's really lame to duplicate all that spring code. Furthermore, there's still other behaviors I'm missing - I noticed that when using the form login code path, that spring triggers some login events which don't get triggered when I do my custom login. And there's probably other stuff that I'm missing or don't understand. The whole process is pretty complicated, and I feel like there's a high chance of introducing bugs if this isn't done right, not to mention that library updates would be a pain if I started duplicating spring code.
So, I feel like I'm approaching this from the wrong way. Should I be using a different strategy, so that I'm not bypassing so much of the stuff that spring does for me? Maybe I should try to make my own AuthenticationProvider to accomplish this custom login?
*To clarify, my code more or less works. But, I feel like I accomplished it using a poor strategy because I had to write code duplicating a lot of stuff that spring does for me. Further, my code doesn't perfectly replicate what spring does, making me wonder what negative implications might result. There must be a better way to programatically achieve login.
I wanted to elaborate on how I implemented the advice of dur. In my scenario, I only used a custom AuthenticationProvider.
Instead of creating a custom servlet Filter, such as extending AbstractAuthenticationProcessingFilter, which seemed like a lot of work, I choose to instead use the following strategy:
At the point in my code where I was confident that I had identified the user, and wanted them to be "logged in", I stuck a flag in the user's session, marking that they should be logged in on the next request, along with any other identity/bookkeeping info I needed, such as their username.
Then, I told the browser client to make an http post to the loginProcessingUrl (the same one I configured spring security to use for form-based login), telling them to send the standard username and password form params, although they don't need to send real values - dummy values like foo are fine.
When the user makes that post request (eg to /login), spring will invoke my custom AuthenticationProvider, which will look in the user's session to check for the flag, and to gather the username. Then it will create and return an Authentication object, such as PreAuthenticatedAuthenticationToken, which identifies the user.
Spring will handle the rest. The user is now logged in.
By doing it this way, you stay within the "normal" way of doing logins, and so spring will still automatically:
Call any custom success and failure handlers you configured for the form login, which is nice if you use that place to do certain things on login, like query or update a db.
It will respect any max concurrent sessions per user settings that you may be using.
You get to keep spring's default session fixation attack protection (changing session id upon login).
If you set a custom session timeout, eg via server.session.timeout in a properties file, spring will use it. There's probably other session config attributes that are done at this time too.
If you enabled spring's "remember me" functionality, it will work.
It will fire a login event, which is used for other spring components, such as storing the user's session in a SessionRegistry. I think the events are also used by other parts of spring, such as the actuator, and for auditing.
When I first tried just doing the typically recommended SecurityContextHolder.getContext().setAuthentication(authentication) to login my user, instead of the custom AuthenticationProvider, none of the above bullets were done for me, which can utterly break your app... or cause subtle security bugs - neither are good.
Here's some code to help solidify what I said:
Custom AuthenticationProvider
#Component
public class AccountVerificationAuthenticationProvider implements AuthenticationProvider {
#Autowired
private AppAuthenticatedUserService appAuthenticatedUserService;
#Autowired
private AuthService authService;
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
// This will look in the user's session to get their username, and to make sure the flag is set to allow login without password on this request.
UserAccount userAccount = authService.getUserAccountFromRecentAccountVerificationProcess();
if (userAccount == null) {
// Tell spring we can't process this AuthenticationProvider obj.
// Spring will continue, and try another AuthenticationProvider, if it can.
return null;
}
// A service to create a custom UserDetails object for this user.
UserDetails appAuthenticatedUser = appAuthenticatedUserService.create(userAccount.getEmail(), "", true);
PreAuthenticatedAuthenticationToken authenticationToken = new PreAuthenticatedAuthenticationToken(appAuthenticatedUser, "", appAuthenticatedUser.getAuthorities());
authenticationToken.setAuthenticated(true);
return authenticationToken;
}
#Override
public boolean supports(Class<?> authentication) {
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
}
Config spring security to use the provider
// In your WebSecurityConfigurerAdapter
#Configuration
#EnableWebSecurity
public class AppLoginConfig extends WebSecurityConfigurerAdapter {
#Autowired
private AccountVerificationAuthenticationProvider accountVerificationAuthenticationProvider;
#Autowired
private ActiveDirectoryLdapAuthenticationProvider activeDirectoryLdapAuthenticationProvider;
#Override
protected void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
// Spring will try these auth providers in the order we register them.
// We do the accountVerificationAuthenticationProvider provider first, since it doesn't need to do any slow IO to check,
// so it's very fast. Only if this AuthenticationProvider rejects (which means this http request is not for programmatic login), will spring then try the next AuthenticationProvider in the list.
authenticationManagerBuilder
.authenticationProvider(accountVerificationAuthenticationProvider)
// I'm using ActiveDirectory / LDAP for when a user logs in via entering a user + password via the html form, but whatever you want to use here should work.
.authenticationProvider(activeDirectoryLdapAuthenticationProvider);
}
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
...
}
}
For custom web authentication you should implement combination of a custom authentication filter (for example AbstractAuthenticationProcessingFilter or just GenericFilterBean), a custom authentication provider (AuthenticationProvider) or/and custom authentication token (AbstractAuthenticationToken).
For example, see source of Spring Security Kerberos.
See also:
The AuthenticationManager, ProviderManager and AuthenticationProvider
I have a custom authenticator (ie. a class that extends Authenticator and does some custom authentication in authenticate(Response res, Request req)) that I then want to pass a value to the function that handles the actual API call. I thought adding an argument like #Context Request request could work but I don't think so.
Anyone know how I can pass an object from Authenticator to resource?
Thanks,
Daniel
jax-rs has a mechanism for that. You can create a custom context that implements SecurityContext. This security context returns your your user object as Principal.
Your authenticator which probably works as a filter can then set the security context of your request. From there on you can access your User object via the SecurityContext.
While not perfect, I have a working piece of code on github that can get you started.
I am working on a web application that uses Spring Security. We are using a legacy database system, so it was necessary to write a custom AuthenticationProvider. After successful authentication, we can load info on the user, e.g. roles, available domains, etc. While this logic can be contained within the AuthenticationProvider, we have good reasons to factor it out to an external location. To do so, I wrote a listener for the Spring Security AuthenticationSuccessEvent:
public void onApplicationEvent(AuthenticationSuccessEvent event) {
Authentication auth = event.getAuthentication();
User user = (User)auth.getPrincipal(); //Custom UserDetails implementation
List<GrantedAuthority> newAuthorities;
//Do stuff to user and get new authorities
SecurityContextHolder.getContext().setAuthentication(
new UsernamePasswordAuthenticationToken(user, null, newAuthorities);
}
The SecurityContext is changed within the method, but then I seem to lose the new authorities afterwards. In particular, within the method, the SecurityContext contains
UsernamePasswordAuthenticationToken#70df1ce8
At the end of the filter chain, I get the message that
UsernamePasswordAuthenticationToken#bbe0f021
is being persisted by SecurityContextPersistenceFilter.
I may be able to work around this by just putting the logic the AuthenticationProvider, customizing the Authentication Filter, or trying to use AuthenticationSuccessHandler instead. But I'd still like to understand why the changes made in the event handler aren't reflected outside of it.
Is there a way to pass URL parameters to an authentication provider in Spring Security 3?
Our login page will need to receive an email token as a parameter that the authentication system will need to be aware of when it sets the status of the user. Specifically, it will let a user with a correct token log in that would not otherwise be able to.
I have a custom class extending the DaoAuthenticationProvider class. My authentication logic is in that class's authenticate method.
I'm hoping there is some way to pass this data into the authenticate method.
You could inject the HttpServletRequest object on your authentication provider class:
private #Autowired HttpServletRequest request;
Now, you should be able to access the request parameters with APIs such as request.getParameterValues(paramName)
You need to override UsernamePasswordAuthenticationFilter.setDetails() and pass extra information to your custom authentication provider via details property of UsernamePasswordAuthenticationToken.
I have a custom-authentication-provider defined in my Spring Security configuration. This class implements AuthenticationProvider, and I can successfully log in using the form defined on my page. The issue is I want to call this class not just on the login page, but from the registration page as well.
The registration page uses a different command class and collects more information than the login form. Right now, when the user registers, I call the appropriate controller, add the record to the database and they can then log in but they aren't logged in automatically. As they've just given me their user name/password on the registration page, can I then pass this to the custom AuthenticationProvider class so they are also logged in?
I've tried creating an org.springframework.security.Authentication class in the registration controller and calling the authenticate method on my customer AuthenticationProvider class, and this doesn't error out, but the user isn't logged in. Do I have to call a method higher in the Spring Security filter chain to accomplish this? Should I redirect the controller to the j_spring_security_check URL? If so, how would I pass the username/password?
You need to put the result of AuthenticationProvider.authenticate() into SecurityContext (obtained from SecurityContextHolder).
Also be aware of AuthenticationSuccessEvent - if your application rely on this event (some Spring Security features may use it, too), you should publish it (you can obtain the default AuthenticationEventPublisher via autowiring). It may be useful to wrap your authentication provider with ProviderManager, it publishes the event automatically using the given publisher.
The problem you are having is that although you have successfully authenticated the user you have not stored the result of this authentication in the user's SecurityContext. In a web application this is a ThreadLocal object which the SecurityContextPersistenceFilter will use to store the user's credentials in the HTTPSession
You should also avoid authenticating directly with your custom authentication provider if you can. Your xml configuration should contain an AuthenticationManager which your custom authentication provider has been wired into. For example,
<bean id="customAuthenticationProvider"
class="com.foo.CustomAuthenticationProvider">
<property name="accountService" ref="accountService"/>
</bean>
<security:authentication-manager alias="authenticationManager">
<security:authentication-provider ref="customAuthenticationProvider"/>
</security:authentication-manager>
If you wire the authenticationManager into your registration service and authenticate using that it will additionally,
allow you to swap in/out additional authentication providers at later points
publish the authentication result to other parts of the Spring Security framework (eg success/failure Exception handling code)
Our registration service does this as follows
final UsernamePasswordAuthenticationToken authRequest = new
UsernamePasswordAuthenticationToken(username, password);
final Authentication authentication =
authenticationManager.authenticate(authRequest);
SecurityContextHolder.getContext().setAuthentication(authentication);
We also optionally store the authentication result at this point in a remember-me cookie using the onLoginSuccess() method of TokenBasedRememberMeServices.