I\'m using Spring Security to secure a Struts2 web application. Due to project constraints, I\'m using Spring Security 2.06.
My team built a custom User Management A
thanks for posting this Luke!
Saved me from more brain damage.
Only thing of note I ran into, for anyone who cares:
My setup:
When utilizing the greatly appreciated simplified/elegant approach Luke suggests, NOT implementing a custom UserDetails (or UserDetailsService) object -and- using your own User domain object that does not extend anything special, you must take an extra step if you are using the "sec" custom tags from spring security (in your pages of course):
When you instantiate a basic, non-custom UsernamePasswordAuthenticationToken, you MUST pass it an instantiation of something that extends Principal, again, if you want your spring security custom gap tags to work. I did something like this, to keep it as simple as possible (referencing my user domain object values where useful/appropriate):
def principalUser = new org.springframework.security.core.userdetails.User(user.username, user.password, user.enabled, !user.accountExpired, !user.passwordExpired,!user.accountLocked, authorities)
def token = new UsernamePasswordAuthenticationToken(principalUser, presentedPassword, authorities)
This should satisfy the conditions tested for in grails.plugins.springsecurity.SecurityTagLib.determineSource() so, you know, your pages that use <sec:loggedInUserInfo>
will actually render:
if (principal.metaClass.respondsTo(principal, 'getDomainClass')) {
return principal.domainClass
}
Otherwise, if you instantiate the UsernamePasswordAuthenticationToken with your User domain object (as Luke show in his example), that security tag lib method (determineSource()) will just do it's level best and return the (meta) value of org.codehaus.groovy.grails.commons.DefaultGrailsDomainClass and you'll get an error when the tag goes looking for the username member variable stating:
Error executing tag <sec:ifLoggedIn>: Error executing tag <sec:loggedInUserInfo>: No such property: username for class: org.codehaus.groovy.grails.commons.DefaultGrailsDomainClass
Short of re-implementing/subclassing the spring-security-core plugin taglibs in my grails project, there's just no way to both use the taglibs AND use your custom domain User class to instantiate the token being passed from your filter to your provider.
Then again, one extra line of code is a very small price to pay :)
If you are implementing your own AuthenticationProvider
, You don't have to implement a UserDetailsService
if you don't want to. UserDetailsService
just provides a standard DAO for loading user information and some other classes within the framework are implemented to use it.
Normally, to authenticate using a username and password, you would instantiate a DaoAuthenticationProvider and inject that with a UserDetailsService
. That may still be your best approach. If you implement your own provider, you take on the responsibility of making sure the user has supplied the correct password and so on. However, in some cases this is a simpler approach.
To answer your "what should happen here?" comment in your code, it would be something like
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
UsernamePasswordAuthenticationToken auth = (UsernamePasswordAuthenticationToken) authentication;
String username = String.valueOf(auth.getPrincipal());
String password = String.valueOf(auth.getCredentials());
logger.info("username:" + username);
logger.info("password:" + password); // Don't log passwords in real app
// 1. Use the username to load the data for the user, including authorities and password.
YourUser user = ....
// 2. Check the passwords match (should use a hashed password here).
if (!user.getPassword().equals(password)) {
throw new BadCredentialsException("Bad Credentials");
}
// 3. Preferably clear the password in the user object before storing in authentication object
user.clearPassword();
// 4. Return an authenticated token, containing user data and authorities
return new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities()) ;
}
The user object will then be accessible using the
Authentication.getPrincipal()
method, and you can access the additional properties (email etc) by casting it to your custom user implementation.
How you load the user data is up to you. All Spring Security cares about here is the AuthenticationProvider
interface.
You should also store hashed passwords and validate the supplied password using the same algorithm, rather than a simple equality check.