Friday, April 5, 2013

Apply OAuth2.0 base security for Rest endpoint with WSO2ESB 4.6.0 and WSO2IS 4.1.1 alpha

I think this would be good example for applying the security for the simple rest endpoint. Lets think we already have some rest endpoint without security but we need to expose this with the OAuth2.0 base security.

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.

Functional Scenario

Rest endpoint with security

1. Creating custom handler (Download the mvn project here)
You need to extends AbstractHandler and implements ManagedLifecycle as follows. as well I'm getting some parameters from the axis2.xml
[sourcecode language="java"]
package org.wso2.handler;

/**
* Created with IntelliJ IDEA.
* User: dinuka
* Date: 4/4/13
* Time: 3:46 PM
* To change this template use File | Settings | File Templates.
*/
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.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 java.util.Map;

public class SimpleOauthHandler extends AbstractHandler implements ManagedLifecycle {

private String securityHeader = HttpHeaders.AUTHORIZATION;
private String consumerKeyHeaderSegment = "Bearer";
private String oauthHeaderSplitter = ",";
private String consumerKeySegmentDelimiter = " ";
private String oauth2TokenValidationService = "oauth2TokenValidationService";
private String identityServerUserName = "identityServerUserName";
private String identityServerPw = "identityServerPw";

@Override
public boolean handleRequest(MessageContext messageContext) {
try{
ConfigurationContext configCtx = ConfigurationContextFactory.createConfigurationContextFromFileSystem(null, null);
//Read parameters from axis2.xml
String identityServerUrl = messageContext.getConfiguration().getAxisConfiguration().getParameter(oauth2TokenValidationService).getValue().toString();
String username = messageContext.getConfiguration().getAxisConfiguration().getParameter(identityServerUserName).getValue().toString();
String password = messageContext.getConfiguration().getAxisConfiguration().getParameter(identityServerPw).getValue().toString();

OAuth2TokenValidationServiceStub stub = new OAuth2TokenValidationServiceStub(configCtx,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 = new OAuth2TokenValidationRequestDTO();
dto.setTokenType("bearer");
Map headers = (Map) ((Axis2MessageContext) messageContext).getAxis2MessageContext().
getProperty(org.apache.axis2.context.MessageContext.TRANSPORT_HEADERS);
String apiKey = null;
if (headers != null) {
apiKey = extractCustomerKeyFromAuthHeader(headers);
}
dto.setAccessToken(apiKey);
//validate passed apiKey(token)
if(stub.validate(dto).getValid()){
return true;
}else{
return false;
}
}catch(Exception e){
e.printStackTrace();
return false;
}
}

public 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(securityHeader);
if (authHeader == null) {
return null;
}

if (authHeader.startsWith("OAuth ") || authHeader.startsWith("oauth ")) {
authHeader = authHeader.substring(authHeader.indexOf("o"));
}

String[] headers = authHeader.split(oauthHeaderSplitter);
if (headers != null) {
for (int i = 0; i < headers.length; i++) {
String[] elements = headers[i].split(consumerKeySegmentDelimiter);
if (elements != null && elements.length > 1) {
int j = 0;
boolean isConsumerKeyHeaderAvailable = false;
for (String element : elements) {
if (!"".equals(element.trim())) {
if (consumerKeyHeaderSegment.equals(elements[j].trim())) {
isConsumerKeyHeaderAvailable = true;
} else if (isConsumerKeyHeaderAvailable) {
return removeLeadingAndTrailing(elements[j].trim());
}
}
j++;
}
}
}
}
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) {
//To change body of implemented methods use File | Settings | File Templates.
}

@Override
public void destroy() {
//To change body of implemented methods use File | Settings | File Templates.
}
}
[/sourcecode]

2. Now I take rest unsecured endpoint as "https://www.google.lk/search?q=wso2"(You can use your own endpoint)
Lets look at how to configure the WSO2 ESB with this endpoint.

Start the ESB 4.6.0 and Sign in as admin.
Then go to Source View.
esb

Insert following xml configuration in to the source view to create the API element name as TestGoogle
[sourcecode language="xml"]
<api name="TestGoogle" context="/search">
<resource methods="GET">
<inSequence>
<log level="custom">
<property name="Test" value="Test"/>
</log>
<send>
<endpoint>
<address uri="https://www.google.lk/search?q=wso2"/>
</endpoint>
</send>
</inSequence>
</resource>
<handlers>
<handler class="org.wso2.handler.SimpleOauthHandler"/>
</handlers>
</api>
[/sourcecode]

esb2

3. We need to add the created custom handler.jar in to $ESB_HOME/repository/components/libs and go to the $ESB_HOME/repository/conf/axis2/axis2.xml and put the following parameters.
[sourcecode language="xml"]
<!-- 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>
[/sourcecode]

restart the ESB.

Again go to source view and place the following xml to engage the custom handler in to the API element
[sourcecode language="xml"]
<handlers>
<handler class="org.wso2.handler.SimpleOauthHandler"/>
</handlers>
[sourcecode]

Total configuration looks like this
[sourcecode language="xml"]
<api name="TestGoogle" context="/search">
<resource methods="GET">
<inSequence>
<log level="custom">
<property name="Test" value="Test"/>
</log>
<send>
<endpoint>
<address uri="https://www.google.lk/search?q=wso2"/>
</endpoint>
</send>
</inSequence>
</resource>
<handlers>
<handler class="org.wso2.handler.SimpleOauthHandler"/>
</handlers>
</api>
[/sourcecode]

4. Start the WSO2 Identity server and create the Oauth2.0 Application

is

Request the access token from IS you need to pass the ClientID and Client Secret with the curl request.
[sourcecode language="console"]
curl -v -X POST --user <strong>R2CNjiq672f6xXQabAfWbYby2nca</strong>:<strong>QhEQi9eJv8BmSinPBnWscCFFDgsa</strong> -H "Content-Type: application/x-www-form-urlencoded;charset=UTF-8" -k -d "grant_type=password&username=admin&password=admin" https://localhost:9444/oauth2endpoints/token
[/sourcecode]

Then you will receive the access token
cmd

5. Now you can invoke the API with the received access token

curl -v -X GET -H "Authorization: Bearer ca1799fc84986bd87c120ba499838a7" http://10.100.1.198:8280/search
cmd

1 comment:

  1. I downloaded the maven project, but can't find all the dependencies. Are there any special repositories (from WSO2?) that need to be specified? Also, I'll go to the WSO2 docs for the info, but at least a note on how to deploy the handler after it is build would be nice. But very helpful and well written, thanks.

    ReplyDelete