Feb 2, 2014

Exchanging SAML2 token to OAuth2 token in WSO2 Platform

This is something I came-across while I was on my first QSP. There can be situations where you need to exchange SAML2 to OAuth2 token. In our case, we authenticate users using SAML2 and then authorize APIs on behalf of the user using OAuth2. In this blog post, I'll be walking through how this type scenario can be handled using WSO2 products [1]. In fact, I'll be using WSO2 Identity Server, WSO2 ESB and WSO2 API Manger. Firstly, lets start with the deployment diagram. So, that everyone can get a grasp on how these components are connected to each other and the order of the communication.

As depicted in the image. User get authenticated with SAML2 token and then Service Provider exchanges it to OAuth2 token using the API manager. Now let's see how to configure these components to archive this [2].


STEP 1 - Configure Identity Server (IS 4.6.0)

Register the Service Provider as in the image for authenticating using SAML2. For more information refer link [3]. For Assertion Consumer URL enter http://localhost:8080/travelocity.com/samlsso-home.jsp and be cautious not to forget to select wso2carbon for Certificate Alias. 


STEP 2 - Configure API Manager (APIM 1.6.0)

In this example, we will be configuring all the products in one machine. Therefore, Let's in crease port offset by 2. In order to do that open <APIM_HOME>/repository/conf/carbon.xml and set the port set as below. Now start the APIM.

<!-- 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>2</Offset>

After that in APIM go to configur and click on Trusted Identity Providers. There fill the fields as in the below image.



For the Identity Provider Public Certificate there are two important things to do. First, we need to generate a certificate and then we need to add that certificate to JAVA trusted certificates. So, please issue following commands accordingly. 

  • keytool -export -alias wso2carbon -keystore <IS_HOME>/repository/resources/security/wso2carbon.jks -storepass wso2carbon -file mycert.pem
  • keytool -import -trustcacerts -file <IS_HOME>/repository/resources/security/mycert.pem -alias wso2carbon -keystore $JAVA_HOME/jre/lib/security/cacerts

STEP 3 - Modifying Service Provider  (travelocity.com)

In travelocity.com when you fist get authenticated with SAML2 you get the SAML2 token. Therefore, Using that SAML2 token you are going to get the OAuth2 token. So, that is what done in the below code snippet.

Exchanging SAML2 token to OAuth2 token


 // Get the SAML2 Assertion part from the response
StringWriter rspWrt = new StringWriter();
XMLHelper.writeNode(samlResponse.getAssertions().get(0).getDOM(), rspWrt);
String requestMessage = rspWrt.toString();

// Get the Base64 encoded string of the message
// Then Get it prepared to send it over HTTP protocol
String encodedRequestMessage = Base64.encodeBytes(requestMessage.getBytes(), Base64.DONT_BREAK_LINES);
String saml2assertion = URLEncoder.encode(encodedRequestMessage,"UTF-8").trim();

String urlParameters = "grant_type=urn:ietf:params:oauth:grant-type:saml2-bearer&assertion=" + saml2assertion + "&scope=PRODUCTION";

//Create connection to the Token endpoint of API manger
url = new URL("https://localhost:9445/oauth2/token");

connection = (HttpURLConnection)url.openConnection();
connection.setRequestMethod("POST");
connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8");
// Set the consumer-key and Consumer-secret
connection.setRequestProperty ("Authorization", "Basic " + Base64.encodeBytes(("0P6YbqXQHwS38rTJ5wIzzrIUgNga:HosDgUAhLrgoZh2Ts_L2nrzf4V0a").getBytes(), Base64.DONT_BREAK_LINES));
connection.setUseCaches(false);
connection.setDoInput(true);
connection.setDoOutput(true);

//Send request
DataOutputStream wr = new DataOutputStream (connection.getOutputStream());
wr.writeBytes (urlParameters);
wr.flush ();
wr.close ();

//Get Response
InputStream is = connection.getInputStream();
BufferedReader rd = new BufferedReader(new InputStreamReader(is));

String line;
StringBuffer response = new StringBuffer();
while((line = rd.readLine()) != null) {
   response.append(line);
   response.append('\r');
}

rd.close();
return response.toString();

As you may have already noticed you need a consumer key and a consumer secret in order to get the OAuth2 token. So this consumer key and consumer secret are retrieved when you get subscribed to a particular API available in the APIM store.
Following is an sample SAML2 Assertion which was taken from SAML2 token. As mentioned in the code snippet, you only need this part to get OAuthe2 token.


