Showing posts with label SAML. Show all posts
Showing posts with label SAML. Show all posts

Sunday, June 1, 2014

Getting User Attributes in SAML2 Bearer Assertion Profile for OAuth 2.0 using JWT Token Generation

We can configure WSO2 API Manager to send user attributes to backend API, if the internal users of API Manager consumes the API using obtained OAuth token. That process is described in https://docs.wso2.org/display/AM170/Passing+Enduser+attributes+to+the+Backend+Using+JWT. JSON Web Token(JWT) tokens are used in this.

But when users get OAuth token using SAML2 Bearer Assertion Profile for OAuth 2.0 how can we do that? What we need to do is share the user store with IS as well as API Manger. That kind of a architecture is given below.


JAVA client to SAML2 Bearer Assertion Profile for OAuth 2.0

In SAML2 Bearer Assertion Profile for OAuth 2.0 user can get a SAML token from WSO2 Identity Server by authenticating. After that user can give that SAML token to WSO2 API Manger to get an OAuth token without going for authentication.I am giving you a JAVA client to exchange SAML token to OAuth token.

import org.example.webUI.Config;
import org.opensaml.Configuration;
import org.opensaml.DefaultBootstrap;
import org.opensaml.saml2.core.Response;
import org.opensaml.xml.ConfigurationException;
import org.opensaml.xml.XMLObject;
import org.opensaml.xml.io.Unmarshaller;
import org.opensaml.xml.io.UnmarshallerFactory;
import org.opensaml.xml.io.UnmarshallingException;
import org.opensaml.xml.util.Base64;
import org.opensaml.xml.util.XMLHelper;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xml.sax.SAXException;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLEncoder;
import java.util.HashMap;

public class OAuthUtil {

    public static String exchangeSAMLTokenToOauth(String responseMessage) throws ConfigurationException, ParserConfigurationException, IOException, SAXException, UnmarshallingException {

        DefaultBootstrap.bootstrap();

        byte[] decoded = Base64.decode(responseMessage);

        ByteArrayInputStream is = new ByteArrayInputStream(decoded);

        DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
        documentBuilderFactory.setNamespaceAware(true);
        DocumentBuilder docBuilder = documentBuilderFactory.newDocumentBuilder();

        Document document = docBuilder.parse(is);
        Element element = document.getDocumentElement();

        UnmarshallerFactory unmarshallerFactory = Configuration.getUnmarshallerFactory();
        Unmarshaller unmarshaller = unmarshallerFactory.getUnmarshaller(element);
        XMLObject responseXmlObj = unmarshaller.unmarshall(element);

        Response responseObj = (Response) responseXmlObj;


        // Get the SAML2 Assertion part from the response
        StringWriter rspWrt = new StringWriter();
        XMLHelper.writeNode(responseObj.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 enc_rslt = URLEncoder.encode(encodedRequestMessage, "UTF-8").trim();

        //Create connection to the Token endpoint of API manger
        URL url = new URL(Config.apiMangerOAuthURL);

        HttpURLConnection connection = (HttpURLConnection) url.openConnection();
        connection.setRequestMethod("POST");
        connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");

        String userCredentials = Config.apiMangerClientID+":"+Config.apiMangerClientSecret;
        String basicAuth = "Basic " + new String(Base64.encodeBytes(userCredentials.getBytes()));
        basicAuth = basicAuth.replaceAll("\\r|\\n", "");

        // Set the consumer-key and Consumer-secret
        connection.setRequestProperty("Authorization", basicAuth);
        connection.setUseCaches(false);
        connection.setDoInput(true);
        connection.setDoOutput(true);

        //Send request
        DataOutputStream wr = new DataOutputStream(connection.getOutputStream());
        wr.writeBytes("grant_type=urn:ietf:params:oauth:grant-type:saml2-bearer&assertion=" + enc_rslt + "&scope=PRODUCTION");
        wr.flush();
        wr.close();

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

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

        rd.close();
        return responseString.toString();
    }

    public static boolean revokeToken(Token token) throws IOException {
        //Create connection to the Token endpoint of API manger
        URL url = new URL(Config.apiMangerOAuthRevokeURL);

        HttpURLConnection connection = (HttpURLConnection) url.openConnection();
        connection.setRequestMethod("POST");
        connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8");

        String userCredentials = Config.apiMangerClientID+":"+Config.apiMangerClientSecret;
        String basicAuth = "Basic " + new String(Base64.encodeBytes(userCredentials.getBytes()));
        basicAuth = basicAuth.replaceAll("\\r|\\n", "");

        // Set the consumer-key and Consumer-secret
        connection.setRequestProperty("Authorization", basicAuth);
        connection.setUseCaches(false);
        connection.setDoInput(true);
        connection.setDoOutput(true);

        //Send request
        DataOutputStream wr = new DataOutputStream(connection.getOutputStream());
        wr.writeBytes("token="+token.getAccess_token());
        wr.flush();
        wr.close();

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

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

        rd.close();

        System.out.println("Revoking Token -"+token.getAccess_token());
        System.out.println("Revoking Response -"+responseString.toString());

        return true
                ;
    }
}

The related token class is this,

public class Token {
    private String access_token,refresh_token,expires_in,token_type;

