better handling of HTTP and JSON errors on network fetches, added http-forcing behavior for webfinger client and sector URL service
parent
9ae92b983a
commit
f4a1b27e2e
|
@ -29,7 +29,6 @@ import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
import javax.servlet.http.HttpSession;
|
import javax.servlet.http.HttpSession;
|
||||||
|
|
||||||
import com.google.common.collect.Iterables;
|
|
||||||
import org.apache.http.client.HttpClient;
|
import org.apache.http.client.HttpClient;
|
||||||
import org.apache.http.client.config.RequestConfig;
|
import org.apache.http.client.config.RequestConfig;
|
||||||
import org.apache.http.impl.client.HttpClientBuilder;
|
import org.apache.http.impl.client.HttpClientBuilder;
|
||||||
|
@ -57,10 +56,11 @@ import org.springframework.security.web.authentication.AbstractAuthenticationPro
|
||||||
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
|
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
|
||||||
import org.springframework.util.LinkedMultiValueMap;
|
import org.springframework.util.LinkedMultiValueMap;
|
||||||
import org.springframework.util.MultiValueMap;
|
import org.springframework.util.MultiValueMap;
|
||||||
import org.springframework.web.client.HttpClientErrorException;
|
import org.springframework.web.client.RestClientException;
|
||||||
import org.springframework.web.client.RestTemplate;
|
import org.springframework.web.client.RestTemplate;
|
||||||
|
|
||||||
import com.google.common.base.Strings;
|
import com.google.common.base.Strings;
|
||||||
|
import com.google.common.collect.Iterables;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
import com.google.gson.JsonElement;
|
import com.google.gson.JsonElement;
|
||||||
import com.google.gson.JsonObject;
|
import com.google.gson.JsonObject;
|
||||||
|
@ -407,15 +407,13 @@ public class OIDCAuthenticationFilter extends AbstractAuthenticationProcessingFi
|
||||||
|
|
||||||
try {
|
try {
|
||||||
jsonString = restTemplate.postForObject(serverConfig.getTokenEndpointUri(), form, String.class);
|
jsonString = restTemplate.postForObject(serverConfig.getTokenEndpointUri(), form, String.class);
|
||||||
} catch (HttpClientErrorException httpClientErrorException) {
|
} catch (RestClientException e) {
|
||||||
|
|
||||||
// Handle error
|
// Handle error
|
||||||
|
|
||||||
logger.error("Token Endpoint error response: "
|
logger.error("Token Endpoint error response: " + e.getMessage());
|
||||||
+ httpClientErrorException.getStatusText() + " : "
|
|
||||||
+ httpClientErrorException.getMessage());
|
|
||||||
|
|
||||||
throw new AuthenticationServiceException("Unable to obtain Access Token: " + httpClientErrorException.getMessage());
|
throw new AuthenticationServiceException("Unable to obtain Access Token: " + e.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.debug("from TokenEndpoint jsonString = " + jsonString);
|
logger.debug("from TokenEndpoint jsonString = " + jsonString);
|
||||||
|
|
|
@ -114,11 +114,8 @@ public class DynamicServerConfigurationService implements ServerConfigurationSer
|
||||||
}
|
}
|
||||||
|
|
||||||
return servers.get(issuer);
|
return servers.get(issuer);
|
||||||
} catch (UncheckedExecutionException ue) {
|
} catch (UncheckedExecutionException | ExecutionException e) {
|
||||||
logger.warn("Couldn't load configuration for " + issuer, ue);
|
logger.warn("Couldn't load configuration for " + issuer + ": " + e);
|
||||||
return null;
|
|
||||||
} catch (ExecutionException e) {
|
|
||||||
logger.warn("Couldn't load configuration for " + issuer, e);
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -36,6 +36,38 @@ import com.google.common.collect.Sets;
|
||||||
*/
|
*/
|
||||||
public class HybridIssuerService implements IssuerService {
|
public class HybridIssuerService implements IssuerService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return
|
||||||
|
* @see org.mitre.openid.connect.client.service.impl.ThirdPartyIssuerService#getAccountChooserUrl()
|
||||||
|
*/
|
||||||
|
public String getAccountChooserUrl() {
|
||||||
|
return thirdPartyIssuerService.getAccountChooserUrl();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param accountChooserUrl
|
||||||
|
* @see org.mitre.openid.connect.client.service.impl.ThirdPartyIssuerService#setAccountChooserUrl(java.lang.String)
|
||||||
|
*/
|
||||||
|
public void setAccountChooserUrl(String accountChooserUrl) {
|
||||||
|
thirdPartyIssuerService.setAccountChooserUrl(accountChooserUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return
|
||||||
|
* @see org.mitre.openid.connect.client.service.impl.WebfingerIssuerService#isForceHttps()
|
||||||
|
*/
|
||||||
|
public boolean isForceHttps() {
|
||||||
|
return webfingerIssuerService.isForceHttps();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param forceHttps
|
||||||
|
* @see org.mitre.openid.connect.client.service.impl.WebfingerIssuerService#setForceHttps(boolean)
|
||||||
|
*/
|
||||||
|
public void setForceHttps(boolean forceHttps) {
|
||||||
|
webfingerIssuerService.setForceHttps(forceHttps);
|
||||||
|
}
|
||||||
|
|
||||||
private ThirdPartyIssuerService thirdPartyIssuerService = new ThirdPartyIssuerService();
|
private ThirdPartyIssuerService thirdPartyIssuerService = new ThirdPartyIssuerService();
|
||||||
private WebfingerIssuerService webfingerIssuerService = new WebfingerIssuerService();
|
private WebfingerIssuerService webfingerIssuerService = new WebfingerIssuerService();
|
||||||
|
|
||||||
|
|
|
@ -78,6 +78,11 @@ public class WebfingerIssuerService implements IssuerService {
|
||||||
*/
|
*/
|
||||||
private String loginPageUrl;
|
private String loginPageUrl;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Strict enfocement of "https"
|
||||||
|
*/
|
||||||
|
private boolean forceHttps = true;
|
||||||
|
|
||||||
public WebfingerIssuerService() {
|
public WebfingerIssuerService() {
|
||||||
issuers = CacheBuilder.newBuilder().build(new WebfingerIssuerFetcher());
|
issuers = CacheBuilder.newBuilder().build(new WebfingerIssuerFetcher());
|
||||||
}
|
}
|
||||||
|
@ -102,7 +107,7 @@ public class WebfingerIssuerService implements IssuerService {
|
||||||
|
|
||||||
return new IssuerServiceResponse(issuer, identifier, null);
|
return new IssuerServiceResponse(issuer, identifier, null);
|
||||||
} catch (UncheckedExecutionException | ExecutionException e) {
|
} catch (UncheckedExecutionException | ExecutionException e) {
|
||||||
logger.warn("Issue fetching issuer for user input: " + identifier, e.getMessage());
|
logger.warn("Issue fetching issuer for user input: " + identifier + ": " + e.getMessage());
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -169,6 +174,20 @@ public class WebfingerIssuerService implements IssuerService {
|
||||||
this.blacklist = blacklist;
|
this.blacklist = blacklist;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the forceHttps
|
||||||
|
*/
|
||||||
|
public boolean isForceHttps() {
|
||||||
|
return forceHttps;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param forceHttps the forceHttps to set
|
||||||
|
*/
|
||||||
|
public void setForceHttps(boolean forceHttps) {
|
||||||
|
this.forceHttps = forceHttps;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author jricher
|
* @author jricher
|
||||||
*
|
*
|
||||||
|
@ -188,9 +207,16 @@ public class WebfingerIssuerService implements IssuerService {
|
||||||
|
|
||||||
// preserving http scheme is strictly for demo system use only.
|
// preserving http scheme is strictly for demo system use only.
|
||||||
String scheme = key.getScheme();
|
String scheme = key.getScheme();
|
||||||
if (!Strings.isNullOrEmpty(scheme) && scheme.equals("http")) {
|
|
||||||
|
if (!Strings.isNullOrEmpty(scheme)) {
|
||||||
|
if (scheme.equals("http")) {
|
||||||
|
if (forceHttps) {
|
||||||
|
throw new IllegalArgumentException("Scheme must start with htps");
|
||||||
|
} else {
|
||||||
|
logger.warn("Webfinger endpoint MUST use the https URI scheme, overriding by configuration");
|
||||||
scheme = "http://"; // add on colon and slashes.
|
scheme = "http://"; // add on colon and slashes.
|
||||||
logger.warn("Webfinger endpoint MUST use the https URI scheme.");
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
scheme = "https://";
|
scheme = "https://";
|
||||||
}
|
}
|
||||||
|
|
|
@ -82,11 +82,8 @@ public class JWKSetCacheService {
|
||||||
public JWTSigningAndValidationService getValidator(String jwksUri) {
|
public JWTSigningAndValidationService getValidator(String jwksUri) {
|
||||||
try {
|
try {
|
||||||
return validators.get(jwksUri);
|
return validators.get(jwksUri);
|
||||||
} catch (UncheckedExecutionException ue) {
|
} catch (UncheckedExecutionException | ExecutionException e) {
|
||||||
logger.warn("Couldn't load JWK Set from " + jwksUri, ue);
|
logger.warn("Couldn't load JWK Set from " + jwksUri + ": " + e.getMessage());
|
||||||
return null;
|
|
||||||
} catch (ExecutionException e) {
|
|
||||||
logger.warn("Couldn't load JWK Set from " + jwksUri, e);
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -94,11 +91,8 @@ public class JWKSetCacheService {
|
||||||
public JWTEncryptionAndDecryptionService getEncrypter(String jwksUri) {
|
public JWTEncryptionAndDecryptionService getEncrypter(String jwksUri) {
|
||||||
try {
|
try {
|
||||||
return encrypters.get(jwksUri);
|
return encrypters.get(jwksUri);
|
||||||
} catch (UncheckedExecutionException ue) {
|
} catch (UncheckedExecutionException | ExecutionException e) {
|
||||||
logger.warn("Couldn't load JWK Set from " + jwksUri, ue);
|
logger.warn("Couldn't load JWK Set from " + jwksUri + ": " + e.getMessage());
|
||||||
return null;
|
|
||||||
} catch (ExecutionException e) {
|
|
||||||
logger.warn("Couldn't load JWK Set from " + jwksUri, e);
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -117,7 +111,6 @@ public class JWKSetCacheService {
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public JWTSigningAndValidationService load(String key) throws Exception {
|
public JWTSigningAndValidationService load(String key) throws Exception {
|
||||||
|
|
||||||
String jsonString = restTemplate.getForObject(key, String.class);
|
String jsonString = restTemplate.getForObject(key, String.class);
|
||||||
JWKSet jwkSet = JWKSet.parse(jsonString);
|
JWKSet jwkSet = JWKSet.parse(jsonString);
|
||||||
|
|
||||||
|
@ -126,7 +119,6 @@ public class JWKSetCacheService {
|
||||||
JWTSigningAndValidationService service = new DefaultJWTSigningAndValidationService(keyStore);
|
JWTSigningAndValidationService service = new DefaultJWTSigningAndValidationService(keyStore);
|
||||||
|
|
||||||
return service;
|
return service;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,6 +36,7 @@ import org.mitre.oauth2.repository.OAuth2ClientRepository;
|
||||||
import org.mitre.oauth2.repository.OAuth2TokenRepository;
|
import org.mitre.oauth2.repository.OAuth2TokenRepository;
|
||||||
import org.mitre.oauth2.service.ClientDetailsEntityService;
|
import org.mitre.oauth2.service.ClientDetailsEntityService;
|
||||||
import org.mitre.oauth2.service.SystemScopeService;
|
import org.mitre.oauth2.service.SystemScopeService;
|
||||||
|
import org.mitre.openid.connect.config.ConfigurationPropertiesBean;
|
||||||
import org.mitre.openid.connect.model.WhitelistedSite;
|
import org.mitre.openid.connect.model.WhitelistedSite;
|
||||||
import org.mitre.openid.connect.service.ApprovedSiteService;
|
import org.mitre.openid.connect.service.ApprovedSiteService;
|
||||||
import org.mitre.openid.connect.service.BlacklistedSiteService;
|
import org.mitre.openid.connect.service.BlacklistedSiteService;
|
||||||
|
@ -54,6 +55,7 @@ import com.google.common.base.Strings;
|
||||||
import com.google.common.cache.CacheBuilder;
|
import com.google.common.cache.CacheBuilder;
|
||||||
import com.google.common.cache.CacheLoader;
|
import com.google.common.cache.CacheLoader;
|
||||||
import com.google.common.cache.LoadingCache;
|
import com.google.common.cache.LoadingCache;
|
||||||
|
import com.google.common.util.concurrent.UncheckedExecutionException;
|
||||||
import com.google.gson.JsonElement;
|
import com.google.gson.JsonElement;
|
||||||
import com.google.gson.JsonParser;
|
import com.google.gson.JsonParser;
|
||||||
|
|
||||||
|
@ -86,6 +88,9 @@ public class DefaultOAuth2ClientDetailsEntityService implements ClientDetailsEnt
|
||||||
@Autowired
|
@Autowired
|
||||||
private StatsService statsService;
|
private StatsService statsService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ConfigurationPropertiesBean config;
|
||||||
|
|
||||||
// map of sector URI -> list of redirect URIs
|
// map of sector URI -> list of redirect URIs
|
||||||
private LoadingCache<String, List<String>> sectorRedirects = CacheBuilder.newBuilder()
|
private LoadingCache<String, List<String>> sectorRedirects = CacheBuilder.newBuilder()
|
||||||
.expireAfterAccess(1, TimeUnit.HOURS)
|
.expireAfterAccess(1, TimeUnit.HOURS)
|
||||||
|
@ -167,8 +172,8 @@ public class DefaultOAuth2ClientDetailsEntityService implements ClientDetailsEnt
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (ExecutionException e) {
|
} catch (UncheckedExecutionException | ExecutionException e) {
|
||||||
throw new IllegalArgumentException("Unable to load sector identifier URI: " + client.getSectorIdentifierUri());
|
throw new IllegalArgumentException("Unable to load sector identifier URI " + client.getSectorIdentifierUri() + ": " + e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -321,7 +326,9 @@ public class DefaultOAuth2ClientDetailsEntityService implements ClientDetailsEnt
|
||||||
public List<String> load(String key) throws Exception {
|
public List<String> load(String key) throws Exception {
|
||||||
|
|
||||||
if (!key.startsWith("https")) {
|
if (!key.startsWith("https")) {
|
||||||
// TODO: this should optionally throw an error (#506)
|
if (config.isForceHttps()) {
|
||||||
|
throw new IllegalArgumentException("Sector identifier must start with https: " + key);
|
||||||
|
}
|
||||||
logger.error("Sector identifier doesn't start with https, loading anyway...");
|
logger.error("Sector identifier doesn't start with https, loading anyway...");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -339,7 +346,7 @@ public class DefaultOAuth2ClientDetailsEntityService implements ClientDetailsEnt
|
||||||
|
|
||||||
return redirectUris;
|
return redirectUris;
|
||||||
} else {
|
} else {
|
||||||
return null;
|
throw new IllegalArgumentException("JSON Format Error");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue