December 26, 2019
Greetings Everyone 😊 …
This is another medium on walking 🚶 and swimming 🏊🏼 through customizing the opaque access tokens using APIM Token Issuer when we do have a requirement issue
The demo is presented and walked-through using WSO2 API Manager v2.6
Whaaaaat? wait… Why? 🤔
You probably now have a question on why do we need to do that and does it actually matter… Let me give you a simple stupid occasion on when and where I actually need that.
RepoDeWS is a developer-management system which has a set of in-built management services in SOAP. These SOAP services are exposed as REST APIs with the help of WSO2 API Manager. Now the RepoDeWS team is having an undergoing process of security maintenance and they are expecting their users to invoke the APIs using access tokens with a “Dev-Hash” value appended to it.
This “Dev-Hash” value will be passed as a Header (devhash) when requesting an Access Token and will be appended during the generation of the opaque access token
Oh… I forgot to mention that the Dev-Hash value is a unique hash value given to a set of top-rated developers to enrich their API responses with the last accessed timestamps 😅
Let’s just focus on the implementation and enhancements and will keep our use-cases behind that.
To accomplish my simple requirement, We have to extend the implementation of APIMTokenIssuer and push our enhancements.
The APIMTokenIssuer is responsible for generating opaque access tokens and JWT tokens in the WSO2 API Manager platform
First, create a simple maven project and add the following dependency to the POM. This will help us to resolve most of the dependency issues when extending the APIMTokenIssuer class.
<dependencies><dependency><groupId>org.wso2.carbon.apimgt</groupId><artifactId>org.wso2.carbon.apimgt.keymgt</artifactId><version>6.5.222</version></dependency></dependencies>
But, if you encounter any dependency problems or if you are not able to resolve the dependencies in the POM, then add the following as well …
<repositories><repository><id>wso2-nexus</id><url>http://maven.wso2.org/nexus/content/groups/wso2-public/</url><releases><enabled>true</enabled><updatePolicy>daily</updatePolicy><checksumPolicy>ignore</checksumPolicy></releases></repository></repositories><pluginRepositories><pluginRepository><id>wso2-nexus</id><url>http://maven.wso2.org/nexus/content/groups/wso2-public/</url><releases><enabled>true</enabled><updatePolicy>daily</updatePolicy><checksumPolicy>ignore</checksumPolicy></releases></pluginRepository></pluginRepositories>
Phew 😅
Managing dependencies is harder than managing expenses bruh 😬😬😭 …
Now let’s jump into the extended implementation. Given below is the piece of code (I) developed to solve my own problem [Am I overreacting 😳 ? I think I am 😒]
package com.sample.token;import org.wso2.carbon.apimgt.impl.APIConstants;import org.wso2.carbon.apimgt.impl.utils.APIUtil;import org.wso2.carbon.apimgt.keymgt.issuers.APIMTokenIssuer;import org.wso2.carbon.identity.oauth2.token.OAuthTokenReqMessageContext;import org.wso2.carbon.identity.oauth2.model.RequestParameter;public class MyAPIMTokenIssuer extends APIMTokenIssuer {private static final Log log = LoggerFactory.getLog(MyAPIMTokenIssuer.class)@Overridepublic String accessToken(OAuthTokenReqMessageContext tokReqMsgCtx) throws OAuthSystemException {// generate access token using super methodString accessToken = super.accessToken(tokReqMsgCtx);String clientId = tokReqMsgCtx.getOauth2AccessTokenReqDTO().getClientId();Application application;try {application = APIUtil.getApplicationByClientId(clientId);String tokenType = application.getTokenType();// only acceptable for opaque access tokens and not JWT tokensif (!APIConstants.JWT.equals(tokenType)) {// retrieve all request parameters sent to the token requestRequestParameter[] reqParams = tokReqMsgCtx.getOauth2AccessTokenreqDTO().getRequestParameters();for (int i = 0l i < reqParams.length; i++) {// check for devhash from the request parameters// and append it to the access tokenif ("devhash".equals(reqParams[i].getKey())) {accessToken += reqParams[i].getValue()[0];break;}}}} catch (APIManagementException e) {log.error("Exception occured ", e);}return accessToken;}}
I know right, it’s really hard to follow these things up 😐
Therefore, I give you a golden opportunity… Please go and find the sample implementation (gist) from GitHub Gist (save your time on reading this medium) and enjoy enriching it 😍
package com.sample.token; | |
import org.apache.oltu.oauth2.common.exception.OAuthSystemException; | |
import org.wso2.carbon.apimgt.api.APIManagementException; | |
import org.wso2.carbon.apimgt.api.model.Application; | |
import org.wso2.carbon.apimgt.impl.APIConstants; | |
import org.wso2.carbon.apimgt.impl.utils.APIUtil; | |
import org.wso2.carbon.apimgt.keymgt.issuers.APIMTokenIssuer; | |
import org.wso2.carbon.identity.oauth2.token.OAuthTokenReqMessageContext; | |
import org.wso2.carbon.identity.oauth2.model.RequestParameter; | |
import org.apache.commons.logging.Log; | |
import org.apache.commons.logging.LogFactory; | |
public class MyAPIMTokenIssuer extends APIMTokenIssuer { | |
private static final Log log = LogFactory.getLog(MyAPIMTokenIssuer.class); | |
@Override | |
public String accessToken(OAuthTokenReqMessageContext tokReqMsgCtx) throws OAuthSystemException { | |
// generate access token using super method | |
String accessToken = super.accessToken(tokReqMsgCtx); | |
String clientId = tokReqMsgCtx.getOauth2AccessTokenReqDTO().getClientId(); | |
Application application; | |
try { | |
application = APIUtil.getApplicationByClientId(clientId); | |
String tokenType = application.getTokenType(); | |
// only acceptable for opaque access tokens and not JWT tokens | |
if (!APIConstants.JWT.equals(tokenType)) { | |
// retrieve all request parameters sent to the token request | |
RequestParameter[] reqParams = tokReqMsgCtx.getOauth2AccessTokenReqDTO().getRequestParameters(); | |
for (int i = 0; i < reqParams.length; i++) { | |
// check for devhash parameter from the request parameters and append it to the | |
// access token | |
if ("devhash".equals(reqParams[i].getKey())) { | |
accessToken += reqParams[i].getValue()[0]; | |
break; | |
} | |
} | |
} | |
} catch (APIManagementException e) { | |
log.error("Exception occured in my piece of code ", e); | |
} | |
return accessToken; | |
} | |
} |
<?xml version="1.0" encoding="UTF-8"?> | |
<project xmlns="http://maven.apache.org/POM/4.0.0" | |
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | |
<modelVersion>4.0.0</modelVersion> | |
<groupId>com.sample.token</groupId> | |
<artifactId>custom-token-issuer</artifactId> | |
<version>1.0.0</version> | |
<!-- change the packaging to bundle (from jar) to support OSGi --> | |
<packaging>jar</packaging> | |
<properties> | |
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> | |
<maven.compiler.source>1.8</maven.compiler.source> | |
<maven.compiler.target>1.8</maven.compiler.target> | |
</properties> | |
<dependencies> | |
<dependency> | |
<groupId>org.wso2.carbon.apimgt</groupId> | |
<artifactId>org.wso2.carbon.apimgt.keymgt</artifactId> | |
<version>6.5.222</version> | |
</dependency> | |
</dependencies> | |
<repositories> | |
<repository> | |
<id>wso2-nexus</id> | |
<url>http://maven.wso2.org/nexus/content/groups/wso2-public/</url> | |
<releases> | |
<enabled>true</enabled> | |
<updatePolicy>daily</updatePolicy> | |
<checksumPolicy>ignore</checksumPolicy> | |
</releases> | |
</repository> | |
</repositories> | |
<pluginRepositories> | |
<pluginRepository> | |
<id>wso2-nexus</id> | |
<url>http://maven.wso2.org/nexus/content/groups/wso2-public/</url> | |
<releases> | |
<enabled>true</enabled> | |
<updatePolicy>daily</updatePolicy> | |
<checksumPolicy>ignore</checksumPolicy> | |
</releases> | |
</pluginRepository> | |
</pluginRepositories> | |
<build> | |
<plugins> | |
<plugin> | |
<groupId>org.apache.maven.plugins</groupId> | |
<artifactId>maven-compiler-plugin</artifactId> | |
<version>2.0</version> | |
<configuration> | |
<source>1.8</source> | |
<target>1.8</target> | |
</configuration> | |
</plugin> | |
<!-- build plugin for OSGi Bundle support --> | |
<plugin> | |
<groupId>org.apache.felix</groupId> | |
<artifactId>maven-bundle-plugin</artifactId> | |
<version>2.3.4</version> | |
<extensions>true</extensions> | |
<configuration> | |
<instructions> | |
<Bundle-SymbolicName>com.sample.token</Bundle-SymbolicName> | |
<Bundle-Name>com.sample.token</Bundle-Name> | |
<Export-Package> | |
com.sample.token.*, | |
</Export-Package> | |
<Import-Package> | |
*; resolution:=optional | |
</Import-Package> | |
</instructions> | |
</configuration> | |
</plugin> | |
</plugins> | |
</build> | |
</project> |
Your inner voice: Hmmm… Thanks for that. I have gone through the samples and copied everything from the given sources. Now, what should I do next? Where should I put these implementations???
Me: Ummm… I think I heard your inner voice. But thank you for the question.
As of next steps we have to build-up the maven project by executing the following command from a terminal
mvn clean package
And then copy and place the built JAR artifact from the target
folder to the <APIM>/repository/components/lib directory
.
And at last [Whoa…. the magical keyword 🤗😍], change the APIMTokenIssuer
class representation to the MyAPIMTokenIssuer
class by editing the <IdentityOAuthTokenGenerator>
property in the <APIM>/repository/conf/identity/identity.xml
.
<IdentityOAuthTokenGenerator>com.sample.token.MyAPIMTokenIssuer</IdentityOAuthTokenGenerator>
Let’s do a test-run with our piece of code…
Keep Stacking!!!
Fire up the WSO2 API Manager server with the artifacts and the above-mentioned configuration changes.
Execute the following CURL command to generate an Opaque Access Token and let’s verify whether it got generated with the specified Dev-Hash value or not…
curl --location --request POST 'https://localhost:8243/token' \--header 'Content-Type: application/x-www-form-urlencoded' \--header 'Authorization: Basic {Base64<ClientID>:<ClientSecret>}' \--data-urlencode 'grant_type=password' \--data-urlencode 'username=admin' \--data-urlencode 'password=admin' \--data-urlencode 'scope=default' \--data-urlencode 'devhash=af1c4ca13ab7d6c8d2a887d7ce8250a2'
And the response should be as follows with the Dev-Hash value appended to the Access Token
{"access_token": "25b9ded7-7441-3b69-bb6b-b1f1828bfff9af1c4ca13ab7d6c8d2a887d7ce8250a2","refresh_token": "d86ac9b8-a3aa-3664-9d39-090ca49a9435","scope": "default","token_type": "Bearer","expires_in": 3600}
A simple custom APIMTokenIssuer Implementation