Skip to content

Commit 48e0814

Browse files
committed
load resident IDP and validate its issuer against the token.
load certificate/jwks url from resident IDP and validate signature against the token.
1 parent 0703924 commit 48e0814

File tree

1 file changed

+179
-71
lines changed

1 file changed

+179
-71
lines changed

components/apimgt/org.wso2.carbon.apimgt.rest.api.util/src/main/java/org/wso2/carbon/apimgt/rest/api/util/impl/OAuthJwtAuthenticatorImpl.java

+179-71
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,16 @@
2020
import com.nimbusds.jwt.JWTClaimsSet;
2121
import com.nimbusds.jwt.SignedJWT;
2222
import com.nimbusds.jwt.util.DateUtils;
23+
import org.apache.commons.codec.binary.Base64;
24+
import org.apache.commons.lang3.ArrayUtils;
2325
import org.apache.commons.lang3.StringUtils;
2426
import org.apache.commons.logging.Log;
2527
import org.apache.commons.logging.LogFactory;
2628
import org.apache.cxf.message.Message;
2729
import org.wso2.carbon.apimgt.api.APIManagementException;
2830
import org.wso2.carbon.apimgt.api.OAuthTokenInfo;
29-
import org.wso2.carbon.apimgt.api.dto.KeyManagerConfigurationDTO;
3031
import org.wso2.carbon.apimgt.common.gateway.constants.JWTConstants;
32+
import org.wso2.carbon.apimgt.common.gateway.dto.JWKSConfigurationDTO;
3133
import org.wso2.carbon.apimgt.common.gateway.dto.JWTValidationInfo;
3234
import org.wso2.carbon.apimgt.common.gateway.dto.TokenIssuerDto;
3335
import org.wso2.carbon.apimgt.impl.APIConstants;
@@ -36,6 +38,7 @@
3638
import org.wso2.carbon.apimgt.impl.dto.KeyManagerDto;
3739
import org.wso2.carbon.apimgt.impl.factory.KeyManagerHolder;
3840
import org.wso2.carbon.apimgt.impl.jwt.JWTValidator;
41+
import org.wso2.carbon.apimgt.impl.jwt.JWTValidatorImpl;
3942
import org.wso2.carbon.apimgt.impl.jwt.SignedJWTInfo;
4043
import org.wso2.carbon.apimgt.impl.utils.APIUtil;
4144
import org.wso2.carbon.apimgt.rest.api.common.APIMConfigUtil;
@@ -45,12 +48,23 @@
4548
import org.wso2.carbon.apimgt.rest.api.util.authenticators.AbstractOAuthAuthenticator;
4649
import org.wso2.carbon.apimgt.rest.api.util.utils.RestApiUtil;
4750
import org.wso2.carbon.context.PrivilegedCarbonContext;
51+
import org.wso2.carbon.identity.application.common.model.FederatedAuthenticatorConfig;
52+
import org.wso2.carbon.identity.application.common.model.IdentityProvider;
53+
import org.wso2.carbon.identity.application.common.model.IdentityProviderProperty;
54+
import org.wso2.carbon.identity.application.common.util.IdentityApplicationConstants;
55+
import org.wso2.carbon.identity.application.common.util.IdentityApplicationManagementUtil;
56+
import org.wso2.carbon.idp.mgt.IdentityProviderManagementException;
57+
import org.wso2.carbon.idp.mgt.IdentityProviderManager;
4858
import org.wso2.carbon.user.api.UserStoreException;
4959
import org.wso2.carbon.user.core.service.RealmService;
5060
import org.wso2.carbon.utils.CarbonUtils;
5161
import org.wso2.carbon.utils.multitenancy.MultitenantConstants;
5262
import org.wso2.carbon.utils.multitenancy.MultitenantUtils;
5363