    public Token(String access_token, String refresh_token, String expires_in, String token_type) {
        this.access_token = access_token;
        this.refresh_token = refresh_token;
        this.expires_in = expires_in;
        this.token_type = token_type;
    }

    public String getRefresh_token() {
        return refresh_token;
    }

    public void setRefresh_token(String refresh_token) {
        this.refresh_token = refresh_token;
    }

    public String getAccess_token() {
        return access_token;
    }

    public void setAccess_token(String access_token) {
        this.access_token = access_token;
    }

    public String getExpires_in() {
        return expires_in;
    }

    public void setExpires_in(String expires_in) {
        this.expires_in = expires_in;
    }

    public String getToken_type() {
        return token_type;
    }

    public void setToken_type(String token_type) {
        this.token_type = token_type;
    }
}
Hope this helped you! 

SAML2 Bearer Assertion Profile for OAuth 2.0 with WSO2 Identity Server 5.0.0 and WSO2 API Manger 1.7.0

WSO2 product stack supports SAML2 Bearer Assertion Profile for OAuth 2.0. You can find lot of details about it in https://docs.wso2.org/display/IS460/SAML2+Bearer+Assertion+Profile+for+OAuth+2.0. What happens in here actually is a user can get a SAML token from WSO2 Identity Server by authenticating. After that user can give that SAML token to API Manger to get an OAuth token without going for authentication. To do that WSO2 Identity Server have to be a trusted identity provider for WSO2 API Manager. I will explain how to do it in these products.

You need,
Here a sample web application is using SAML SSO to authenticate it's users using  WSO2 Identity Server  and use SAML2 Bearer Assertion Profile to get OAuth token from WSO2 API Manager. Those tokens are used to query a REST API published in API Manager.

To understand this more I will expalin the flow,
  • When a user access the sample web application using this kind of urls http://example.com/mobile, they will be redirected to WSO2 Identity Server authentication page.
  • When users authenticate them self in the login UI WSO2 Identity Server will redirect the request back to web application with a SAML response like this,
<?xml version="1.0" encoding="UTF-8"?> 
<saml2:Assertion xmlns:saml2="urn:oasis:names: tc:SAML:2.0:assertion" ID="hnpnedgkgdbhinomeabplbjdnhlffjbbckodpkhl" IssueInstant="2014-05-31T15:41:03.665Z" Version="2.0" xmlns:xs="http://www.w3.org/2001/XMLSchema">
    <saml2:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity">localhost</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="#hnpnedgkgdbhinomeabplbjdnhlffjbbckodpkhl">
                <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#">
                        <ec:InclusiveNamespaces xmlns:ec="http://www.w3.org/2001/10/xml-exc-c14n#" PrefixList="xs"/>
                    </ds:Transform>
                </ds:Transforms>
                <ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
                <ds:DigestValue>KaUzRnwIJfhVboyWpcgg555ea0Y=</ds:DigestValue>
            </ds:Reference>
        </ds:SignedInfo>
        <ds:SignatureValue>
            L3jOrttx7+Vb6iBnDV6hu4GRoGDX6k1U2Z+C7XLh7+jgvq53/N7Ro0rY4d4GiJJb5gOcAg3f8qt+gKQbAvcjvbJCRW1x4GFZ0au4iqZUSbHOPYPiIT4mYzxrmZg+cynVN+mrJCEEFdhFaP/3yPITeeapP9Yqp3QvtnbRC6s8ejk=
        </ds:SignatureValue>
        <ds:KeyInfo>
            <ds:X509Data>
                <ds:X509Certificate>
                    MIICNTCCAZ6gAwIBAgIES343gjANBgkqhkiG9w0BAQUFADBVMQswCQYDVQQGEwJVUzELMAkGA1UECAwCQ0ExFjAUBgNVBAcMDU1vdW50YWluIFZpZXcxDTALBg
                </ds:X509Certificate>
            </ds:X509Data>
        </ds:KeyInfo>
    </ds:Signature>
    <saml2:Subject>
        <saml2:NameID Format="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress">demo</saml2:NameID>
        <saml2:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
            <saml2:SubjectConfirmationData InResponseTo="0" NotOnOrAfter="2014-05-31T15:46:03.664Z" Recipient="http://example.com/mobile/mobile.jsp"/>
        </saml2:SubjectConfirmation>
        <saml2:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
            <saml2:SubjectConfirmationData InResponseTo="0" NotOnOrAfter="2014-05-31T15:46:03.664Z" Recipient="https://IP:9448/oauth2/token"/>
        </saml2:SubjectConfirmation>
    </saml2:Subject>
    <saml2:Conditions NotBefore="2014-05-31T15:41:03.665Z" NotOnOrAfter="2014-05-31T15:46:03.664Z"
        <saml2:AudienceRestriction>
            <saml2:Audience>exampleMobile</saml2:Audience>             
	    <saml2:Audience>https://IP:9448/oauth2/token</saml2:Audience>
        </saml2:AudienceRestriction>
    </saml2:Conditions>
    <saml2:AuthnStatement AuthnInstant="2014-05-31T15:41:03.665Z" SessionIndex="57c87332-97f2-4fe1-bb70-326c0e59fd36">
        <saml2:AuthnContext>
            <saml2:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:Password</saml2:AuthnContextClassRef>
        </saml2:AuthnContext>
    </saml2:AuthnStatement>
    <saml2:AttributeStatement>
        <saml2:Attribute Name="http://wso2.org/claims/role"
                         NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
            <saml2:AttributeValue xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">company_admin,Internal/everyone</saml2:AttributeValue>
        </saml2:Attribute>
        <saml2:Attribute Name="http://wso2.org/claims/organization" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
            <saml2:AttributeValue xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">5386e408b35805c8649e08b8</saml2:AttributeValue>
        </saml2:Attribute>
    </saml2:AttributeStatement>
</saml2:Assertion> 
  • By giving this to OAuth token endpoint of API Manger we can get a OAuth token like this,
{"token_type":"bearer","expires_in":3600,"refresh_token":"b75d83e892994882abf3c19cea4db4a2","access_token":"51ee8f8f417f944a4d9c787adc6c260"}
To get this done we need to configure like this,

Step 1 - Start the Identity Server and go to Main Tab > Identity > Service Providers >Add

Enter a Service Provider Name and click Register,


Step 2 - In the next UI select Inbound Authentication Configuration > SAML2 Web SSO Configuration >  Configure

Step 3 - Fill the Like below. Assertion consumer url will be the web application url which will IS sends the SAML response. Give the API Manager's OAuth token url as audience restriction as well as recipient restriction. Also check Enable Attribute Profile to get user attributes with SAML response. And click register.


Step 4 - In the next UI select Claim Configuration > Requested Claims > Add Claim URI. Add the user claims which you need to get it with SAML response to the web application.

Step 5 - Click the Update button to save the Service Provider.

Now the SAML SSO configuration from Identity Server side is done. The web application can be configured using the SAML SSO servlet filter given by the Identity Server it self. That will check user's authentication and will redirect to IS and will process responses form IS. You can find more about that by looking at the code given in https://docs.wso2.org/display/IS500/Configuring+SAML2+SSO.

Now we need to configure API Manger side.
 
Step 1 - Start the API Manger and go to Main Tab > Identity > Identity Providers >Add

Give a Identity Provider Name and  You can keep Alias empty. Also you need to upload the public certificate of Identity Server. You can get it by executing following command in WSO2 IS Home/repository/resources/security/. Then public certificate will be saved in wso2pem.pem file.
keytool -export -alias wso2carbon -file wso2pem.pem -keystore wso2carbon.jks
Then click on Federated Authenticators tab.
Step 2 - In that UI click on SAML2 Web SSO Configuration

In that you need to give the Issuer ID of the SAML config in IS side ad Identity Provider Entity Id.

Step 3 - And then click register.

Now you are ready to go. All the configurations which need to do SAML 2 Bearer Assertion Profile is done. You can use these articles to setup a OAuth application in API Manger to consume APIs.
After doing that follow the steps given in Invoking Token API to generate tokens section of this article. https://docs.wso2.org/display/AM170/Token+API#TokenAPI-ExchangingSAML2bearertokenswithOAuth2%28SAMLextensiongranttype%29. By doing that you can get a OAuth token as above.