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 46363a656..96b1e494e 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
@@ -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) {
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/IntrospectionUrlProvider.java
new file mode 100644
index 000000000..78c39ba67
--- /dev/null
+++ b/openid-connect-client/src/main/java/org/mitre/oauth2/introspectingfilter/IntrospectionUrlProvider.java
@@ -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);
+	
+}
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
new file mode 100644
index 000000000..c6d5eb621
--- /dev/null
+++ b/openid-connect-client/src/main/java/org/mitre/oauth2/introspectingfilter/JWTParsingIntrospectionUrlProvider.java
@@ -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);
+        }
+		
+	}
+
+}
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/StaticIntrospectionUrlProvider.java
new file mode 100644
index 000000000..a8e1aeaf9
--- /dev/null
+++ b/openid-connect-client/src/main/java/org/mitre/oauth2/introspectingfilter/StaticIntrospectionUrlProvider.java
@@ -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();
+	}
+
+}
diff --git a/openid-connect-client/src/main/java/org/mitre/openid/connect/client/service/impl/DynamicServerConfigurationService.java b/openid-connect-client/src/main/java/org/mitre/openid/connect/client/service/impl/DynamicServerConfigurationService.java
index f4b02a60e..a2017a4bc 100644
--- a/openid-connect-client/src/main/java/org/mitre/openid/connect/client/service/impl/DynamicServerConfigurationService.java
+++ b/openid-connect-client/src/main/java/org/mitre/openid/connect/client/service/impl/DynamicServerConfigurationService.java
@@ -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);
diff --git a/openid-connect-common/src/main/java/org/mitre/openid/connect/config/ServerConfiguration.java b/openid-connect-common/src/main/java/org/mitre/openid/connect/config/ServerConfiguration.java
index 7adf15cb7..0b0809e59 100644
--- a/openid-connect-common/src/main/java/org/mitre/openid/connect/config/ServerConfiguration.java
+++ b/openid-connect-common/src/main/java/org/mitre/openid/connect/config/ServerConfiguration.java
@@ -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;
+    }
 
 
 }