I was trying to write a massive security infrastructure beast. I don't need to do that.
The first thing I've done since the last post is to write my own 'SecurityService' class that implements the UserDetailsService interface. UserDetailsService has just one method -- UserDetails loadUserByUsername(String). So it takes a username and loads that whole user object.
Well this confused me. Where the hell, I thought, does the password get dealt with here?
The answer is that the password is handled by the Authentication Provider (in this example, an org.acegisecurity.providers.dao.DaoAuthenticationProvider). So this particular authentication provider accepts a username and password, uses the username to pull the UserDetails data out of the UserDetailsService, and then (in memory) (presumably) compares the submitted password to the password returned from the UserDetails object's getPassword() method.
I'm not thrilled by this implementation, because it assumes that passwords will be stored in plaintext! They shouldn't be.
Anyway, I verified all of this by building out a deliberately stupid implementation of the UserDetailsService and wiring it into my application -- specifically I wrote one that always returns the same UserDetails object, with a known and bogus username and password. I tested this and it worked, so it all makes more sense to me now. Next step is to rewrite my bogus UserDetailsService to talk to my actual application code and return real user objects.
Down the road a bit I'm probably going to have to look at the other authentication providers, so that I can handle encrypted passwords. But in the interim this is good progress.
Stupid side note: for some reason, whenever I log in, users are getting redirected to an image file the first time.