The Grails Spring Security plugin totally rocks. Even though Peter wrote a fantastic chapter on it for Grails in Action, I’ve always been a bit scared of it (based on some early bad experiences with the raw acegi codebase which was pretty insanely complex to get going).
Anyways, I’ve had cause to revisit Spring Security for a new client project, and had some tricky corner cases to solve. In particular, the app exposes a REST API that needs a special custom security provider. The client is issued an API key when they purchase the COTS product, and (basically) a hash of this key is remoted by a rich client during the authentication process so they can access backend services. However they can also access parts of the app using a normal browser interface with a user password and standard “remember me” features.
So the trick was supporting both a custom auth mechanism for certain URLs (eg /api/**) whilst maintaining usernames and passwords for the rest of the app. Turns out that it’s all totally doable. First a disclaimer: I know next to nothing about Spring Security, so there is probably way better ways to accomplish this, so feel free to give feedback about how I could simplify all this so that future googlers can benefit. That aside, here’s my crack.
Ok. To get all this custom stuff happening you’ll need to implement a few things:
Let’s start with the easy stuff and define our custom Authentication
object to hold our credentials. You really only need a custom one so that your AuthenticationProvider
class can answer true to supportsClass(auth)
, and there’s probably good adaptors I could have subclassed. Given I’m doing it all in Groovy, it’s very concise to implement the entire interface anyways:
import org.springframework.security.*
class CustomAppTokenAuthentication implements Authentication {
String name
GrantedAuthority[] authorities
Object credentials
Object details
Object principal
boolean authenticated
}
Ok. We have our “holder” for the credentials that the user is going to present. I’m going to populate the credentials and principal details from the incoming http request. It’s the role of the SpringSecurityFilter
to do that scraping, then fire off the AuthenticationManager
‘s pipeline of AuthenticationProviders
. Here’s rough crack at a custom Filter:
import org.springframework.security.ui.*
import org.springframework.security.context.*
import org.springframework.beans.factory.*
import org.springframework.context.*
import javax.servlet.*
import javax.servlet.http.*
class CustomAppTokenFilter extends SpringSecurityFilter implements InitializingBean{
def authenticationManager
def customProvider
void doFilterHttp(HttpServletRequest request, HttpServletResponse response, FilterChain chain) {
if (SecurityContextHolder.getContext().getAuthentication() == null) {
def userId = request.getParameter("userId")
def apiKey = request.getParameter("apiKey")
if ( userId && apiKey ) {
def myAuth = new CustomAppTokenAuthentication(
name: userId,
credentials: apiKey,
principal: userId,
authenticated: true
)
myAuth = authenticationManager.authenticate(myAuth);
if (myAuth) {
println "Successfully Authenticated ${userId} in object ${myAuth}"
// Store to SecurityContextHolder
SecurityContextHolder.getContext().setAuthentication(myAuth);
}
}
}
chain.doFilter(request, response)
}
int getOrder() {
return FilterChainOrder.REMEMBER_ME_FILTER
}
void afterPropertiesSet() {
def providers = authenticationManager.providers
providers.add(customProvider)
authenticationManager.providers = providers
}
}
There’s a little magic going on here in afterPropertiesSet()
where I add my custom AuthenticationProvider
(which is going to actually validate the token) to the existing pipeline of AuthenticationManager providers. I was hoping to do that via configuration, but couldn’t find out how. Ideas?
With all that in place, the actual logic of firing the request is pretty straightforward. If there is a userId and apiKey coming in, and the user hasn’t already been authenticated, you create your custom Authentication holder object for the credentials (contrary to what you might think, the authenticated: true
attribute means “I should be inspected by an AutheticationProvider classes” NOT “the user has been authenticated with this credential”). Traps for young players.