64+
import javax.security.cert.X509Certificate;
65+
import java.io.ByteArrayInputStream;
66+
import java.io.IOException;
67+
import java.io.InputStream;
5468
import java.net.MalformedURLException;
5569
import java.net.URL;
5670
import java.text.ParseException;
@@ -66,6 +80,8 @@ public class OAuthJwtAuthenticatorImpl extends AbstractOAuthAuthenticator {
6680
private static final Log log = LogFactory.getLog(OAuthJwtAuthenticatorImpl.class);
6781
private static final String SUPER_TENANT_SUFFIX =
6882
APIConstants.EMAIL_DOMAIN_SEPARATOR + MultitenantConstants.SUPER_TENANT_DOMAIN_NAME;
83+
private static final String OIDC_IDP_ENTITY_ID = "IdPEntityId";
84+
public static final String JWKS_URI = "jwksUri";
6985
private boolean isRESTApiTokenCacheEnabled;
7086
private Map<String, TokenIssuerDto> tokenIssuers;
7187

@@ -226,70 +242,65 @@ private JWTValidationInfo validateJWTToken(SignedJWTInfo signedJWTInfo, String j
226242

227243
if (StringUtils.isNotEmpty(issuer)) {
228244
//validate Issuer
229-
if (tokenIssuers != null && validateIssuer(subject, issuer, maskedToken)) {
230-
if (isRESTApiTokenCacheEnabled) {
231-
JWTValidationInfo tempJWTValidationInfo = (JWTValidationInfo) getRESTAPITokenCache().get(jti);
232-
if (tempJWTValidationInfo != null) {
233-
boolean isExpired = checkTokenExpiration(new Date(tempJWTValidationInfo.getExpiryTime()));
234-
if (isExpired) {
235-
tempJWTValidationInfo.setValid(false);
236-
getRESTAPITokenCache().remove(jti);
237-
getRESTAPIInvalidTokenCache().put(jti, tempJWTValidationInfo);
238-
log.error("JWT token validation failed. Reason: Expired Token. " + maskedToken);
239-
return tempJWTValidationInfo;
240-
}
241-
//check accessToken
242-
if (!tempJWTValidationInfo.getRawPayload().equals(accessToken)) {
243-
tempJWTValidationInfo.setValid(false);
244-
getRESTAPITokenCache().remove(jti);
245-
getRESTAPIInvalidTokenCache().put(jti, tempJWTValidationInfo);
246-
log.error("JWT token validation failed. Reason: Invalid Token. " + maskedToken);
247-
return tempJWTValidationInfo;
248-
}
245+
JWTValidator jwtValidator;
246+
try {
247+
jwtValidator = validateAndGetJWTValidatorForIssuer(subject, issuer, maskedToken);
248+
} catch (APIManagementException e) {
249+
log.error(e.getMessage(), e);
250+
return null;
251+
}
252+
if (isRESTApiTokenCacheEnabled) {
253+
JWTValidationInfo tempJWTValidationInfo = (JWTValidationInfo) getRESTAPITokenCache().get(jti);
254+
if (tempJWTValidationInfo != null) {
255+
boolean isExpired = checkTokenExpiration(new Date(tempJWTValidationInfo.getExpiryTime()));
256+
if (isExpired) {
257+
tempJWTValidationInfo.setValid(false);
258+
getRESTAPITokenCache().remove(jti);
259+
getRESTAPIInvalidTokenCache().put(jti, tempJWTValidationInfo);
260+
log.error("JWT token validation failed. Reason: Expired Token. " + maskedToken);
249261
return tempJWTValidationInfo;
262+
}
263+
//check accessToken
264+
if (!tempJWTValidationInfo.getRawPayload().equals(accessToken)) {
265+
tempJWTValidationInfo.setValid(false);
266+
getRESTAPITokenCache().remove(jti);
267+
getRESTAPIInvalidTokenCache().put(jti, tempJWTValidationInfo);
268+
log.error("JWT token validation failed. Reason: Invalid Token. " + maskedToken);
269+
return tempJWTValidationInfo;
270+
}
271+
return tempJWTValidationInfo;
250272

251-
} else if (getRESTAPIInvalidTokenCache().get(jti) != null) {
252-
if (log.isDebugEnabled()) {
253-
log.debug("Token retrieved from the invalid token cache. Token: " + maskedToken);
254-
}
255-
return (JWTValidationInfo) getRESTAPIInvalidTokenCache().get(jti);
273+
} else if (getRESTAPIInvalidTokenCache().get(jti) != null) {
274+
if (log.isDebugEnabled()) {
275+
log.debug("Token retrieved from the invalid token cache. Token: " + maskedToken);
256276
}
277+
return (JWTValidationInfo) getRESTAPIInvalidTokenCache().get(jti);
257278
}
258-
//info not in cache. validate signature and exp
259-
JWTValidator jwtValidator = getJWTValidator(issuer, subject, maskedToken);
260-
if (jwtValidator != null) {
261-
jwtValidationInfo = jwtValidator.validateToken(signedJWTInfo);
262-
if (jwtValidationInfo.isValid()) {
263-
//valid token
264-
if (isRESTApiTokenCacheEnabled) {
265-
getRESTAPITokenCache().put(jti, jwtValidationInfo);
266-
}
267-
} else {
268-
//put in invalid cache
269-
if (isRESTApiTokenCacheEnabled) {
270-
getRESTAPIInvalidTokenCache().put(jti, jwtValidationInfo);
271-
}
272-
//invalid credentials : 900901 error code
273-
log.error("JWT token validation failed. Reason: Invalid Credentials. " +
274-
"Make sure you have provided the correct security credentials in the token :"
275-
+ maskedToken);
279+
}
280+
//info not in cache. validate signature and exp
281+
if (jwtValidator != null) {
282+
jwtValidationInfo = jwtValidator.validateToken(signedJWTInfo);
283+
if (jwtValidationInfo.isValid()) {
284+
//valid token
285+
if (isRESTApiTokenCacheEnabled) {
286+
getRESTAPITokenCache().put(jti, jwtValidationInfo);
276287
}
277288
} else {
278-
log.error("JWT token issuer validation failed. Reason: Cannot find a JWTValidator for the " +
279-
"issuer present in the JWT: " + issuer);
280-
return null;
289+
//put in invalid cache
290+
if (isRESTApiTokenCacheEnabled) {
291+
getRESTAPIInvalidTokenCache().put(jti, jwtValidationInfo);
292+
}
293+
//invalid credentials : 900901 error code
294+
log.error("JWT token validation failed. Reason: Invalid Credentials. " +
295+
"Make sure you have provided the correct security credentials in the token :"
296+
+ maskedToken);
281297
}
282298
} else {
283-
//invalid issuer. invalid token
284-
if (tokenIssuers != null) {
285-
log.error("JWT token issuer validation failed. Reason: Issuer present in the JWT (" + issuer
286-
+ ") does not match with the token issuer (" + tokenIssuers.keySet() + ")");
287-
} else {
288-
log.error("JWT token issuer validation failed. Reason: Issuer present in the JWT (" + issuer
289-
+ ") does not match with the token issuer.");
290-
}
299+
log.error("JWT token issuer validation failed. Reason: Cannot find a JWTValidator for the " +
300+
"issuer present in the JWT: " + issuer);
291301
return null;
292302
}
303+
293304
} else {
294305
log.error("Issuer is not found in the token " + maskedToken);
295306
return null;
@@ -315,16 +326,15 @@ private String getOrganizationFromSubject(String subject, String maskedToken) {
315326
/**
316327
* Retrieve JWT Validator for the given issuer.
317328
*
318-
* @param issuer Issuer from the token
319-
* @param subject Subject from the token
320-
* @param maskedToken Masked token string for logging
329+
* @param issuer Issuer from the token
330+
* @param organization Organization
331+
* @param maskedToken Masked token string for logging
321332
* @return JWTValidator implementation for the given issuer.
322333
*/
323-
private JWTValidator getJWTValidator(String issuer, String subject, String maskedToken) {
334+
private JWTValidator getJWTValidator(String issuer, String organization, String maskedToken) {
324335

325336
JWTValidator jwtValidator = APIMConfigUtil.getJWTValidatorMap().get(issuer);
326337
if (jwtValidator == null) {
327-
String organization = getOrganizationFromSubject(subject, maskedToken);
328338
if (StringUtils.isNotEmpty(issuer) && StringUtils.isNotEmpty(organization)) {
329339
KeyManagerDto keyManagerDto = KeyManagerHolder.getKeyManagerByIssuer(organization, issuer);
330340
if (keyManagerDto != null && keyManagerDto.getJwtValidator() != null) {
@@ -341,26 +351,68 @@ private JWTValidator getJWTValidator(String issuer, String subject, String maske
341351
* @param subject Subject to derive the logged-in organization
342352
* @param tokenIssuer Token issuer from the token
343353
* @param maskedToken Masked token for logging purposes
344-
* @return if issuer validation fails or sucess
354+
* @return if issuer validation fails or success
345355
* @throws APIManagementException if an error occurs during validation
346356
*/
347-
private boolean validateIssuer(String subject, String tokenIssuer, String maskedToken)
357+
private JWTValidator validateAndGetJWTValidatorForIssuer(String subject, String tokenIssuer, String maskedToken)
348358
throws APIManagementException {
349359

350360
String organization = getOrganizationFromSubject(subject, maskedToken);
351361
if (tokenIssuers != null && !tokenIssuers.isEmpty()) {
352-
return tokenIssuers.containsKey(tokenIssuer);
362+
if (tokenIssuers.containsKey(tokenIssuer)) {
363+
return getJWTValidator(tokenIssuer, organization, maskedToken);
364+
}
365+
throw new APIManagementException("JWT token issuer validation failed. Reason: Issuer present in the JWT ("
366+
+ tokenIssuer + ") does not match with the token issuer (" + tokenIssuers.keySet() + ")");
353367
}
354-
KeyManagerConfigurationDTO keyManagerConfigurationDTO =
355-
APIUtil.getDefaultKeyManagerConfiguration(organization);
356-
if (keyManagerConfigurationDTO == null) {
368+
IdentityProvider residentIDP = validateAndGetResidentIDPForIssuer(organization, tokenIssuer);
369+
if (residentIDP == null) {
357370
//invalid issuer. invalid token
358-
log.error("JWT token issuer validation failed. Reason: Default Key Manager configuration cannot be"
359-
+ " found for the organization: " + organization);
360-
return false;
371+
throw new APIManagementException("JWT token issuer validation failed. Reason: Resident Identity Provider "
372+
+ "cannot be found for the organization: " + organization);
373+
}
374+
JWTValidator jwtValidator = new JWTValidatorImpl();
375+
TokenIssuerDto tokenIssuerDto = new TokenIssuerDto(tokenIssuer);
376+
if (residentIDP.getCertificate() != null) {
377+
tokenIssuerDto.setCertificate(retrieveCertificateFromContent(residentIDP.getCertificate()));
378+
} else {
379+
JWKSConfigurationDTO jwksConfigurationDTO = new JWKSConfigurationDTO();
380+
jwksConfigurationDTO.setEnabled(true);
381+
jwksConfigurationDTO.setUrl(getJwksUriForIDP(residentIDP));
382+
tokenIssuerDto.setJwksConfigurationDTO(jwksConfigurationDTO);
383+
}
384+
jwtValidator.loadTokenIssuerConfiguration(tokenIssuerDto);
385+
return jwtValidator;
386+
}
387+
388+
/**
389+
* Retrieve JWKS URI configured for the resident IDP.
390+
*
391+
* @param idp IdentityProvider
392+
* @return JWKS URI
393+
*/
394+
private String getJwksUriForIDP(IdentityProvider idp) {
395+
396+
String jwksUri = null;
397+
IdentityProviderProperty[] identityProviderProperties = idp.getIdpProperties();
398+
if (!ArrayUtils.isEmpty(identityProviderProperties)) {
399+
for (IdentityProviderProperty identityProviderProperty : identityProviderProperties) {
400+
if (StringUtils.equals(identityProviderProperty.getName(), JWKS_URI)) {
401+
jwksUri = identityProviderProperty.getValue();
402+
if (log.isDebugEnabled()) {
403+
log.debug("JWKS endpoint set for the identity provider : " + idp.getIdentityProviderName()
404+
+ ", jwks_uri : " + jwksUri);
405+
}
406+
break;
407+
} else {
408+
if (log.isDebugEnabled()) {
409+
log.debug("JWKS endpoint not specified for the identity provider : "
410+
+ idp.getIdentityProviderName());
411+
}
412+
}
413+
}
361414
}
362-
return StringUtils.equals(tokenIssuer, (String) keyManagerConfigurationDTO.getAdditionalProperties()
363-
.get(APIConstants.KeyManager.ISSUER));
415+
return jwksUri;
364416
}
365417

366418
/**
@@ -398,4 +450,60 @@ private String getJWTTokenIdentifier(SignedJWTInfo signedJWTInfo) {
398450
}
399451
return signedJWTInfo.getSignedJWT().getSignature().toString();
400452
}
453+
454+
455+
/**
456+
* Validate issuer and get resident Identity Provider.
457+
*
458+
* @param tenantDomain tenant Domain
459+
* @param jwtIssuer issuer extracted from assertion
460+
* @return resident Identity Provider
461+
* @throws APIManagementException if an error occurs while retrieving resident idp issuer
462+
*/
463+
private IdentityProvider validateAndGetResidentIDPForIssuer(String tenantDomain, String jwtIssuer)
464+
throws APIManagementException {
465+
466+
String issuer = StringUtils.EMPTY;
467+
IdentityProvider residentIdentityProvider;
468+
try {
469+
residentIdentityProvider = IdentityProviderManager.getInstance().getResidentIdP(tenantDomain);
470+
} catch (IdentityProviderManagementException e) {
471+
String errorMsg = String.format("Error while getting Resident Identity Provider of '%s' tenant.",
472+
tenantDomain);
473+
throw new APIManagementException(errorMsg, e);
474+
}
475+
FederatedAuthenticatorConfig[] fedAuthnConfigs = residentIdentityProvider.getFederatedAuthenticatorConfigs();
476+
FederatedAuthenticatorConfig oauthAuthenticatorConfig =
477+
IdentityApplicationManagementUtil.getFederatedAuthenticator(fedAuthnConfigs,
478+
IdentityApplicationConstants.Authenticator.OIDC.NAME);
479+
if (oauthAuthenticatorConfig != null) {
480+
issuer = IdentityApplicationManagementUtil.getProperty(oauthAuthenticatorConfig.getProperties(),
481+
OIDC_IDP_ENTITY_ID).getValue();
482+
}
483+
return jwtIssuer.equals(issuer) ? residentIdentityProvider : null;
484+
}
485+
486+
/**
487+
* Util method to convert base64 encoded certificate content to X509Certificate instance.
488+
*
489+
* @param base64EncodedCertificate Base64 encoded cert string (not URL encoded)
490+
* @return javax.security.cert.X509Certificate
491+
* @throws APIManagementException if an error occurs while retrieving from IDP
492+
*/
493+
private X509Certificate retrieveCertificateFromContent(String base64EncodedCertificate)
494+
throws APIManagementException {
495+
496+
if (base64EncodedCertificate != null) {
497+
base64EncodedCertificate = APIUtil.getX509certificateContent(base64EncodedCertificate);
498+
byte[] bytes = Base64.decodeBase64(base64EncodedCertificate.getBytes());
499+
try (InputStream inputStream = new ByteArrayInputStream(bytes)) {
500+
return X509Certificate.getInstance(inputStream);
501+
} catch (IOException | javax.security.cert.CertificateException e) {
502+
String msg = "Error while converting into X509Certificate";
503+
log.error(msg, e);
504+
throw new APIManagementException(msg, e);
505+
}
506+
}
507+
return null;
508+
}
401509
}

0 commit comments

Comments
 (0)