Pages

Friday, February 27, 2015

How to develop SAML-ECP Custom Client

Pre-Requisite:

1. Installed and Configured Shibboleth IDP 3.0
2. Installed and Configured Shibboleth native SP 2.5.3 with ECP Enabled

Environment:

1. JDK 1.7
2. Eclipse
3. Gradle 1.5
4. Open SAML2.6.4
5. Apache Http Client 4.4
6. SL4J 1.7.10


Developing SAMl-ECP client

The following tasks needs to be performed to build the ECP clients:

1. Sending Request from ECP  to SP 
2. Sending Request from ECP  to IDP
3. Sending Request from ECP  to SP  


1. Sending Request from ECP  to SP

 Accessing the Secure URL through Enhanced Client Proxy(ECP) using the Get Request. In get request you need to add two header fields in the request:

1. Accept: application/vnd.paos+xml
2. PAOS: ver='url:liberty:paos:2003-08';'urn:oasis:names:tc:SAML:2.0:profiles:SSO:ecp'

Please refer example code : EcpRequestInterceptor  for adding the header variable in the request.

 1.1 Send the Get Request Secure URL from ECP to SP with PAOS Header

   The PAOS header variables are added in the EcpRequestInterceptor class.
     
     public Envelope accessProtectedPage()
    {
        Envelope envelope=null;
        HttpGet request= new HttpGet(ecp.getProtectedUrl());
        try
        {
            CloseableHttpResponse httpResponse =  client.execute(request);
            if(httpResponse != null)
            {
                if(httpResponse.getStatusLine().getStatusCode()==200)
                {
                    HttpEntity entity = httpResponse.getEntity();
                    envelope=convertRequestToSoap(entity);
                    if (!envelope.getBody().getUnknownXMLObjects().isEmpty())
                    {
                        if(envelope.getBody().getUnknownXMLObjects(AuthnRequest.DEFAULT_ELEMENT_NAME) != null)
                        {
                            logger.info("Auethentication Access"+convertSoapToString(envelope));  
                        }
                        else
                        {
                            throw new RuntimeException("Invalid ECP Response");
                        }
                    }
                    else
                    {
                        throw new RuntimeException("Invalid ECP Response");
                    }
                    EntityUtils.consume(entity);
                }
                else
                {
                    throw new RuntimeException(httpResponse.getStatusLine().getStatusCode()+" - " +httpResponse.getStatusLine().getReasonPhrase());
                }
            }
        } catch (ClientProtocolException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return envelope;
    }


  1.2 SP will send the SOAP Response to ECP with PAOS Request  in SOAP Headers and Authentication Request Statement in Soap Body.

The Ecp response from the SP is shown below.

<?xml version="1.0" encoding="UTF-8"?>
<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
    <S:Header>
        <paos:Request xmlns:paos="urn:liberty:paos:2003-08" S:actor="http://schemas.xmlsoap.org/soap/actor/next" S:mustUnderstand="1"
        responseConsumerURL="https://shib-sp.example.edu/Shibboleth.sso/SAML2/ECP" service="urn:oasis:names:tc:SAML:2.0:profiles:SSO:ecp"/>
        <ecp:Request xmlns:ecp="urn:oasis:names:tc:SAML:2.0:profiles:SSO:ecp" IsPassive="0" S:actor="http://schemas.xmlsoap.org/soap/actor/next" S:mustUnderstand="1">
        <saml:Issuer xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">https://shib-sp.example.edu/shibboleth</saml:Issuer>
            <samlp:IDPList xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol">
                <samlp:IDPEntry ProviderID="https://shib-idp.example.edu/idp/shibboleth"/>
            </samlp:IDPList>
        </ecp:Request>
        <ecp:RelayState xmlns:ecp="urn:oasis:names:tc:SAML:2.0:profiles:SSO:ecp" S:actor="http://schemas.xmlsoap.org/soap/actor/next"
         S:mustUnderstand="1">ss:mem:ec7e3978ddd1958ca9efb27be7c9445017daf9068b62c647cc398a4dfc73573c</ecp:RelayState>
    </S:Header>
    <S:Body>
        <samlp:AuthnRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
            AssertionConsumerServiceURL="https://shib-sp.example.edu/Shibboleth.sso/SAML2/ECP"
            ID="_c06ac9aa1e8e172b920bd32435547b05"    
            IssueInstant="2015-02-27T00:04:38Z"
            ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:PAOS"
            Version="2.0">
                <saml:Issuer xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">https://shib-sp.example.edu/shibboleth</saml:Issuer>
                <samlp:NameIDPolicy AllowCreate="1"/>
                <samlp:Scoping>
                    <samlp:IDPList>
                        <samlp:IDPEntry ProviderID="https://shib-idp.example.edu/idp/shibboleth"/>
                    </samlp:IDPList>
                </samlp:Scoping>
        </samlp:AuthnRequest>
    </S:Body>
</S:Envelope>
     

2. Sending the Authentication Request from ECP to IDP with PAOS and Authorization Headers

 2.1 Detach the SP Soap Body and Create a new Soap Envelop and set the detached soap body to new envelop

You need to remove the SOAP headers from the SP Initial Secure URL response or You can copy the authentication request from the  SP Initial Secure URL response and build a new Soap Envelope. I have detached (copy) from the initial soap response and building the new soap request and sample code is given below:

public Envelope buildIdpAuthRequest(Envelope envelope)
    {
       
        Envelope newAuthRequest = new EnvelopeBuilder().buildObject();
       
        Body authBody= envelope.getBody();
        authBody.detach();
        newAuthRequest.setBody(authBody);
       
        return newAuthRequest;
       
    }

Sample Soap XML : 

<?xml version="1.0" encoding="UTF-8"?>
<soap11:Envelope xmlns:soap11="http://schemas.xmlsoap.org/soap/envelope/">
    <S:Body xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
        <samlp:AuthnRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
            AssertionConsumerServiceURL="https://shib-sp.example.edu/Shibboleth.sso/SAML2/ECP"
            ID="_b2c92bc8216ff317a5c42d797a7cd8ca" IssueInstant="2015-02-27T18:49:37Z"
            ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:PAOS"
            Version="2.0">
            <saml:Issuer xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">https://shib-sp.example.edu/shibboleth</saml:Issuer>
            <samlp:NameIDPolicy AllowCreate="1"/>
                <samlp:Scoping>
                    <samlp:IDPList><samlp:IDPEntry ProviderID="https://shib-idp.example.edu/idp/shibboleth"/></samlp:IDPList>
                </samlp:Scoping>
        </samlp:AuthnRequest>
    </S:Body>
</soap11:Envelope>

2.2 Send the Authentication Request to IDP with PAOS and Authorization Headers

You need to add the following header variables into the post request because these variables are required  for IDP ECP profile:

1. Accept: application/vnd.paos+xml
2. PAOS: ver='url:liberty:paos:2003-08';'urn:oasis:names:tc:SAML:2.0:profiles:SSO:ecp'
3. Authorization: Basic  + User Name and password encode with base 64.

The sample code is given below:

    public Envelope sendAuthenticationToIdp(String authRequestXml)
    {
        logger.info("Authentication Request from ECP to IDP :" + authRequestXml);
        Envelope envelope=null;
        HttpPost request= new HttpPost(ecp.getIdpUrl());
        request.addHeader(HttpHeaders.AUTHORIZATION, "Basic "+ Base64.encodeBytes((ecp.getUserName()+":"+ecp.getPassword()).getBytes()));
        try
        {
            request.setEntity(new StringEntity(authRequestXml));
        } catch (UnsupportedEncodingException e1) {
            // TODO Auto-generated catch block
            e1.printStackTrace();
        }
        try
        {
            CloseableHttpResponse httpResponse =  client.execute(request);
            if(httpResponse != null)
            {
                if(httpResponse.getStatusLine().getStatusCode()==200)
                {
                    HttpEntity entity = httpResponse.getEntity();
                    envelope=convertRequestToSoap(entity);
                    if (!envelope.getBody().getUnknownXMLObjects().isEmpty())
                    {
                        if(envelope.getBody().getUnknownXMLObjects(Response.DEFAULT_ELEMENT_NAME) != null)
                        {
                            logger.info("Auethentication Access"+convertSoapToString(envelope));
                            org.opensaml.saml2.core.Response samlResp=(org.opensaml.saml2.core.Response)envelope.getBody().getUnknownXMLObjects(org.opensaml.saml2.core.Response.DEFAULT_ELEMENT_NAME).get(0);
                            if(samlResp.getStatus().getStatusCode().getValue().equals(StatusCode.SUCCESS_URI))
                            {
                                System.out.println("Status  :"+samlResp.getStatus().getStatusCode().getValue());   
                            }
                            else
                            {
                               
                                StatusDetail detail= (StatusDetail)samlResp.getStatus().getStatusDetail().getUnknownXMLObjects(StatusDetail.DEFAULT_ELEMENT_NAME).get(0);
                                throw new RuntimeException("SAML Authentication Failed "+samlResp.getStatus().getStatusCode().getValue() + " - ");
                            }
                           
                        }
                        else
                        {
                            throw new RuntimeException("Invalid IDP ECP Response");
                        }
                    }
                    else
                    {
                        throw new RuntimeException("Invalid ECP Response");
                    }
                    EntityUtils.consume(entity);
                }
                else
                {
                    // it will throw 403 error due to invalid password or invalid user name or authentication failed
                    throw new RuntimeException(httpResponse.getStatusLine().getStatusCode()+" - " +httpResponse.getStatusLine().getReasonPhrase());
                }
            }
        } catch (ClientProtocolException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return envelope;
       
    }


2.3 IDP will send the SOAP response to ECP with SAML Response in soap body and ECP response in SOAP Headers.

The sample Authentication Successful response from IDP as follows:
  
<?xml version="1.0" encoding="UTF-8"?>
<soap11:Envelope xmlns:soap11="http://schemas.xmlsoap.org/soap/envelope/">
    <soap11:Header>
        <ecp:Response xmlns:ecp="urn:oasis:names:tc:SAML:2.0:profiles:SSO:ecp"
            AssertionConsumerServiceURL="https://shib-sp.example.edu/Shibboleth.sso/SAML2/ECP"
            soap11:actor="http://schemas.xmlsoap.org/soap/actor/next" soap11:mustUnderstand="1"/>
        <samlec:GeneratedKey xmlns:samlec="urn:ietf:params:xml:ns:samlec"
            soap11:actor="http://schemas.xmlsoap.org/soap/actor/next">Saml keys</samlec:GeneratedKey>
    </soap11:Header>
    <soap11:Body>
        <saml2p:Response xmlns:saml2p="urn:oasis:names:tc:SAML:2.0:protocol"
            Destination="https://shib-sp.example.edu/Shibboleth.sso/SAML2/ECP"
            ID="_c860f132c4115581218971d92a52f500"
            InResponseTo="_b2c92bc8216ff317a5c42d797a7cd8ca"
            IssueInstant="2015-02-27T18:49:37.867Z" Version="2.0">
                <saml2:Issuer xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion">https://shib-idp.example.edu/idp/shibboleth</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/2001/04/xmldsig-more#rsa-sha512"/>
                        <ds:Reference URI="#_c860f132c4115581218971d92a52f500">
                            <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/2001/04/xmlenc#sha512"/>
                            <ds:DigestValue>Digest Base 64 encoded value </ds:DigestValue>
                        </ds:Reference>
                    </ds:SignedInfo>
                    <ds:SignatureValue>Signature Value Base 64 encoded</ds:SignatureValue>
                    <ds:KeyInfo>
                        <ds:X509Data>
                            <ds:X509Certificate>Certificate</ds:X509Certificate>
                        </ds:X509Data>
                    </ds:KeyInfo>
                </ds:Signature>
                <saml2p:Status>
                    <saml2p:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/>
                </saml2p:Status>
                <saml2:EncryptedAssertion xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion">
                    <xenc:EncryptedData xmlns:xenc="http://www.w3.org/2001/04/xmlenc#"
                        Id="_37f9853b6561b3722b3691392ace994b"
                        Type="http://www.w3.org/2001/04/xmlenc#Element">
                            <xenc:EncryptionMethod Algorithm="http://www.w3.org/2009/xmlenc11#aes128-gcm"/>
                            <ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
                                <xenc:EncryptedKey Id="_7dc4db379206ea3e491f5e6e15bbf6fb"
                                    Recipient="https://shib-sp.example.edu/shibboleth">
                                        <xenc:EncryptionMethod Algorithm="http://www.w3.org/2009/xmlenc11#rsa-oaep">
                                            <ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
                                            <xenc11:MGF xmlns:xenc11="http://www.w3.org/2009/xmlenc11#"
                                                Algorithm="http://www.w3.org/2009/xmlenc11#mgf1sha1"/>
                                        </xenc:EncryptionMethod>
                                        <ds:KeyInfo>
                                            <ds:X509Data>
                                                <ds:X509Certificate>Certificate</ds:X509Certificate>
                                            </ds:X509Data>
                                        </ds:KeyInfo>
                                        <xenc:CipherData>
                                            <xenc:CipherValue>Cipher Value</xenc:CipherValue>
                                        </xenc:CipherData>
                                </xenc:EncryptedKey>
                            </ds:KeyInfo>
                            <xenc:CipherData>
                                <xenc:CipherValue>Certificate</xenc:CipherValue>
                            </xenc:CipherData>
                    </xenc:EncryptedData>
                </saml2:EncryptedAssertion>
        </saml2p:Response>
    </soap11:Body>
</soap11:Envelope>

3. Sending the IDP Authentication Response from ECP to SP to access the Secure URL Access. 

3.1 Add Initial Secure URL Access Relay State in IDP Soap Response Headers

The RelayState information is extracted from the initial Sp Secure Access Response from the SP Soap Envelop. This information is required in the soap header because, the SP going to accept the idp authentication SAML response and also make a decision to allow or denied requested secure page.

            RelayState relayState= (RelayState)secureResourceResponse.getHeader().getUnknownXMLObjects(RelayState.DEFAULT_ELEMENT_NAME).get(0);
            relayState.detach();

            Header header = new HeaderBuilder().buildObject();
            header.getUnknownXMLObjects().clear();
            header.getUnknownXMLObjects().add(relayState);
            idpAuthResponse.setHeader(header);



3.2 Extract the AssertionConsumerService URL from the IDP Soap Response

This assertionConsumerUrl parameter extracted from the Soap Header from the IDP Authentication soap response. This assertionConsumerUrl is being used to post the IDP Authentication response with Relay State header from ECP to Service provider post request.

String assertionConsumerUrl=((Response)idpAuthResponse.getHeader().getUnknownXMLObjects(Response.DEFAULT_ELEMENT_NAME).get(0)).getAssertionConsumerServiceURL();

3.3 Send IDP Authentication response to Service Provider with PAOS and Conetent-Type headers.

Sample Code:


    public Envelope sendAuthResponseToSp(String authResponseXml,String consumeServiceUrl)
    {
        Envelope envelope=null;
        HttpPost request= new HttpPost(consumeServiceUrl);
        request.addHeader(HttpHeaders.CONTENT_TYPE, EcpFlowConstants.PAOS_CONTENT_TYPE);
       
        try
        {
            request.setEntity(new StringEntity(authResponseXml));
        } catch (UnsupportedEncodingException e1) {
            // TODO Auto-generated catch block
            e1.printStackTrace();
        }
        try
        {
            CloseableHttpResponse httpResponse =  client.execute(request);
            if(httpResponse != null)
            {
                if(httpResponse.getStatusLine().getStatusCode()==200)
                {
                    HttpEntity entity = httpResponse.getEntity();
                    entity.writeTo(System.out);
                    logger.info("Cookies   "+store.getCookies());
                    EntityUtils.consume(entity);
                   
                   
                }
                else
                {
                    throw new RuntimeException(httpResponse.getStatusLine().getStatusCode()+" - " +httpResponse.getStatusLine().getReasonPhrase());
                }
            }
        } catch (ClientProtocolException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return envelope;
       
    }



Example Code:


I have classified the example code 4 classes. They are

1. ECP Object

   This object is being used to capture the ECP Endpoint IDP Url, Protected Resource from the SP, User and Password to authenticate the IDP Server

2.  EcpRequestInterceptor

    This object is being used to add the ECP headers in each request.

3. EcpClientFlow

    This object being used to send the GET, POST request to IDP and SP using apache HttpClient.

4. EcpClient

This object is being used to Initialize the SAML Boot strap, and Interacting with EcpClientFlow object invoking the various request such as Accessing the Protected Page, Authenticating User from the IDP, and Finally Send the Authenticated IDP Response to Service Provider.


The Sample Code is given below:





package edu.example.ecp.client;

public class Ecp
{
    private String idpUrl;
    private String protectedUrl;
    private String userName;
    private String password;
    public String getIdpUrl() {
        return idpUrl;
    }
    public void setIdpUrl(String idpUrl) {
        this.idpUrl = idpUrl;
    }
    public String getProtectedUrl() {
        return protectedUrl;
    }
    public void setProtectedUrl(String protectedUrl) {
        this.protectedUrl = protectedUrl;
    }
    public String getUserName() {
        return userName;
    }
    public void setUserName(String userName) {
        this.userName = userName;
    }
    public String getPassword() {
        return password;
    }
    public void setPassword(String password) {
        this.password = password;
    }
   
   
}
   


package edu.example.ecp.client;

import java.io.IOException;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.net.ProxySelector;

import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.TransformerFactoryConfigurationError;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.apache.http.HttpEntity;
import org.apache.http.HttpHeaders;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.CookieStore;
import org.apache.http.client.config.CookieSpecs;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.BasicCookieStore;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.client.LaxRedirectStrategy;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.impl.conn.SystemDefaultRoutePlanner;
import org.apache.http.util.EntityUtils;
import org.opensaml.common.SAMLObject;
import org.opensaml.common.xml.SAMLConstants;
import org.opensaml.saml1.core.AuthenticationStatement;
import org.opensaml.saml2.core.Assertion;
import org.opensaml.saml2.core.AuthnRequest;
import org.opensaml.saml2.core.Status;
import org.opensaml.saml2.core.StatusCode;
import org.opensaml.saml2.core.StatusDetail;
import org.opensaml.saml2.ecp.Response;
import org.opensaml.ws.soap.soap11.Body;
import org.opensaml.ws.soap.soap11.Envelope;
import org.opensaml.ws.soap.soap11.impl.EnvelopeBuilder;
import org.opensaml.xml.Configuration;
import org.opensaml.xml.io.Marshaller;
import org.opensaml.xml.io.MarshallingException;
import org.opensaml.xml.io.Unmarshaller;
import org.opensaml.xml.io.UnmarshallingException;
import org.opensaml.xml.parse.BasicParserPool;
import org.opensaml.xml.parse.ParserPool;
import org.opensaml.xml.parse.XMLParserException;
import org.opensaml.xml.util.Base64;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;


package edu.example.ecp.client;

public class EcpClientFlow
{
    private Ecp ecp;
    private BasicParserPool pool;
    private CloseableHttpClient client;
    private CookieStore store;
    private Logger logger= LoggerFactory.getLogger(EcpClientFlow.class);
   
    public EcpClientFlow(Ecp ecp)
    {
        this.ecp=ecp;
       
        //Creating the Parse Object
       
        pool= new BasicParserPool();
        pool.setNamespaceAware(true);
       
        //Creating the Pooled Http Connection Manager
        PoolingHttpClientConnectionManager manager= new PoolingHttpClientConnectionManager();
        manager.setMaxTotal(20);
        manager.setDefaultMaxPerRoute(10);
       
        //Creating the Cookie Store store the Cookies
        store= new BasicCookieStore();
        //Creating the Browser Compatible Request Cookie Object
        RequestConfig config= RequestConfig.custom().setCookieSpec(CookieSpecs.DEFAULT).build();
       
        // Building the Client Builder
       
        HttpClientBuilder clientBuildert= HttpClients.custom().setConnectionManager(manager)
                .setDefaultCookieStore(store)
                // Add the ECP Header every request
                .addInterceptorFirst(new EcpRequestInterceptor())
                // Redirecting the Request
                .setRedirectStrategy(new LaxRedirectStrategy())
                .setDefaultRequestConfig(config);
        client=clientBuildert.setRoutePlanner(new SystemDefaultRoutePlanner(ProxySelector.getDefault())).build();
    }
   
    public Envelope accessProtectedPage()
    {
        Envelope envelope=null;
        HttpGet request= new HttpGet(ecp.getProtectedUrl());
        try
        {
            CloseableHttpResponse httpResponse =  client.execute(request);
            if(httpResponse != null)
            {
                if(httpResponse.getStatusLine().getStatusCode()==200)
                {
                    HttpEntity entity = httpResponse.getEntity();
                    envelope=convertRequestToSoap(entity);
                    if (!envelope.getBody().getUnknownXMLObjects().isEmpty())
                    {
                        if(envelope.getBody().getUnknownXMLObjects(AuthnRequest.DEFAULT_ELEMENT_NAME) != null)
                        {
                            logger.info("Auethentication Access"+convertSoapToString(envelope));   
                        }
                        else
                        {
                            throw new RuntimeException("Invalid ECP Response");
                        }
                    }
                    else
                    {
                        throw new RuntimeException("Invalid ECP Response");
                    }
                    EntityUtils.consume(entity);
                }
                else
                {
                    throw new RuntimeException(httpResponse.getStatusLine().getStatusCode()+" - " +httpResponse.getStatusLine().getReasonPhrase());
                }
            }
        } catch (ClientProtocolException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return envelope;
    }
   
    public Envelope sendAuthenticationToIdp(String authRequestXml)
    {
        logger.info("Authentication Request from ECP to IDP :" + authRequestXml);
        Envelope envelope=null;
        HttpPost request= new HttpPost(ecp.getIdpUrl());
        request.addHeader(HttpHeaders.AUTHORIZATION, "Basic "+ Base64.encodeBytes((ecp.getUserName()+":"+ecp.getPassword()).getBytes()));
        try
        {
            request.setEntity(new StringEntity(authRequestXml));
        } catch (UnsupportedEncodingException e1) {
            // TODO Auto-generated catch block
            e1.printStackTrace();
        }
        try
        {
            CloseableHttpResponse httpResponse =  client.execute(request);
            if(httpResponse != null)
            {
                if(httpResponse.getStatusLine().getStatusCode()==200)
                {
                    HttpEntity entity = httpResponse.getEntity();
                    envelope=convertRequestToSoap(entity);
                    if (!envelope.getBody().getUnknownXMLObjects().isEmpty())
                    {
                        if(envelope.getBody().getUnknownXMLObjects(Response.DEFAULT_ELEMENT_NAME) != null)
                        {
                            logger.info("Auethentication Access"+convertSoapToString(envelope));
                            org.opensaml.saml2.core.Response samlResp=(org.opensaml.saml2.core.Response)envelope.getBody().getUnknownXMLObjects(org.opensaml.saml2.core.Response.DEFAULT_ELEMENT_NAME).get(0);
                            if(samlResp.getStatus().getStatusCode().getValue().equals(StatusCode.SUCCESS_URI))
                            {
                                System.out.println("Status  :"+samlResp.getStatus().getStatusCode().getValue());   
                            }
                            else
                            {
                               
                                StatusDetail detail= (StatusDetail)samlResp.getStatus().getStatusDetail().getUnknownXMLObjects(StatusDetail.DEFAULT_ELEMENT_NAME).get(0);
                                throw new RuntimeException("SAML Authentication Failed "+samlResp.getStatus().getStatusCode().getValue() + " - ");
                            }
                           
                        }
                        else
                        {
                            throw new RuntimeException("Invalid IDP ECP Response");
                        }
                    }
                    else
                    {
                        throw new RuntimeException("Invalid ECP Response");
                    }
                    EntityUtils.consume(entity);
                }
                else
                {
                    // it will throw 403 error due to invalid password or invalid user name or authentication failed
                    throw new RuntimeException(httpResponse.getStatusLine().getStatusCode()+" - " +httpResponse.getStatusLine().getReasonPhrase());
                }
            }
        } catch (ClientProtocolException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return envelope;
       
    }
   
   
    public Envelope sendAuthResponseToSp(String authResponseXml,String consumeServiceUrl)
    {
        Envelope envelope=null;
        HttpPost request= new HttpPost(consumeServiceUrl);
        request.addHeader(HttpHeaders.CONTENT_TYPE, EcpFlowConstants.PAOS_CONTENT_TYPE);
       
        try
        {
            request.setEntity(new StringEntity(authResponseXml));
        } catch (UnsupportedEncodingException e1) {
            // TODO Auto-generated catch block
            e1.printStackTrace();
        }
        try
        {
            CloseableHttpResponse httpResponse =  client.execute(request);
            if(httpResponse != null)
            {
                if(httpResponse.getStatusLine().getStatusCode()==200)
                {
                    HttpEntity entity = httpResponse.getEntity();
                    entity.writeTo(System.out);
                    logger.info("Cookies   "+store.getCookies());
                    EntityUtils.consume(entity);
                   
                   
                }
                else
                {
                    // it will throw 403 error due to invalid password or invalid user name or authentication failed
                    throw new RuntimeException(httpResponse.getStatusLine().getStatusCode()+" - " +httpResponse.getStatusLine().getReasonPhrase());
                }
            }
        } catch (ClientProtocolException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return envelope;
       
    }

   
    public Envelope buildIdpAuthRequest(Envelope envelope)
    {
       
        Envelope newAuthRequest = new EnvelopeBuilder().buildObject();
       
        Body authBody= envelope.getBody();
        authBody.detach();
        newAuthRequest.setBody(authBody);
       
        return newAuthRequest;
       
    }
   
    public Envelope convertRequestToSoap(HttpEntity entity)
    {
        Envelope envlope=null;
        Document soapDoc;
        try
        {
            soapDoc = pool.parse(entity.getContent());
            Unmarshaller unmarshall = Configuration.getUnmarshallerFactory().getUnmarshaller(soapDoc.getDocumentElement());
            envlope=(Envelope)unmarshall.unmarshall(soapDoc.getDocumentElement());
        } catch (IllegalStateException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (XMLParserException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (UnmarshallingException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return envlope;
    }
   

    public String convertSoapToString(Envelope envelope)
    {
        String result=null;
        try
        {
            Marshaller marshaller= Configuration.getMarshallerFactory().getMarshaller(envelope);
            Element element= marshaller.marshall(envelope);
            Transformer transformer;
            transformer = TransformerFactory.newInstance().newTransformer();
            Source source = new DOMSource(element);
            StringWriter writer= new StringWriter();
            StreamResult output = new StreamResult(writer);
            transformer.transform(source, output);
            result=writer.getBuffer().toString();
        } catch (IllegalStateException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (MarshallingException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (TransformerConfigurationException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (TransformerFactoryConfigurationError e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (TransformerException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return result;
    }

}


package edu.example.ecp.client;

import org.opensaml.common.xml.SAMLConstants;

public interface EcpFlowConstants
{
    public String PAOS_CONTENT_TYPE="application/vnd.paos+xml";
    public String PAOS_HEADER=SAMLConstants.PAOS_PREFIX.toUpperCase();
}

   
   
package edu.example.ecp.client;

   
import org.opensaml.DefaultBootstrap;
import org.opensaml.saml2.ecp.RelayState;
import org.opensaml.saml2.ecp.Response;
import org.opensaml.ws.soap.soap11.Envelope;
import org.opensaml.ws.soap.soap11.Header;
import org.opensaml.ws.soap.soap11.impl.HeaderBuilder;
import org.opensaml.xml.ConfigurationException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


public class EcpClient {

    private static Logger logger= LoggerFactory.getLogger(EcpClient.class);

    private static String SECURE_URL="https://shib-sp.example.edu/sample/";
    private static String IDP_URL="https://shib-idp.example.edu/idp/profile/SAML2/SOAP/ECP";
    private static String USER_NAME="<User Name>";
    private static String PASSWORD="<Password>";

    public static void main(String[] args)
    {
        /*
        System.setProperty("org.apache.commons.logging.Log","org.apache.commons.logging.impl.SimpleLog");
        System.setProperty("org.apache.commons.logging.simplelog.showdatetime","true");
        System.setProperty("org.apache.commons.logging.simplelog.log.org.apache.http","DEBUG");
        System.setProperty("org.apache.commons.logging.simplelog.log.org.apache.http.wire","DEBUG");
        System.setProperty("org.apache.commons.logging.de.tudarmstadt.ukp.shibhttpclient","DEBUG");
        */
        Ecp ecp= new Ecp();
        ecp.setProtectedUrl(SECURE_URL);
        ecp.setIdpUrl(IDP_URL);
        ecp.setUserName(USER_NAME);
        ecp.setPassword(PASSWORD);
       
        try
        {
            DefaultBootstrap.bootstrap();
           
            EcpClientFlow clientFlow= new EcpClientFlow(ecp);
            Envelope secureResourceResponse = clientFlow.accessProtectedPage();
            Envelope authRequest= clientFlow.buildIdpAuthRequest(secureResourceResponse);
            Envelope idpAuthResponse = clientFlow.sendAuthenticationToIdp(clientFlow.convertSoapToString(authRequest));
            String assertionConsumerUrl=((Response)idpAuthResponse.getHeader().getUnknownXMLObjects(Response.DEFAULT_ELEMENT_NAME).get(0)).getAssertionConsumerServiceURL();
           
            RelayState relayState= (RelayState)secureResourceResponse.getHeader().getUnknownXMLObjects(RelayState.DEFAULT_ELEMENT_NAME).get(0);
            relayState.detach();
           
            Header header = new HeaderBuilder().buildObject();
            header.getUnknownXMLObjects().clear();
            header.getUnknownXMLObjects().add(relayState);
            idpAuthResponse.setHeader(header);
           
            clientFlow.sendAuthResponseToSp(clientFlow.convertSoapToString(idpAuthResponse), assertionConsumerUrl);
           
        } catch (ConfigurationException e)
        {
            logger.error("Ecp Client Error", e);
        }
       

    }

}

package edu.example.ecp.client;

import java.io.IOException;

import org.apache.http.HttpException;
import org.apache.http.HttpHeaders;
import org.apache.http.HttpRequest;
import org.apache.http.HttpRequestInterceptor;
import org.apache.http.protocol.HttpContext;
import org.opensaml.common.xml.SAMLConstants;


public class EcpRequestInterceptor implements HttpRequestInterceptor,EcpFlowConstants {

    @Override
    public void process(HttpRequest request, HttpContext context)
            throws HttpException, IOException
    {
        request.addHeader(HttpHeaders.ACCEPT, PAOS_CONTENT_TYPE);
        request.addHeader(PAOS_HEADER,"ver="+SAMLConstants.PAOS_NS+";"+SAMLConstants.SAML20ECP_NS);
    }
}


Testing the Code:

 

Create the Source and Resource Directories


mkdir ecp-client/src/main/java -p
mkdir ecp-client/src/main/resources -p

Copy the Java Source files into  ecp-client/src/main/java directory.

Creating Build File



Create a build.gradle File and add the following content in the File and also copy the file to ecp-client directory:


apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'application'

repositories{
    mavenCentral()
}
dependencies{
    compile 'org.opensaml:opensaml:2.6.4','org.apache.httpcomponents:httpclient:4.4',
        'org.slf4j:log4j-over-slf4j:1.7.10','org.slf4j:slf4j-simple:1.7.10',
        'org.slf4j:slf4j-api:1.7.10'
}
mainClassName='edu.example.ecp.client.EcpClient'

Setting JAVA_HOME, GRADLE_HOME and PATH:


export JAVA_HOME=JavaHOME
export GRADLE_HOME=gradle_home
export PATH=$GRADLE_HOME/bin:$PATH


Setting the Classpath

Run the following command to creating the eclipse project and also setting the classpath

 gradle cleanEclipse eclipse


Running the Code

execute the gradle run and it will execute the ecp client code and display the output in the command line.



Common Errors:

Problem 1. Service Provider not sending the SOAP response with ECP Header while accessing the secure access page using the http client.

   Cause:  The following headers are not available in the request:

    
1. Accept: application/vnd.paos+xml
2. PAOS: ver='url:liberty:paos:2003-08';'urn:oasis:names:tc:SAML:2.0:profiles:SSO:ecp'


Solution: Add the above headers in the request and SP will send the SOAP request with ECP Request Headers and also Authentication Request in the Soap Body.


Problem 2. The Identity Provider throwing 500 error while sending the authentication request from ECP to IDP

   Cause:  The following headers are not available in the request:

    
1. Accept: application/vnd.paos+xml
2. PAOS: ver='url:liberty:paos:2003-08';'urn:oasis:names:tc:SAML:2.0:profiles:SSO:ecp'


Solution: Add the above headers in the request and DP will authenticate the user and send the SAML response in the soap body and also ecp response headers.

Problem 3. The Service Provider throwing 500 error while sending the authentication response from ECP to SP

   Cause:  The following headers are not available in the request:

    
1. Accept: application/vnd.paos+xml
2. PAOS: ver='url:liberty:paos:2003-08';'urn:oasis:names:tc:SAML:2.0:profiles:SSO:ecp'
3. Content-Type:  application/vnd.paos+xml


Solution: Add the above headers in the request and SP will accept the SAML response from the IDP issued assertion and It will redirect to requested target page.


Reference:

SAML 2.0 Profiles




 

 

6 comments:

  1. I have read your article, it is really interesting and has lots of useful information. I also recommend it to friends so they can read it and they see the same thing. Thanks for sharing.Adguard Premium Crack



    ReplyDelete
  2. I have read your article. It is very informative and helpful for me. I admire the valuable information you offer in your articles. And I am sharing your post in my friends. Thanks for posting it.
    Iobit Uninstaller Pro Crack

    ReplyDelete
  3. Thanks for the remarkable information. I have never got like this before so please keep updating us like this for good!
    MacKeeper Crack
    KeyShot Pro Crack
    VueScan Crack
    Avast Antivirus Crack
    Smadav Pro Crack
    Minecraft Crack
    ESET Internet Security Crack

    ReplyDelete
  4. This site have particular software articles which emits an impression of being a significant and significant for you individual, able software installation. This is the spot you can get helps for any software installation, usage and cracked.
    https://crackexe.net/https://crackexe.net

    idm crack

    truecaller premium apk crack

    rekordbox dj crack

    autodesk 3ds max crack

    lucky patcher mod apk crack

    drivereasy pro crack

    ReplyDelete