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 objectpull/419/merge
parent
dfbf01c9e3
commit
a4a18fd54c
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -95,16 +95,34 @@ 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 {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue