When you deploy an API into WSO2 ESB, there might be cases where you want to secure it. If you use WSO2 API Manager to front the API s deployed at ESB, you can go ahead with the security provided by API Manager.
If you do not use WSO2 API Manager to front the APIs, at ESB level we can secure them.
You can achieve this task with the following steps.
1. Create the custom handler to validate the Bearer token.
2. Create API element in the ESB and pointing the rest endpoint that you have
3. Include created handler to the created API element.
4. Go to IS and create the OAuth2.0 application and get the Access token form IS
5. Invoke the API with the valid access token.
Following diagram depicts the high level message flow.
Let's look at how this can be done step by step.
You need to extends AbstractHandler and implements ManagedLifecycle as follows. Here I’m reading some parameters from the axis2.xml as well.
You can make this a maven project and bundle into a .jar file (see pom.xml for dependancies).
Here oauth2TokenValidationService is a service running in Identity Server. Now let us look at how to create this service inside WSO2 Identity Server.
Download WSO2 IS server from here and extract it to a preferred location. If you are going to run ESB and Identity servers on the same machine, we need to offset ports used by Identity server. To do that go to [IS_HOME]/repository/conf/carbon.xml file and edit it specifying a port offset of 1.
Note that this is why we defined 9444 as the port under axis2.xml
Start Identity Server by [IS_HOME]/bin sh wso2server.sh command and point the browser to the Management Console (https://localhost:9444/carbon). Log in using admin/admin default credentials.
Under "Service Providers" click on add.
Now, we need to create an API in ESB. Before that we need some service to call by that API. We can use JAX-RS web service hosted here. Build the .war file and deploy it into WSO2 Application Server or to Apache Tomcat to host it.
We can invoke and test the service using following GET url (assume WSO2 AS is started with port offset =2)
Create an API in ESB with following content.
Now we can test this API using following curl command
Update the API pointing to the handler as below
1. First get the access token. Use generated client key and secret by Identity Server above.
curl -v -k -X POST --user ksf_LQIQu4Zh3tNX0f_1CH9FwBAa:oi1o71BwefGSbIM8cw2dld3WVVIa -H "Content-Type: application/x-www-form-urlencoded;charset=UTF-8" -d 'grant_type=password&username=admin&password=admin' https://localhost:9444/oauth2/token
2. Invoke the created API using the access token granted.
If you do not use WSO2 API Manager to front the APIs, at ESB level we can secure them.
You can achieve this task with the following steps.
1. Create the custom handler to validate the Bearer token.
2. Create API element in the ESB and pointing the rest endpoint that you have
3. Include created handler to the created API element.
4. Go to IS and create the OAuth2.0 application and get the Access token form IS
5. Invoke the API with the valid access token.
Following diagram depicts the high level message flow.
Let's look at how this can be done step by step.
Creating a Custom Handler
You can download the project from hereYou need to extends AbstractHandler and implements ManagedLifecycle as follows. Here I’m reading some parameters from the axis2.xml as well.
package org.wso2.handler; import org.apache.axis2.AxisFault; import org.apache.axis2.client.Options; import org.apache.axis2.client.ServiceClient; import org.apache.axis2.context.ConfigurationContext; import org.apache.axis2.context.ConfigurationContextFactory; import org.apache.axis2.transport.http.HTTPConstants; import org.apache.axis2.transport.http.HttpTransportProperties; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.http.HttpHeaders; import org.apache.synapse.core.axis2.Axis2MessageContext; import org.wso2.carbon.identity.oauth2.stub.OAuth2TokenValidationServiceStub; import org.wso2.carbon.identity.oauth2.stub.dto.OAuth2TokenValidationRequestDTO; import org.apache.synapse.ManagedLifecycle; import org.apache.synapse.MessageContext; import org.apache.synapse.core.SynapseEnvironment; import org.apache.synapse.rest.AbstractHandler; import org.wso2.carbon.identity.oauth2.stub.dto.OAuth2TokenValidationRequestDTO_OAuth2AccessToken; import java.util.Map; public class SimpleOAuthHandler extends AbstractHandler implements ManagedLifecycle { private static final String CONSUMER_KEY_HEADER = "Bearer"; private static final String OAUTH_HEADER_SPLITTER = ","; private static final String CONSUMER_KEY_SEGMENT_DELIMITER = " "; private static final String OAUTH_TOKEN_VALIDATOR_SERVICE = "oauth2TokenValidationService"; private static final String IDP_LOGIN_USERNAME = "identityServerUserName"; private static final String IDP_LOGIN_PASSWORD = "identityServerPw"; private ConfigurationContext configContext; private static final Log log = LogFactory.getLog(SimpleOAuthHandler.class); @Override public boolean handleRequest(MessageContext msgCtx) { if (this.getConfigContext() == null) { log.error("Configuration Context is null"); return false; } try{ //Read parameters from axis2.xml String identityServerUrl = msgCtx.getConfiguration().getAxisConfiguration().getParameter( OAUTH_TOKEN_VALIDATOR_SERVICE).getValue().toString(); String username = msgCtx.getConfiguration().getAxisConfiguration().getParameter( IDP_LOGIN_USERNAME).getValue().toString(); String password = msgCtx.getConfiguration().getAxisConfiguration().getParameter( IDP_LOGIN_PASSWORD).getValue().toString(); OAuth2TokenValidationServiceStub stub = new OAuth2TokenValidationServiceStub(this.getConfigContext(), identityServerUrl); ServiceClient client = stub._getServiceClient(); Options options = client.getOptions(); HttpTransportProperties.Authenticator authenticator = new HttpTransportProperties.Authenticator(); authenticator.setUsername(username); authenticator.setPassword(password); authenticator.setPreemptiveAuthentication(true); options.setProperty(HTTPConstants.AUTHENTICATE, authenticator); client.setOptions(options); OAuth2TokenValidationRequestDTO dto = this.createOAuthValidatorDTO(msgCtx); return stub.validate(dto).getValid(); }catch(Exception e){ log.error("Error occurred while processing the message", e); return false; } } private OAuth2TokenValidationRequestDTO createOAuthValidatorDTO(MessageContext msgCtx) { OAuth2TokenValidationRequestDTO dto = new OAuth2TokenValidationRequestDTO(); Map headers = (Map) ((Axis2MessageContext) msgCtx).getAxis2MessageContext(). getProperty(org.apache.axis2.context.MessageContext.TRANSPORT_HEADERS); String apiKey = null; if (headers != null) { apiKey = extractCustomerKeyFromAuthHeader(headers); } OAuth2TokenValidationRequestDTO_OAuth2AccessToken token = new OAuth2TokenValidationRequestDTO_OAuth2AccessToken(); token.setTokenType("bearer"); token.setIdentifier(apiKey); dto.setAccessToken(token); return dto; } private String extractCustomerKeyFromAuthHeader(Map headersMap) { //From 1.0.7 version of this component onwards remove the OAuth authorization header from // the message is configurable. So we dont need to remove headers at this point. String authHeader = (String) headersMap.get(HttpHeaders.AUTHORIZATION); if (authHeader == null) { return null; } if (authHeader.startsWith("OAuth ") || authHeader.startsWith("oauth ")) { authHeader = authHeader.substring(authHeader.indexOf("o")); } String[] headers = authHeader.split(OAUTH_HEADER_SPLITTER); if (headers != null) { for (String header : headers) { String[] elements = header.split(CONSUMER_KEY_SEGMENT_DELIMITER); if (elements != null && elements.length > 1) { boolean isConsumerKeyHeaderAvailable = false; for (String element : elements) { if (!"".equals(element.trim())) { if (CONSUMER_KEY_HEADER.equals(element.trim())) { isConsumerKeyHeaderAvailable = true; } else if (isConsumerKeyHeaderAvailable) { return removeLeadingAndTrailing(element.trim()); } } } } } } return null; } private String removeLeadingAndTrailing(String base) { String result = base; if (base.startsWith("\"") || base.endsWith("\"")) { result = base.replace("\"", ""); } return result.trim(); } @Override public boolean handleResponse(MessageContext messageContext) { return true; } @Override public void init(SynapseEnvironment synapseEnvironment) { try { this.configContext = ConfigurationContextFactory.createConfigurationContextFromFileSystem(null, null); } catch (AxisFault axisFault) { log.error("Error occurred while initializing Configuration Context", axisFault); } } @Override public void destroy() { this.configContext = null; } private ConfigurationContext getConfigContext() { return configContext; } }
You can make this a maven project and bundle into a .jar file (see pom.xml for dependancies).
Deploying the handler into ESB
To deploy the built handler into ESB, copy the above jar file into [ESB_HOME]/repository/components/lib directory.
Edit Axis2.xml file to read properties
We are reading 3 configurations from axis2.xml file.
- Server url of oauth2TokenValidationService in WSO2 Identity Server. Format is [host Name of Identity Server] : [https port]/services/OAuth2TokenValidationService
- Username to use when connecting to IS.
- Password to use when connecting to IS.
Navigate to [ESB_HOME]/repository/conf/axis2/axis2.xml and place following at the top.
<!-- OAuth2 Token Validation Service --> <parameter name="oauth2TokenValidationService">https://localhost:9444/services/OAuth2TokenValidationService</parameter> <!-- Server credentials --> <parameter name="identityServerUserName">admin</parameter> <parameter name="identityServerPw">admin</parameter>
Here oauth2TokenValidationService is a service running in Identity Server. Now let us look at how to create this service inside WSO2 Identity Server.
Preparing WSO2 Identity Server
Download WSO2 IS server from here and extract it to a preferred location. If you are going to run ESB and Identity servers on the same machine, we need to offset ports used by Identity server. To do that go to [IS_HOME]/repository/conf/carbon.xml file and edit it specifying a port offset of 1.
<Ports> <!-- Ports offset. This entry will set the value of the ports defined below to the define value + Offset. e.g. Offset=2 and HTTPS port=9443 will set the effective HTTPS port to 9445 --> <Offset>1</Offset>
Note that this is why we defined 9444 as the port under axis2.xml
Start Identity Server by [IS_HOME]/bin sh wso2server.sh command and point the browser to the Management Console (https://localhost:9444/carbon). Log in using admin/admin default credentials.
Under "Service Providers" click on add.
Provide a name and click on register. Now, under "Inbound Authentication Configuration" section add "OAuth/OpenID Connect Configuration" . Identity Server will generate a OAuth Client Key and a secret.
Defining an API in ESB and securing
Deploy a JAX-RS web service
Now, we need to create an API in ESB. Before that we need some service to call by that API. We can use JAX-RS web service hosted here. Build the .war file and deploy it into WSO2 Application Server or to Apache Tomcat to host it.
This service is used to receive guest details by a guest name.
Test the deployed service
We can invoke and test the service using following GET url (assume WSO2 AS is started with port offset =2)
curl -v -X GET http://127.0.0.1:9765/GuestDetailsService_1.0.0/services/guest_details_service/
guestDetailService/guestInfo/tom
guestDetailService/guestInfo/tom
Make an API using above service
Create an API in ESB with following content.
<api xmlns="http://ws.apache.org/ns/synapse" name="guestDetailNew" context="/guestDetailNew"> <resource methods="GET" uri-template="/{id}"> <inSequence> <property name="NO_ENTITY_BODY" scope="axis2" action="remove"></property> <log> <property name="STATUS" value="GUEST_DETAIL_NEW_API_HIT"></property> </log> <send> <endpoint> <http method="get" uri-template="http://127.0.0.1:9765/GuestDetailsService_1.0.0/services/guest_details_service/guestDetailService/guestInfo/{uri.var.id}"></http> </endpoint> </send> </inSequence> <outSequence> <send></send> </outSequence> </resource> </api>
Now we can test this API using following curl command
curl -v -X GET http://127.0.0.1:8280/guestDetailNew/tom
Securing the API
Update the API pointing to the handler as below
<api xmlns="http://ws.apache.org/ns/synapse" name="guestDetailNew" context="/guestDetailNew"> <resource methods="GET" uri-template="/{id}"> <inSequence> <property name="NO_ENTITY_BODY" scope="axis2" action="remove"></property> <log> <property name="STATUS" value="GUEST_DETAIL_NEW_API_HIT"></property> </log> <send> <endpoint> <http method="get" uri-template="http://127.0.0.1:9765/GuestDetailsService_1.0.0/services/guest_details_service/guestDetailService/guestInfo/{uri.var.id}"></http> </endpoint> </send> </inSequence> <outSequence> <send></send> </outSequence> </resource> <handlers> <handler class="org.wso2.handler.SimpleOAuthHandler"/> </handlers> </api>
Testing the Secured API
1. First get the access token. Use generated client key and secret by Identity Server above.
curl -v -k -X POST --user ksf_LQIQu4Zh3tNX0f_1CH9FwBAa:oi1o71BwefGSbIM8cw2dld3WVVIa -H "Content-Type: application/x-www-form-urlencoded;charset=UTF-8" -d 'grant_type=password&username=admin&password=admin' https://localhost:9444/oauth2/token
2. Invoke the created API using the access token granted.
curl -v -X GET -H "Authorization: Bearer 51c7b6dc3e98d7ce979e2f30a80ba" http://localhost:8280/guestDetailNew/tom
Please note following:
- You can use refresh token to renew the token. Token time out can be configured at Identity Server.
- This is based on official documentation here. Please read it carefully for enhanced use cases.
Hi Hasitha,
ReplyDeleteI am making Secured API using OAuth2.
while hitting API,got exception like Caused by: javax.net.ssl.SSLPeerUnverifiedException: peer not authenticated.
So Can you please help me to fix this issue?
I'm getting the same error :(.
ReplyDelete