diff --git a/openid-connect-client/src/main/java/org/mitre/oauth2/introspectingfilter/IntrospectingTokenService.java b/openid-connect-client/src/main/java/org/mitre/oauth2/introspectingfilter/IntrospectingTokenService.java index d57cba025..5640f79cc 100644 --- a/openid-connect-client/src/main/java/org/mitre/oauth2/introspectingfilter/IntrospectingTokenService.java +++ b/openid-connect-client/src/main/java/org/mitre/oauth2/introspectingfilter/IntrospectingTokenService.java @@ -16,11 +16,23 @@ ******************************************************************************/ package org.mitre.oauth2.introspectingfilter; +import java.io.IOException; +import java.net.URI; import java.util.Date; import java.util.HashMap; import java.util.Map; +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; @@ -36,6 +48,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 @@ -45,12 +60,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; @@ -63,35 +80,20 @@ public class IntrospectingTokenService implements ResourceServerTokenServices { } private Map authCache = new HashMap(); + 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 @@ -131,22 +133,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 form = new LinkedMultiValueMap(); + + 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 @@ -159,11 +189,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 diff --git a/openid-connect-client/src/main/java/org/mitre/oauth2/introspectingfilter/JWTParsingIntrospectionUrlProvider.java b/openid-connect-client/src/main/java/org/mitre/oauth2/introspectingfilter/JWTParsingIntrospectionUrlProvider.java deleted file mode 100644 index 1d5687107..000000000 --- a/openid-connect-client/src/main/java/org/mitre/oauth2/introspectingfilter/JWTParsingIntrospectionUrlProvider.java +++ /dev/null @@ -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); - } - - } - -} diff --git a/openid-connect-client/src/main/java/org/mitre/oauth2/introspectingfilter/IntrospectionAuthorityGranter.java b/openid-connect-client/src/main/java/org/mitre/oauth2/introspectingfilter/service/IntrospectionAuthorityGranter.java similarity index 95% rename from openid-connect-client/src/main/java/org/mitre/oauth2/introspectingfilter/IntrospectionAuthorityGranter.java rename to openid-connect-client/src/main/java/org/mitre/oauth2/introspectingfilter/service/IntrospectionAuthorityGranter.java index 05d8ff2f2..559cd1191 100644 --- a/openid-connect-client/src/main/java/org/mitre/oauth2/introspectingfilter/IntrospectionAuthorityGranter.java +++ b/openid-connect-client/src/main/java/org/mitre/oauth2/introspectingfilter/service/IntrospectionAuthorityGranter.java @@ -17,7 +17,7 @@ /** * */ -package org.mitre.oauth2.introspectingfilter; +package org.mitre.oauth2.introspectingfilter.service; import java.util.List; diff --git a/openid-connect-client/src/main/java/org/mitre/oauth2/introspectingfilter/IntrospectionUrlProvider.java b/openid-connect-client/src/main/java/org/mitre/oauth2/introspectingfilter/service/IntrospectionConfigurationService.java similarity index 70% rename from openid-connect-client/src/main/java/org/mitre/oauth2/introspectingfilter/IntrospectionUrlProvider.java rename to openid-connect-client/src/main/java/org/mitre/oauth2/introspectingfilter/service/IntrospectionConfigurationService.java index ce2437084..3a4827371 100644 --- a/openid-connect-client/src/main/java/org/mitre/oauth2/introspectingfilter/IntrospectionUrlProvider.java +++ b/openid-connect-client/src/main/java/org/mitre/oauth2/introspectingfilter/service/IntrospectionConfigurationService.java @@ -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); + } diff --git a/openid-connect-client/src/main/java/org/mitre/oauth2/introspectingfilter/service/impl/JWTParsingIntrospectionConfigurationService.java b/openid-connect-client/src/main/java/org/mitre/oauth2/introspectingfilter/service/impl/JWTParsingIntrospectionConfigurationService.java new file mode 100644 index 000000000..c62748c00 --- /dev/null +++ b/openid-connect-client/src/main/java/org/mitre/oauth2/introspectingfilter/service/impl/JWTParsingIntrospectionConfigurationService.java @@ -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"); + } + + } + + + +} diff --git a/openid-connect-client/src/main/java/org/mitre/oauth2/introspectingfilter/SimpleIntrospectionAuthorityGranter.java b/openid-connect-client/src/main/java/org/mitre/oauth2/introspectingfilter/service/impl/SimpleIntrospectionAuthorityGranter.java similarity index 92% rename from openid-connect-client/src/main/java/org/mitre/oauth2/introspectingfilter/SimpleIntrospectionAuthorityGranter.java rename to openid-connect-client/src/main/java/org/mitre/oauth2/introspectingfilter/service/impl/SimpleIntrospectionAuthorityGranter.java index b581a2d2d..3a541285e 100644 --- a/openid-connect-client/src/main/java/org/mitre/oauth2/introspectingfilter/SimpleIntrospectionAuthorityGranter.java +++ b/openid-connect-client/src/main/java/org/mitre/oauth2/introspectingfilter/service/impl/SimpleIntrospectionAuthorityGranter.java @@ -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; diff --git a/openid-connect-client/src/main/java/org/mitre/oauth2/introspectingfilter/StaticIntrospectionUrlProvider.java b/openid-connect-client/src/main/java/org/mitre/oauth2/introspectingfilter/service/impl/StaticIntrospectionConfigurationService.java similarity index 52% rename from openid-connect-client/src/main/java/org/mitre/oauth2/introspectingfilter/StaticIntrospectionUrlProvider.java rename to openid-connect-client/src/main/java/org/mitre/oauth2/introspectingfilter/service/impl/StaticIntrospectionConfigurationService.java index ace9a5942..f577e7e75 100644 --- a/openid-connect-client/src/main/java/org/mitre/oauth2/introspectingfilter/StaticIntrospectionUrlProvider.java +++ b/openid-connect-client/src/main/java/org/mitre/oauth2/introspectingfilter/service/impl/StaticIntrospectionConfigurationService.java @@ -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(); + } + }