<saml2:Assertion xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion" ID="lkombgamkgmffhiaphjlipbgdmlnigdgbgmhidpi" IssueInstant="2014-01-16T15:20:09.230Z" Version="2.0">
   <saml2:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity">https://localhost:9443/samlsso</saml2:Issuer>
   <ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
      <ds:SignedInfo>
         <ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" />
         <ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1" />
         <ds:Reference URI="#lkombgamkgmffhiaphjlipbgdmlnigdgbgmhidpi">
            <ds:Transforms>
               <ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" />
               <ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" />
            </ds:Transforms>
            <ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />
            <ds:DigestValue>CaY1tbi2kfzCqnJARZBs9I6C690=</ds:DigestValue>
         </ds:Reference>
      </ds:SignedInfo>
      <ds:SignatureValue>dOExwKi/lAW7nzb2JCyLJCAppI9sgb0qZDayQcNeiSqv3gjRmsOcfxYyeVZhUaqHuOpqCqWwLQDQ
i4BUINMdlBsw8y2iZH7bhcfUgDIj26PNBlFtZthmX3ERr4leCm0NIo0jt+cVry3BSEO7duamNq3J
ZPIultt6SZWTsfk4nn8=</ds:SignatureValue>
      <ds:KeyInfo>
         <ds:X509Data>
            <ds:X509Certificate>MIICNTCCAZ6gAwIBAgIE...<removed for bravity>...O4d1DeGHT/YnIjs9JogRKv4XHECwLtIVdAbIdWHEtVZJyMSktcyysFcvuhPQK8Qc/E/Wq8uHSCo=</ds:X509Certificate>
         </ds:X509Data>
      </ds:KeyInfo>
   </ds:Signature>
   <saml2:Subject>
      <saml2:NameID Format="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress">admin</saml2:NameID>
      <saml2:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
         <saml2:SubjectConfirmationData InResponseTo="0" NotOnOrAfter="2014-01-16T15:25:09.230Z" Recipient="http://localhost:8080/travelocity.com/samlsso-home.jsp" />
      </saml2:SubjectConfirmation>
   </saml2:Subject>
   <saml2:Conditions NotBefore="2014-01-16T15:20:09.230Z" NotOnOrAfter="2014-01-16T15:25:09.230Z">
      <saml2:AudienceRestriction>
         <saml2:Audience>travelocity.com</saml2:Audience>
         <saml2:Audience>https://localhost:9445/oauth2/token</saml2:Audience>
      </saml2:AudienceRestriction>
   </saml2:Conditions>
   <saml2:AuthnStatement AuthnInstant="2014-01-16T15:20:09.230Z" SessionIndex="28350436-898a-42c4-975f-e8b5aba01d9a">
      <saml2:AuthnContext>
         <saml2:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:Password</saml2:AuthnContextClassRef>
      </saml2:AuthnContext>
   </saml2:AuthnStatement>
</saml2:Assertion>

Making the Service Call

Now you have everything that you need for the legitimate service call. All you have to do is use the retrieved OAuth token to make service call. Following code snippet shows how it is done.

// Create the connection to desired API
url = new URL("http://localhost:8282/datadelete/1.0.0");
            
connection = (HttpURLConnection)url.openConnection();
connection.setRequestMethod("POST");
connection.setRequestProperty("Content-Type", "application/json");
// Using the OAuth token
connection.setRequestProperty ("Authorization", "Bearer " + request.getSession().getAttribute("access_token"));
connection.setUseCaches(false);
connection.setDoInput(true);
connection.setDoOutput(true);

//Send request with the required payload
DataOutputStream wr = new DataOutputStream (connection.getOutputStream());
wr.writeBytes ("{\"Request\":{\"DeviceID\":\""+ request.getParameter("device") +"\"}}");
wr.flush ();
wr.close ();

//Get Response
InputStream is = connection.getInputStream();
BufferedReader rd = new BufferedReader(new InputStreamReader(is));

String line;
while((line = rd.readLine()) != null) {
    rsp.append(line);
    rsp.append('\r');
}
rd.close();

return rsp.toString();

So that is it. That's how you can exchange SAML2 token to OAuth2 token in WSO2 platform. Anyways, you might be wondering why there is an ESB. The actually backend service is hosted in ESB and then exposes using API manager.

You can also exchange the SAML2 token to OAuth2 token just using the IS instead of APIM [4].

See Also


[1] http://docs.wso2.org/dashboard.action
[2] http://docs.wso2.org/display/AM160/Token+API
[3] http://docs.wso2.org/display/IS460/Configuring+SAML2+SSO
[4] http://docs.wso2.org/display/IS450/SAML2+Bearer+Assertion+Profile+for+OAuth+2.0



5 comments :

  1. Do you have a code for this one? Your code have some missing parts!

    ReplyDelete
    Replies
    1. Sorry for the delayed response. I was traveling. I have modified the code to have the missing parts:). its not possible to attach the complete code as its not mine :)

      Delete
  2. The command should have to add the java certificate store password like this. Default one is changeit :) keytool -import -alias wso2carbon -file wso2carbon.pem -keystore /home/andunslg/My_Works/jdk1.7.0_51/jre/lib/security/cacerts -storepass changeit

    ReplyDelete
  3. This article was a big help! But had to do several modification in using the code for change. Thus thought to share it here.

    http://pastebin.com/2CuQJDun

    ReplyDelete

    Blogger news

    Blogger templates

    Blogroll

    About