externalized introspection URL from client's introspecting token service, addresses #435

added service to parses token as JWT and pulls out issuer to find server
added introspection url to serverconfig object
added introspection parsing (and parse checks) to dynamic server config object
pull/419/merge
Justin Richer 2013-07-16 16:36:25 -04:00
parent dfbf01c9e3
commit a4a18fd54c
6 changed files with 300 additions and 104 deletions

View File

@ -38,30 +38,32 @@ import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
/**
* This ResourceServerTokenServices implementation introspects incoming tokens at a
* server's introspection endpoint URL and passes an Authentication object along
* based on the response from the introspection endpoint.
* @author jricher
*
*/
public class IntrospectingTokenService implements ResourceServerTokenServices {
private String clientId;
private String clientSecret;
private String introspectionUrl;
private IntrospectionUrlProvider introspectionUrlProvider;
// Inner class to store in the hash map
private class TokenCacheObject { OAuth2AccessToken token; OAuth2Authentication auth;
private TokenCacheObject(OAuth2AccessToken token, OAuth2Authentication auth) {
this.token = token;
this.auth = auth;
}
private class TokenCacheObject {
OAuth2AccessToken token;
OAuth2Authentication auth;
private TokenCacheObject(OAuth2AccessToken token, OAuth2Authentication auth) {
this.token = token;
this.auth = auth;
}
}
private Map<String, TokenCacheObject> authCache = new HashMap<String, TokenCacheObject>();
public String getIntrospectionUrl() {
return introspectionUrl;
}
public void setIntrospectionUrl(String introspectionUrl) {
this.introspectionUrl = introspectionUrl;
}
public String getClientId() {
return clientId;
}
@ -78,10 +80,24 @@ public class IntrospectingTokenService implements ResourceServerTokenServices {
this.clientSecret = clientSecret;
}
/**
* @return the introspectionUrlProvider
*/
public IntrospectionUrlProvider getIntrospectionUrlProvider() {
return introspectionUrlProvider;
}
/**
* @param introspectionUrlProvider the introspectionUrlProvider to set
*/
public void setIntrospectionUrlProvider(IntrospectionUrlProvider introspectionUrlProvider) {
this.introspectionUrlProvider = introspectionUrlProvider;
}
// Check if there is a token and authentication in the cache
// and check if it is not expired.
// and check if it is not expired.
private TokenCacheObject checkCache(String key) {
if(authCache.containsKey(key)) {
if (authCache.containsKey(key)) {
TokenCacheObject tco = authCache.get(key);
if (tco.token.getExpiration().after(new Date())) {
return tco;
@ -99,26 +115,30 @@ public class IntrospectingTokenService implements ResourceServerTokenServices {
}
// create a default authentication object with authority ROLE_API
private Authentication createAuthentication(JsonObject token){
private Authentication createAuthentication(JsonObject token) {
// TODO: make role/authority configurable somehow
return new PreAuthenticatedAuthenticationToken(token.get("sub").getAsString(), null, AuthorityUtils.createAuthorityList("ROLE_API"));
}
private OAuth2AccessToken createAccessToken(final JsonObject token, final String tokenString){
private OAuth2AccessToken createAccessToken(final JsonObject token, final String tokenString) {
OAuth2AccessToken accessToken = new OAuth2AccessTokenImpl(token, tokenString);
return accessToken;
}
// Validate a token string against the introspection endpoint,
// then parse it and store it in the local cache. Return true on
// sucess, false otherwise.
// then parse it and store it in the local cache. Return true on
// sucess, false otherwise.
private boolean parseToken(String accessToken) {
String validatedToken = null;
// Use the SpringFramework RestTemplate to send the request to the endpoint
// find out which URL to ask
String introspectionUrl = introspectionUrlProvider.getIntrospectionUrl(accessToken);
// Use the SpringFramework RestTemplate to send the request to the
// endpoint
String validatedToken = null;
RestTemplate restTemplate = new RestTemplate();
MultiValueMap<String, String> form = new LinkedMultiValueMap<String, String>();
form.add("token",accessToken);
form.add("token", accessToken);
form.add("client_id", this.clientId);
form.add("client_secret", this.clientSecret);
@ -142,7 +162,7 @@ public class IntrospectingTokenService implements ResourceServerTokenServices {
return false;
}
if (!tokenResponse.get("active").getAsBoolean()){
if (!tokenResponse.get("active").getAsBoolean()) {
// non-valid token
return false;
}
@ -151,9 +171,9 @@ public class IntrospectingTokenService implements ResourceServerTokenServices {
// create an OAuth2AccessToken
OAuth2AccessToken token = createAccessToken(tokenResponse, accessToken);
if (token.getExpiration().after(new Date())){
if (token.getExpiration().after(new Date())) {
// Store them in the cache
authCache.put(accessToken, new TokenCacheObject(token,auth));
authCache.put(accessToken, new TokenCacheObject(token, auth));
return true;
}
@ -165,7 +185,8 @@ public class IntrospectingTokenService implements ResourceServerTokenServices {
@Override
public OAuth2Authentication loadAuthentication(String accessToken) throws AuthenticationException {
// First check if the in memory cache has an Authentication object, and that it is still valid
// First check if the in memory cache has an Authentication object, and
// that it is still valid
// If Valid, return it
TokenCacheObject cacheAuth = checkCache(accessToken);
if (cacheAuth != null) {
@ -186,7 +207,8 @@ public class IntrospectingTokenService implements ResourceServerTokenServices {
@Override
public OAuth2AccessToken readAccessToken(String accessToken) {
// First check if the in memory cache has a Token object, and that it is still valid
// First check if the in memory cache has a Token object, and that it is
// still valid
// If Valid, return it
TokenCacheObject cacheAuth = checkCache(accessToken);
if (cacheAuth != null) {

View File

@ -0,0 +1,19 @@
/**
*
*/
package org.mitre.oauth2.introspectingfilter;
/**
* @author jricher
*
*/
public interface IntrospectionUrlProvider {
/**
* Get the introspection URL based on the access token.
* @param accessToken
* @return
*/
public String getIntrospectionUrl(String accessToken);
}

View File

@ -0,0 +1,73 @@
/**
*
*/
package org.mitre.oauth2.introspectingfilter;
import java.text.ParseException;
import org.mitre.openid.connect.client.service.ServerConfigurationService;
import org.mitre.openid.connect.config.ServerConfiguration;
import com.google.common.base.Strings;
import com.nimbusds.jwt.JWT;
import com.nimbusds.jwt.JWTParser;
/**
*
* Parses the incoming accesstoken as a JWT and determines the issuer based on
* the "iss" field inside the JWT. Uses the ServerConfigurationService to determine
* the introspection URL for that issuer.
*
* @author jricher
*
*/
public class JWTParsingIntrospectionUrlProvider implements IntrospectionUrlProvider {
private ServerConfigurationService serverConfigurationService;
/**
* @return the serverConfigurationService
*/
public ServerConfigurationService getServerConfigurationService() {
return serverConfigurationService;
}
/**
* @param serverConfigurationService the serverConfigurationService to set
*/
public void setServerConfigurationService(ServerConfigurationService serverConfigurationService) {
this.serverConfigurationService = serverConfigurationService;
}
/* (non-Javadoc)
* @see org.mitre.oauth2.introspectingfilter.IntrospectionUrlProvider#getIntrospectionUrl(java.lang.String)
*/
@Override
public String getIntrospectionUrl(String accessToken) {
try {
JWT jwt = JWTParser.parse(accessToken);
String issuer = jwt.getJWTClaimsSet().getIssuer();
if (!Strings.isNullOrEmpty(issuer)) {
ServerConfiguration server = serverConfigurationService.getServerConfiguration(issuer);
if (server != null) {
if (!Strings.isNullOrEmpty(server.getIntrospectionEndpointUri())) {
return server.getIntrospectionEndpointUri();
} else {
throw new IllegalArgumentException("Server does not have Introspection Endpoint defined");
}
} else {
throw new IllegalArgumentException("Could not find server configuration for issuer " + issuer);
}
} else {
throw new IllegalArgumentException("No issuer claim found in JWT");
}
} catch (ParseException e) {
throw new IllegalArgumentException("Unable to parse JWT", e);
}
}
}

View File

@ -0,0 +1,40 @@
/**
*
*/
package org.mitre.oauth2.introspectingfilter;
/**
*
* Always provides the (configured) IntrospectionURL regardless of token. Useful for talking to
* a single, trusted authorization server.
*
* @author jricher
*
*/
public class StaticIntrospectionUrlProvider implements IntrospectionUrlProvider {
private String introspectionUrl;
/**
* @return the introspectionUrl
*/
public String getIntrospectionUrl() {
return introspectionUrl;
}
/**
* @param introspectionUrl the introspectionUrl to set
*/
public void setIntrospectionUrl(String introspectionUrl) {
this.introspectionUrl = introspectionUrl;
}
/* (non-Javadoc)
* @see org.mitre.oauth2.introspectingfilter.IntrospectionUrlProvider#getIntrospectionUrl(java.lang.String)
*/
@Override
public String getIntrospectionUrl(String accessToken) {
return getIntrospectionUrl();
}
}

View File

@ -95,17 +95,35 @@ public class DynamicServerConfigurationService implements ServerConfigurationSer
JsonObject o = parsed.getAsJsonObject();
// sanity checks
if (!o.has("issuer")) {
throw new IllegalStateException("Returned object did not have an 'issuer' field");
}
if (!issuer.equals(o.get("issuer").getAsString())) {
throw new IllegalStateException("Discovered issuers didn't match, expected " + issuer + " got " + o.get("issuer").getAsString());
}
conf.setIssuer(o.get("issuer").getAsString());
conf.setAuthorizationEndpointUri(o.get("authorization_endpoint").getAsString());
conf.setTokenEndpointUri(o.get("token_endpoint").getAsString());
conf.setJwksUri(o.get("jwks_uri").getAsString());
conf.setUserInfoUri(o.get("userinfo_endpoint").getAsString());
conf.setRegistrationEndpointUri(o.get("registration_endpoint").getAsString());
if (o.has("authorization_endpoint")) {
conf.setAuthorizationEndpointUri(o.get("authorization_endpoint").getAsString());
}
if (o.has("token_endpoint")) {
conf.setTokenEndpointUri(o.get("token_endpoint").getAsString());
}
if (o.has("jwks_uri")) {
conf.setJwksUri(o.get("jwks_uri").getAsString());
}
if (o.has("userinfo_endpoint")) {
conf.setUserInfoUri(o.get("userinfo_endpoint").getAsString());
}
if (o.has("registration_endpoint")) {
conf.setRegistrationEndpointUri(o.get("registration_endpoint").getAsString());
}
if (o.has("introspection_endpoint")) {
conf.setIntrospectionEndpointUri(o.get("introspection_endpoint").getAsString());
}
return conf;
} else {
throw new IllegalStateException("Couldn't parse server discovery results for " + url);

View File

@ -39,6 +39,8 @@ public class ServerConfiguration {
private String userInfoUri;
private String introspectionEndpointUri;
/**
* @return the authorizationEndpointUri
*/
@ -123,81 +125,103 @@ public class ServerConfiguration {
this.registrationEndpointUri = registrationEndpointUri;
}
/**
* @return the introspectionEndpointUri
*/
public String getIntrospectionEndpointUri() {
return introspectionEndpointUri;
}
/**
* @param introspectionEndpointUri the introspectionEndpointUri to set
*/
public void setIntrospectionEndpointUri(String introspectionEndpointUri) {
this.introspectionEndpointUri = introspectionEndpointUri;
}
/* (non-Javadoc)
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((authorizationEndpointUri == null) ? 0 : authorizationEndpointUri.hashCode());
result = prime * result + ((issuer == null) ? 0 : issuer.hashCode());
result = prime * result + ((jwksUri == null) ? 0 : jwksUri.hashCode());
result = prime * result + ((registrationEndpointUri == null) ? 0 : registrationEndpointUri.hashCode());
result = prime * result + ((tokenEndpointUri == null) ? 0 : tokenEndpointUri.hashCode());
result = prime * result + ((userInfoUri == null) ? 0 : userInfoUri.hashCode());
return result;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((authorizationEndpointUri == null) ? 0 : authorizationEndpointUri.hashCode());
result = prime * result + ((introspectionEndpointUri == null) ? 0 : introspectionEndpointUri.hashCode());
result = prime * result + ((issuer == null) ? 0 : issuer.hashCode());
result = prime * result + ((jwksUri == null) ? 0 : jwksUri.hashCode());
result = prime * result + ((registrationEndpointUri == null) ? 0 : registrationEndpointUri.hashCode());
result = prime * result + ((tokenEndpointUri == null) ? 0 : tokenEndpointUri.hashCode());
result = prime * result + ((userInfoUri == null) ? 0 : userInfoUri.hashCode());
return result;
}
/* (non-Javadoc)
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
ServerConfiguration other = (ServerConfiguration) obj;
if (authorizationEndpointUri == null) {
if (other.authorizationEndpointUri != null) {
return false;
}
} else if (!authorizationEndpointUri.equals(other.authorizationEndpointUri)) {
return false;
}
if (issuer == null) {
if (other.issuer != null) {
return false;
}
} else if (!issuer.equals(other.issuer)) {
return false;
}
if (jwksUri == null) {
if (other.jwksUri != null) {
return false;
}
} else if (!jwksUri.equals(other.jwksUri)) {
return false;
}
if (registrationEndpointUri == null) {
if (other.registrationEndpointUri != null) {
return false;
}
} else if (!registrationEndpointUri.equals(other.registrationEndpointUri)) {
return false;
}
if (tokenEndpointUri == null) {
if (other.tokenEndpointUri != null) {
return false;
}
} else if (!tokenEndpointUri.equals(other.tokenEndpointUri)) {
return false;
}
if (userInfoUri == null) {
if (other.userInfoUri != null) {
return false;
}
} else if (!userInfoUri.equals(other.userInfoUri)) {
return false;
}
return true;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (!(obj instanceof ServerConfiguration)) {
return false;
}
ServerConfiguration other = (ServerConfiguration) obj;
if (authorizationEndpointUri == null) {
if (other.authorizationEndpointUri != null) {
return false;
}
} else if (!authorizationEndpointUri.equals(other.authorizationEndpointUri)) {
return false;
}
if (introspectionEndpointUri == null) {
if (other.introspectionEndpointUri != null) {
return false;
}
} else if (!introspectionEndpointUri.equals(other.introspectionEndpointUri)) {
return false;
}
if (issuer == null) {
if (other.issuer != null) {
return false;
}
} else if (!issuer.equals(other.issuer)) {
return false;
}
if (jwksUri == null) {
if (other.jwksUri != null) {
return false;
}
} else if (!jwksUri.equals(other.jwksUri)) {
return false;
}
if (registrationEndpointUri == null) {
if (other.registrationEndpointUri != null) {
return false;
}
} else if (!registrationEndpointUri.equals(other.registrationEndpointUri)) {
return false;
}
if (tokenEndpointUri == null) {
if (other.tokenEndpointUri != null) {
return false;
}
} else if (!tokenEndpointUri.equals(other.tokenEndpointUri)) {
return false;
}
if (userInfoUri == null) {
if (other.userInfoUri != null) {
return false;
}
} else if (!userInfoUri.equals(other.userInfoUri)) {
return false;
}
return true;
}
}