Skip to content

Commit

Permalink
Merge pull request #6 from integon/feature/1.2.0-thread-safety
Browse files Browse the repository at this point in the history
feat:1.2.0: implement thread-safety for mediator
  • Loading branch information
mathias-paris authored May 21, 2024
2 parents 216d069 + 741711d commit 53da6ec
Show file tree
Hide file tree
Showing 4 changed files with 69 additions and 114 deletions.
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# wso2-mi-jwt-validator
The wso2-mi-jwt-validator is a custom handler and mediator for the WSO2 Micro Integrator. This class can be used to validate JWT tokens against a JWKS endpoint. The class can be used as a custom handler or as a custom mediator. The following example shows how to use the class as a custom handler and mediator.

The wso2-mi-jwt-validator is available on the Maven Central Repository. You can find the latest version [here](https://s01.oss.sonatype.org/service/local/repositories/releases/content/io/integon/wso2mi/jwt/wso2-mi-jwt-validator/1.1.5).
The wso2-mi-jwt-validator is available on the Maven Central Repository. You can find the latest version [here](https://s01.oss.sonatype.org/service/local/repositories/releases/content/io/integon/wso2mi/jwt/wso2-mi-jwt-validator/1.2.0).

[TOC]

Expand All @@ -13,7 +13,7 @@ Add the following dependencies to your pom.xml file:
<dependency>
<groupId>io.integon.wso2mi.jwt</groupId>
<artifactId>wso2-mi-jwt-validator</artifactId>
<version>1.1.4</version>
<version>1.2.0</version>
</dependency>
<dependency>
<groupId>com.nimbusds</groupId>
Expand All @@ -26,10 +26,10 @@ These dependencies are required for the wso2-mi-jwt-validator to work. The wso2-

### Without pom.xml
Add the following .jar Files to the MI Folder "/home/wso2carbon/wso2mi-{version}/lib"
- wso2-mi-jwt-validator-1.1.5.jar (or the latest version)
- wso2-mi-jwt-validator-1.2.0.jar (or the latest version)
- nimbus-jose-jwt-9.37.3.jar (or the latest version)

Both .jar files are available on the Maven Central Repository. You can find the latest version [here](https://s01.oss.sonatype.org/service/local/repositories/releases/content/io/integon/wso2mi/jwt/wso2-mi-jwt-validator/1.1.5/wso2-mi-jwt-validator-1.1.3.jar) and [here](https://mvnrepository.com/artifact/com.nimbusds/nimbus-jose-jwt).
Both .jar files are available on the Maven Central Repository. You can find the latest version [here](https://s01.oss.sonatype.org/service/local/repositories/releases/content/io/integon/wso2mi/jwt/wso2-mi-jwt-validator/1.2.0/wso2-mi-jwt-validator-1.2.0.jar) and [here](https://mvnrepository.com/artifact/com.nimbusds/nimbus-jose-jwt).

## Usage
### Available Properties (Custom Handler)
Expand Down
4 changes: 2 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>io.integon.wso2mi.jwt</groupId>
<artifactId>wso2-mi-jwt-validator</artifactId>
<version>1.1.5</version>
<version>1.2.0</version>

<name>wso2-mi-jwt-validator</name>
<description>A brief description of my project.</description>
Expand Down Expand Up @@ -265,4 +265,4 @@
</build>
</profile>
</profiles>
</project>
</project>
9 changes: 4 additions & 5 deletions src/main/java/io/integon/JWTValidator.java
Original file line number Diff line number Diff line change
Expand Up @@ -275,8 +275,7 @@ private void getAndVerifyJWKByKid(SignedJWT signedJWT) throws Exception {
* @throws Exception
* If there is a parse exception while loading the JwkSet.
*/
private void loadAndCacheJWKSet(String jwksEndpoint) throws Exception {

private synchronized void loadAndCacheJWKSet(String jwksEndpoint) throws Exception {
if (jwkSet == null || cachedTimeJWKSet + ttl < System.currentTimeMillis()
|| !cachedJwksEndpoint.equals(jwksEndpoint)) {
try {
Expand All @@ -293,12 +292,12 @@ private void loadAndCacheJWKSet(String jwksEndpoint) throws Exception {
} else if (cachedTimeJWKSet + refreshTimeout < System.currentTimeMillis()) {
try {
jwkSet = JWKSet.load(new URL(jwksEndpoint));
log.debug("JWK set loaded from the provided endpoint (refresh): " + jwksEndpoint);
log.debug("JWK set refreshed from the provided endpoint: " + jwksEndpoint);
cachedTimeJWKSet = System.currentTimeMillis();
cachedJwksEndpoint = jwksEndpoint;
} catch (Exception ignored) {
} catch (Exception e) {
log.error("Failed to refresh JWKS from the provided endpoint: " + e.getMessage());
}
// Ignore any exceptions while refreshing the cache
}
}

Expand Down
162 changes: 59 additions & 103 deletions src/main/java/io/integon/JwtAuthMediator.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,25 +18,21 @@ public class JwtAuthMediator extends AbstractMediator {

private static final Log log = LogFactory.getLog(JwtAuthMediator.class);

private String jwtToken;
private String jwksEndpoint;
private String jwksEnvVariable;
private String iatClaim;
private String issClaim;
private String subClaim;
private String audClaim;
private String jtiClaim;
private String jwksTimeout;
private String jwksRefreshTime;

private long cachedTimeValidator = 0;
private long cachedTimeValidatorReset = 86400000; // 24 hours

private JWTValidator validator = null;

private String forwardToken;

private String respond;
// Static counter for instances
private static int instanceCount = 0;

// Constructor
public JwtAuthMediator() {
synchronized (JwtAuthMediator.class) {
instanceCount++;
log.info("JwtAuthMediator instance created. Current instance count: " + instanceCount);
}
}

/**
* This method is called when the request is received by the API Get properties
Expand All @@ -52,17 +48,37 @@ public class JwtAuthMediator extends AbstractMediator {
*/
@Override
public boolean mediate(MessageContext messageContext) {
try {
applyProperties(messageContext);
} catch (Exception e) {
handleException(e.getMessage(), messageContext);
return false;
}
// initialize the JWTValidator
if (validator == null || cachedTimeValidator + cachedTimeValidatorReset < System.currentTimeMillis()) {
validator = new JWTValidator();
log.debug("JWTValidator: " + validator);
cachedTimeValidator = System.currentTimeMillis();
}

String jwksEndpoint = (String) messageContext.getProperty("jwksEndpoint");
String jwksEnvVariable = (String) messageContext.getProperty("jwksEnvVariable");
if ((jwksEndpoint == null || jwksEndpoint.isEmpty())
&& (jwksEnvVariable == null || jwksEnvVariable.isEmpty())) {
handleException("JWKS endpoint not found in the message", messageContext);
}

// If jwksEnvVariable is set, check if the environment variable contains a valid URL
if (jwksEnvVariable != null && CommonUtils.containsUrl(System.getenv().get(jwksEnvVariable))) {
jwksEndpoint = System.getenv().get(jwksEnvVariable);
log.debug("JWKS endpoint from Env Variable " + jwksEnvVariable + ": " + jwksEndpoint);
}

// retrieve JWKS_TIMEOUT & JWKS_REFRESH_TIME from the message context
String jwksTimeout = (String) messageContext.getProperty("jwksTimeout");
String jwksRefreshTime = (String) messageContext.getProperty("jwksRefreshTime");
validator.setCacheTimeouts(jwksTimeout, jwksRefreshTime);

String jwtToken = (String) messageContext.getProperty("jwtToken");
if (jwtToken == null || jwtToken.isEmpty()) {
log.debug("JWT not found in the message");
handleException("JWT not found in the message",messageContext);
}

// Check if the token starts with "Bearer "
if (!jwtToken.trim().startsWith("Bearer")) {
log.debug("Invalid JWT format: " + jwtToken);
Expand All @@ -77,22 +93,6 @@ public boolean mediate(MessageContext messageContext) {
return false;
}
}
// If jwksEnvVariable is set, check if the environment variable contains a valid
// URL
if (jwksEnvVariable != null && CommonUtils.containsUrl(System.getenv().get(jwksEnvVariable))) {
jwksEndpoint = System.getenv().get(jwksEnvVariable);
log.debug("JWKS endpoint from Env Variable " + jwksEnvVariable + ": " + jwksEndpoint);
} else {
// Check if the JWKS endpoint
if (jwksEndpoint == null || jwksEndpoint.isEmpty()) {
log.debug("JWKS endpoint not found in the message context or environment variable");
handleException("JWKS endpoint not found", messageContext);
return false;
}
}

// retrieve JWKS_TIMEOUT & JWKS_REFRESH_TIME from the message context
validator.setCacheTimeouts(jwksTimeout, jwksRefreshTime);

// validate the JWT token
boolean isValidJWT;
Expand All @@ -116,6 +116,26 @@ public boolean mediate(MessageContext messageContext) {
}

// retrieve the sub claim from the message context
String iatClaim = (String) messageContext.getProperty("iatClaim");
if (iatClaim != null && iatClaim.isEmpty()) {
iatClaim = null;
}
String issClaim = (String) messageContext.getProperty("issClaim");
if (issClaim != null && issClaim.isEmpty()) {
issClaim = null;
}
String subClaim = (String) messageContext.getProperty("subClaim");
if (subClaim != null && subClaim.isEmpty()) {
subClaim = null;
}
String audClaim = (String) messageContext.getProperty("audClaim");
if (audClaim != null && audClaim.isEmpty()) {
audClaim = null;
}
String jtiClaim = (String) messageContext.getProperty("jtiClaim");
if (jtiClaim != null && jtiClaim.isEmpty()) {
jtiClaim = null;
}
HashMap<String, String> claims = new HashMap<String, String>();
claims.put("iat", iatClaim);
claims.put("iss", issClaim);
Expand All @@ -141,6 +161,7 @@ public boolean mediate(MessageContext messageContext) {
}
log.debug("JWT validation successful");

String forwardToken = (String) messageContext.getProperty("forwardToken");
log.debug("Forward token: " + forwardToken);
if (forwardToken != null && forwardToken.equals("true")) {
log.debug("Set JWT token in the message context");
Expand All @@ -156,72 +177,6 @@ public boolean mediate(MessageContext messageContext) {
return true;
}

/**
* Retrieve the properties from the message context Check if the required
* properties are set If not, throw an exception
*
* @param messageContext
* Synapse message context
* @throws Exception
* if a required property is not set
*/
private void applyProperties(MessageContext messageContext) throws Exception {
clearProperties();
respond = (String) messageContext.getProperty("respond");
jwtToken = (String) messageContext.getProperty("jwtToken");
if (jwtToken == null || jwtToken.isEmpty()) {
throw new Exception("JWT not found in the message");
}
jwksEndpoint = (String) messageContext.getProperty("jwksEndpoint");
jwksEnvVariable = (String) messageContext.getProperty("jwksEnvVariable");
if ((jwksEndpoint == null || jwksEndpoint.isEmpty())
&& (jwksEnvVariable == null || jwksEnvVariable.isEmpty())) {
throw new Exception("JWKS endpoint not found in the message");
}
iatClaim = (String) messageContext.getProperty("iatClaim");
if (iatClaim != null && iatClaim.isEmpty()) {
iatClaim = null;
}
issClaim = (String) messageContext.getProperty("issClaim");
if (issClaim != null && issClaim.isEmpty()) {
issClaim = null;
}
subClaim = (String) messageContext.getProperty("subClaim");
if (subClaim != null && subClaim.isEmpty()) {
subClaim = null;
}
audClaim = (String) messageContext.getProperty("audClaim");
if (audClaim != null && audClaim.isEmpty()) {
audClaim = null;
}
jtiClaim = (String) messageContext.getProperty("jtiClaim");
if (jtiClaim != null && jtiClaim.isEmpty()) {
jtiClaim = null;
}
jwksTimeout = (String) messageContext.getProperty("jwksTimeout");
jwksRefreshTime = (String) messageContext.getProperty("jwksRefreshTime");
forwardToken = (String) messageContext.getProperty("forwardToken");

log.debug("applyProperties set");
}

/**
* This method is used to clear the properties
*/
private void clearProperties() {
jwtToken = null;
jwksEndpoint = null;
jwksEnvVariable = null;
iatClaim = null;
issClaim = null;
subClaim = null;
audClaim = null;
jtiClaim = null;
jwksTimeout = null;
jwksRefreshTime = null;
log.debug("Properties cleared");
}

/**
* This method is used to handle the exceptions
*
Expand Down Expand Up @@ -258,7 +213,8 @@ protected void handleException(String message, MessageContext messageContext) {
axis2MessageContext.setProperty("messageType", "application/json");
axis2MessageContext.setProperty("ContentType", "application/json");

// Respond from mediator if respond is 'true' else throw SynapseException
// Respond from mediator if respond is 'true' else throw SynapseException
String respond = (String) messageContext.getProperty("respond");
if (respond != null && respond.equals("true")){
log.debug("Respond from Mediator");
// Set the "to" property to null
Expand Down

0 comments on commit 53da6ec

Please sign in to comment.