I'm using Spring Security OAuth2. The client application (that we own) makes a "password" grant request that passes the user's username and password. Just like the draft specifies.
I need this mechanism to also support other types of credentials, like card number, PIN, and even a pre-authenticated, password not required grant.
Please keep in mind, these requests will only be permitted by a privileged client_id, one that will only be used from the application we own.
Dave, thanks for the quick response. I actually found the perfect solution, one which you took part in. It has to do with "custom-grant" token granters... https://jira.spring.io/browse/SECOAUTH-347
Had I updated my rather old 1.0.0.M5 version I might have known about those.
My approach was to extend AbstractTokenGranter
with a class that supports a custom grant type (I call it "studentCard"). Once an authentication request makes it here, I examine the parameter list just like ResourceOwnerPasswordTokenGranter
, but instead look for my custom "cardNumber" parameter. I then pass my own, id-based version of UsernamePasswordAuthenticationToken
to my AuthenticationProvider, which knows how to authenticate users based on id card.
Here is the custom token granter class I came up with:
public class StudentCardTokenGranter extends AbstractTokenGranter {
private static final String GRANT_TYPE = "studentCard";
private final AuthenticationManager authenticationManager;
public StudentCardTokenGranter(AuthenticationManager authenticationManager,
AuthorizationServerTokenServices tokenServices, ClientDetailsService clientDetailsService) {
super(tokenServices, clientDetailsService, GRANT_TYPE);
this.authenticationManager = authenticationManager;
}
@Override
protected OAuth2Authentication getOAuth2Authentication(AuthorizationRequest clientToken) {
Map<String, String> parameters = clientToken.getAuthorizationParameters();
String cardNumber = parameters.get("cardNumber");
Authentication userAuth = new StudentCardAuthenticationToken(cardNumber);
try {
userAuth = authenticationManager.authenticate(userAuth);
} catch (BadCredentialsException e) {
// If the username/password are wrong the spec says we should send 400/bad grant
throw new InvalidGrantException(e.getMessage());
}
if (userAuth == null || !userAuth.isAuthenticated()) {
throw new InvalidGrantException("Could not authenticate student: " + cardNumber);
}
return new OAuth2Authentication(clientToken, userAuth);
}
}
And my authorization server config:
<!-- Issues tokens for both client and client/user authorization requests -->
<oauth:authorization-server client-details-service-ref="clientDetails" token-services-ref="tokenServices">
<oauth:refresh-token />
<oauth:client-credentials />
<oauth:password authentication-manager-ref="myUserManager" />
<oauth:custom-grant token-granter-ref="studentCardGranter" />
</oauth:authorization-server>
<bean id="studentCardGranter" class="com.api.security.StudentCardTokenGranter">
<constructor-arg name="authenticationManager" ref="myUserManager" />
<constructor-arg name="tokenServices" ref="tokenServices" />
<constructor-arg name="clientDetailsService" ref="clientDetails" />
</bean>
The spec doesn't explicitly allow direct, non-password-based exchange of user tokens between a client and the auth server directly. I think it would be quite natural to extend the password grant to other forms of authentication though. It's in the spirit of the spec, if not by the letter, so if you own both sides of the relationship there isn't much to go wrong. Spring OAuth doesn't explictly support anything that extends the password grant in this way, but its not hard to do (it's really just about the security of the /token endpoint). An alternative approach I've seen is to stick to the password grant protocol, but make the "password" a one-time token that the client can only get by knowing the user has authenticated in one of those alternative ways.