refactored token introspection packages to support different client credentials at different services

pull/516/head
Justin Richer 2013-09-04 14:54:03 -04:00
parent 127507246e
commit 2201206f0e
7 changed files with 240 additions and 137 deletions

View File

@ -16,13 +16,25 @@
******************************************************************************/
package org.mitre.oauth2.introspectingfilter;
import java.io.IOException;
import java.net.URI;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.apache.http.impl.client.DefaultHttpClient;
import org.mitre.oauth2.introspectingfilter.service.IntrospectionAuthorityGranter;
import org.mitre.oauth2.introspectingfilter.service.IntrospectionConfigurationService;
import org.mitre.oauth2.introspectingfilter.service.impl.SimpleIntrospectionAuthorityGranter;
import org.mitre.oauth2.model.RegisteredClient;
import org.mitre.openid.connect.client.service.ClientConfigurationService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpMethod;
import org.springframework.http.client.ClientHttpRequest;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
@ -39,6 +51,9 @@ import org.springframework.web.client.RestTemplate;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.nimbusds.jose.util.Base64;
import static org.mitre.oauth2.model.ClientDetailsEntity.AuthMethod.SECRET_BASIC;
/**
* This ResourceServerTokenServices implementation introspects incoming tokens at a
@ -48,12 +63,14 @@ import com.google.gson.JsonParser;
*
*/
public class IntrospectingTokenService implements ResourceServerTokenServices {
private String clientId;
private String clientSecret;
private IntrospectionUrlProvider introspectionUrlProvider;
private ClientConfigurationService clientService;
private IntrospectionConfigurationService introspectionConfigurationService;
private IntrospectionAuthorityGranter introspectionAuthorityGranter = new SimpleIntrospectionAuthorityGranter();
private DefaultHttpClient httpClient = new DefaultHttpClient();
private HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(httpClient);
// Inner class to store in the hash map
private class TokenCacheObject {
OAuth2AccessToken token;
@ -66,35 +83,20 @@ public class IntrospectingTokenService implements ResourceServerTokenServices {
}
private Map<String, TokenCacheObject> authCache = new HashMap<String, TokenCacheObject>();
private static Logger logger = LoggerFactory.getLogger(IntrospectingTokenService.class);
public String getClientId() {
return clientId;
}
public void setClientId(String clientId) {
this.clientId = clientId;
}
public String getClientSecret() {
return clientSecret;
}
public void setClientSecret(String clientSecret) {
this.clientSecret = clientSecret;
/**
* @return the introspectionConfigurationService
*/
public IntrospectionConfigurationService getIntrospectionConfigurationService() {
return introspectionConfigurationService;
}
/**
* @return the introspectionUrlProvider
* @param introspectionConfigurationService the introspectionConfigurationService to set
*/
public IntrospectionUrlProvider getIntrospectionUrlProvider() {
return introspectionUrlProvider;
}
/**
* @param introspectionUrlProvider the introspectionUrlProvider to set
*/
public void setIntrospectionUrlProvider(IntrospectionUrlProvider introspectionUrlProvider) {
this.introspectionUrlProvider = introspectionUrlProvider;
public void setIntrospectionConfigurationService(IntrospectionConfigurationService introspectionUrlProvider) {
this.introspectionConfigurationService = introspectionUrlProvider;
}
// Check if there is a token and authentication in the cache
@ -113,7 +115,7 @@ public class IntrospectingTokenService implements ResourceServerTokenServices {
}
private OAuth2Request createStoredRequest(final JsonObject token) {
clientId = token.get("client_id").getAsString();
String clientId = token.get("client_id").getAsString();
Set<String> scopes = new HashSet<String>();
for (JsonElement e : token.get("scope").getAsJsonArray()) {
scopes.add(e.getAsString());
@ -140,22 +142,50 @@ public class IntrospectingTokenService implements ResourceServerTokenServices {
private boolean parseToken(String accessToken) {
// find out which URL to ask
String introspectionUrl = introspectionUrlProvider.getIntrospectionUrl(accessToken);
String introspectionUrl;
RegisteredClient client;
try {
introspectionUrl = introspectionConfigurationService.getIntrospectionUrl(accessToken);
client = introspectionConfigurationService.getClientConfiguration(accessToken);
} catch (IllegalArgumentException e) {
logger.error("Unable to load introspection URL or client configuration", e);
return false;
}
// Use the SpringFramework RestTemplate to send the request to the
// endpoint
String validatedToken = null;
RestTemplate restTemplate = new RestTemplate();
RestTemplate restTemplate;
MultiValueMap<String, String> form = new LinkedMultiValueMap<String, String>();
final String clientId = client.getClientId();
final String clientSecret = client.getClientSecret();
if (SECRET_BASIC.equals(client.getTokenEndpointAuthMethod())){
// use BASIC auth if configured to do so
restTemplate = new RestTemplate(factory) {
@Override
protected ClientHttpRequest createRequest(URI url, HttpMethod method) throws IOException {
ClientHttpRequest httpRequest = super.createRequest(url, method);
httpRequest.getHeaders().add("Authorization",
String.format("Basic %s", Base64.encode(String.format("%s:%s", clientId, clientSecret)) ));
return httpRequest;
}
};
} else { //Alternatively use form based auth
restTemplate = new RestTemplate(factory);
form.add("client_id", clientId);
form.add("client_secret", clientSecret);
}
form.add("token", accessToken);
form.add("client_id", this.clientId);
form.add("client_secret", this.clientSecret);
try {
validatedToken = restTemplate.postForObject(introspectionUrl, form, String.class);
} catch (RestClientException rce) {
// TODO: LOG THIS!?
LoggerFactory.getLogger(IntrospectingTokenService.class).error("validateToken", rce);
logger.error("validateToken", rce);
}
if (validatedToken != null) {
// parse the json
@ -168,11 +198,13 @@ public class IntrospectingTokenService implements ResourceServerTokenServices {
if (tokenResponse.get("error") != null) {
// report an error?
logger.error("Got an error back: " + tokenResponse.get("error") + ", " + tokenResponse.get("error_description"));
return false;
}
if (!tokenResponse.get("active").getAsBoolean()) {
// non-valid token
logger.info("Server returned non-active token");
return false;
}
// create an OAuth2Authentication

View File

@ -1,92 +0,0 @@
/*******************************************************************************
* Copyright 2013 The MITRE Corporation
* and the MIT Kerberos and Internet Trust Consortium
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
/**
*
*/
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

@ -17,7 +17,7 @@
/**
*
*/
package org.mitre.oauth2.introspectingfilter;
package org.mitre.oauth2.introspectingfilter.service;
import java.util.List;

View File

@ -17,13 +17,15 @@
/**
*
*/
package org.mitre.oauth2.introspectingfilter;
package org.mitre.oauth2.introspectingfilter.service;
import org.mitre.oauth2.model.RegisteredClient;
/**
* @author jricher
*
*/
public interface IntrospectionUrlProvider {
public interface IntrospectionConfigurationService {
/**
* Get the introspection URL based on the access token.
@ -32,4 +34,13 @@ public interface IntrospectionUrlProvider {
*/
public String getIntrospectionUrl(String accessToken);
/**
* Get the client configuration to use to connect to the
* introspection endpoint. In particular, this cares about
* the clientId, clientSecret, and tokenEndpointAuthMethod
* fields.
*/
public RegisteredClient getClientConfiguration(String accessToken);
}

View File

@ -0,0 +1,125 @@
/*******************************************************************************
* Copyright 2013 The MITRE Corporation
* and the MIT Kerberos and Internet Trust Consortium
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
/**
*
*/
package org.mitre.oauth2.introspectingfilter.service.impl;
import java.text.ParseException;
import org.mitre.oauth2.introspectingfilter.service.IntrospectionConfigurationService;
import org.mitre.oauth2.model.RegisteredClient;
import org.mitre.openid.connect.client.service.ClientConfigurationService;
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 JWTParsingIntrospectionConfigurationService implements IntrospectionConfigurationService {
private ServerConfigurationService serverConfigurationService;
private ClientConfigurationService clientConfigurationService;
/**
* @return the serverConfigurationService
*/
public ServerConfigurationService getServerConfigurationService() {
return serverConfigurationService;
}
/**
* @param serverConfigurationService the serverConfigurationService to set
*/
public void setServerConfigurationService(ServerConfigurationService serverConfigurationService) {
this.serverConfigurationService = serverConfigurationService;
}
private String getIssuer(String accessToken) {
try {
JWT jwt = JWTParser.parse(accessToken);
String issuer = jwt.getJWTClaimsSet().getIssuer();
return issuer;
} catch (ParseException e) {
throw new IllegalArgumentException("Unable to parse JWT", e);
}
}
/* (non-Javadoc)
* @see org.mitre.oauth2.introspectingfilter.IntrospectionConfigurationService#getIntrospectionUrl(java.lang.String)
*/
@Override
public String getIntrospectionUrl(String accessToken) {
String issuer = getIssuer(accessToken);
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");
}
}
/* (non-Javadoc)
* @see org.mitre.oauth2.introspectingfilter.service.IntrospectionConfigurationService#getClientConfiguration(java.lang.String)
*/
@Override
public RegisteredClient getClientConfiguration(String accessToken) {
String issuer = getIssuer(accessToken);
if (!Strings.isNullOrEmpty(issuer)) {
ServerConfiguration server = serverConfigurationService.getServerConfiguration(issuer);
if (server != null) {
RegisteredClient client = clientConfigurationService.getClientConfiguration(server);
if (client != null) {
return client;
} else {
throw new IllegalArgumentException("Could not find client configuration for issuer " + issuer);
}
} else {
throw new IllegalArgumentException("Could not find server configuration for issuer " + issuer);
}
} else {
throw new IllegalArgumentException("No issuer claim found in JWT");
}
}
}

View File

@ -17,10 +17,11 @@
/**
*
*/
package org.mitre.oauth2.introspectingfilter;
package org.mitre.oauth2.introspectingfilter.service.impl;
import java.util.List;
import org.mitre.oauth2.introspectingfilter.service.IntrospectionAuthorityGranter;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;

View File

@ -17,19 +17,37 @@
/**
*
*/
package org.mitre.oauth2.introspectingfilter;
package org.mitre.oauth2.introspectingfilter.service.impl;
import org.mitre.oauth2.introspectingfilter.service.IntrospectionConfigurationService;
import org.mitre.oauth2.model.RegisteredClient;
/**
*
* Always provides the (configured) IntrospectionURL regardless of token. Useful for talking to
* a single, trusted authorization server.
* Always provides the (configured) IntrospectionURL and RegisteredClient regardless
* of token. Useful for talking to a single, trusted authorization server.
*
* @author jricher
*
*/
public class StaticIntrospectionUrlProvider implements IntrospectionUrlProvider {
public class StaticIntrospectionConfigurationService implements IntrospectionConfigurationService {
private String introspectionUrl;
private RegisteredClient clientConfiguration;
/**
* @return the clientConfiguration
*/
public RegisteredClient getClientConfiguration() {
return clientConfiguration;
}
/**
* @param clientConfiguration the clientConfiguration to set
*/
public void setClientConfiguration(RegisteredClient client) {
this.clientConfiguration = client;
}
/**
* @return the introspectionUrl
@ -46,11 +64,19 @@ public class StaticIntrospectionUrlProvider implements IntrospectionUrlProvider
}
/* (non-Javadoc)
* @see org.mitre.oauth2.introspectingfilter.IntrospectionUrlProvider#getIntrospectionUrl(java.lang.String)
* @see org.mitre.oauth2.introspectingfilter.IntrospectionConfigurationService#getIntrospectionUrl(java.lang.String)
*/
@Override
public String getIntrospectionUrl(String accessToken) {
return getIntrospectionUrl();
}
/* (non-Javadoc)
* @see org.mitre.oauth2.introspectingfilter.service.IntrospectionConfigurationService#getClientConfiguration(java.lang.String)
*/
@Override
public RegisteredClient getClientConfiguration(String accessToken) {
return getClientConfiguration();
}
}