Friday, August 14, 2015

Accessing a single WSO2 Identity Server instance with different domain names

A quick note on the $subject :)

Usecase:

FooSP calls IS as proxy1.com

GET http://localhost:8080/travelocity.com/index.jsp
GET https://proxy1.com/samlsso
GET https://proxy1.com/commonauth
GET https://proxy1.com/authenticationendpoint/login.do

BarSP calls IS as proxy2.com

GET http://localhost:8080/travelocity.com/index.jsp
GET https://proxy2.com/samlsso
GET https://proxy2.com/commonauth
GET https://proxy2.com/authenticationendpoint/login.do

This could be achieved simply by using two Virtual hosts and reverse proxies.

A simple ApacheDS config that would do this is:

<IfModule mod_proxy.c>
<VirtualHost *:443>
 ServerAdmin techops@wso2.com
 ServerName proxy1.com
 ServerAlias proxy1.com

 ProxyRequests Off

 SSLEngine On
 SSLProxyEngine On
 SSLCertificateFile /etc/apache2/credential/server.crt
 SSLCertificateKeyFile /etc/apache2/credential/server.key
 SSLCACertificateFile /etc/apache2/credential/ca.crt

 ProxyPass / https://localhost:9443/
 ProxyPassReverse / https://localhost:9443/

</VirtualHost>

<VirtualHost *:443>
 ServerAdmin techops@wso2.com
 ServerName proxy2.com
 ServerAlias proxy2.com

 ProxyRequests Off

 SSLEngine On
 SSLProxyEngine On
 SSLCertificateFile /etc/apache2/credential/server.crt
 SSLCertificateKeyFile /etc/apache2/credential/server.key
 SSLCACertificateFile /etc/apache2/credential/ca.crt

 ProxyPass / https://localhost:9443/
 ProxyPassReverse / https://localhost:9443/

</VirtualHost>
</ifModule>

Please note that the above config doesn't contain load balancing.

Problems in allowing access using different domain names are:
  • SAML SSO destination validation will fail if request signing validation is enabled. Because, IS compares the destination with the value given in <IdentityProviderURL> in identity.xml. There's no way to define multiple values.
  • If federates to outside, redirect URLs will contain the same response URL. We can't have "https://proxy1.com/commonauth" and "https://proxy2.com/commonauth". Response URL is built using the carbon.xml's defined hostname.
  • If someone wants to access the Management Console also by using different domains, there's no way to display different URLs in the Resident IdP UI.

Monday, July 14, 2014

[Dissecting SAML Spec] Validation of Assertion Consumer Service URL

Assertion Consumer Service URL is the endpoint at Service Provider side to which the SAML Assertions will be sent by the SAML IdP. When sending a SAML Authentication Request, the SP can specify the ACS URL that he prefers.

So, how should a SAML IdP treat/validate the ACS URL that is coming inside a AuthnRequest from an SP? Let's check what SAML specs have to say...

<samlp:AuthnRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
                    AssertionConsumerServiceURL="http://localhost:8080/travelocity.com/home.jsp"
                    AttributeConsumingServiceIndex="1731527999"
                    Destination="https://localhost:9443/samlsso"
                    ForceAuthn="false"
                    ID="fjirjgorgoirjderoeijogreiure"
                    IsPassive="false"
                    IssueInstant="2014-07-25T05:47:20.273Z"
                    ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
                    Version="2.0"
                    >
        <samlp:Issuer xmlns:samlp="urn:oasis:names:tc:SAML:2.0:assertion">travelocity.com</samlp:Issuer>
        <saml2p:NameIDPolicy xmlns:saml2p="urn:oasis:names:tc:SAML:2.0:protocol"
                             AllowCreate="true"
                             Format="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent"
                             SPNameQualifier="Issuer"
                             />
        <saml2p:RequestedAuthnContext xmlns:saml2p="urn:oasis:names:tc:SAML:2.0:protocol"
                                      Comparison="exact"
                                      >
                <saml:AuthnContextClassRef xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport</saml:AuthnContextClassRef>
        </saml2p:RequestedAuthnContext>
</samlp:AuthnRequest>

According to SAMLMeta spec section 2.4.4, it is mandatory that at least one ACS URL of a SP is registered at the IdP prior to communication between the parties:
<AssertionConsumerService> [One or More]
One or more elements that describe indexed endpoints that support the profiles of the Authentication Request protocol defined in [SAMLProf]. All service providers support at least one such endpoint, by definition."
So, does that mean SP should only send that registered ACS URL in the request? SAMLProf has this to say in section 4.1.3.5:
The location of the assertion consumer service MAY be determined using metadata (as in [SAMLMeta]). The identity provider MUST have some means to establish that this location is in fact controlled by the service provider. A service provider MAY indicate the SAML binding and the specific assertion consumer service to use in its <AuthnRequest> and the identity provider MUST honor them if it can.
Section 4.1.4.1 also states:
Note that if the <AuthnRequest> is not authenticated and/or integrity protected, the information in it MUST NOT be trusted except as advisory. Whether the request is signed or not, the identity provider MUST ensure that any <AssertionConsumerServiceUrl> or <AssertionConsumerServiceIndex> elements in the request are verified as belonging to the service provider to whom the response will be sent. Failure to do so can result in a man-in-the-middle attack.
So we can come to the following conclusions:

1. If no AssertionConsumerServiceUrl is given in the <AuthnRequest> IdP must send the response to one of the registered ACS URLs of the SP.

2. If the AssertionConsumerServiceUrl in <AuthnRequest> matches with one of the registered URLs, send the response to it.

3. If the AssertionConsumerServiceUrl in <AuthnRequest> does not match with any of the registered ACS URLs and if the request is signed (which removes the possibility of man-in-the-middle attack), then send the response to the ACS URL in the request only if the signature is valid.

4. Else, reject the <AuthnRequest>

Customizing the login page for different SAML SSO Service Providers in WSO2 Identity Server 5.0

Usecase

Usually WSO2 Identity Server displays a default login page for all the SAML SSO Service Providers (SP) that are sending authentication requests to it. However, there might be a need to display different login pages for each SP.

This post explains how it can be done using IS 5.0.0 [1]

Please refer [2] for how to achieve this in IS 4.5.0 and 4.6.0.

Configuring 2 SPs - Travelocity.com & Avis.com

1.  Copy travelocity.com.war [3] and avis.com.war [4] to your application server (I used Tomcat 7)

2. Start the application server and access following URLs to make sure both apps are running:

Travelocity.com = http://localhost:8080/travelocity.com/index.jsp


Avis.com = http://localhost:8080/avis.com/index.jsp




Registering the 2 SPs at IS

1. Download WSO2 Identity Server and extract it (wso2is-5.0.0.zip).

2. Run the server by executing wso2is-5.0.0/bin/wso2carbon.sh if on a Unix based systems, or /bin/wso2carbon.bat if on Windows.

3. On the home page, under Identity -> Service Providers, click 'Add'. Then give "travelocity.com" as the 'Service Provider Name' and press 'Register'.



4. In the next page click 'Configure' under 'SAML2 Web SSO Configuration' in Inbound Authentication Configuration.


5. Fill following details on the next page:
  • Issuer: travelocity.com 
  • Assertion Consumer URL: http://localhost:8080/travelocity.com/samlsso-home.jsp
  • Select Enable Response Signing 
  • Select Enable Assertion Signing 
  • Select Enable Single Logout
6. Similarly, register Avis.com as a new Service Provider:
  • Issuer: avis.com 
  • Assertion Consumer URL: http://localhost:8080/avis.com/samlsso-home.jsp
  • Select Enable Response Signing 
  • Select Enable Assertion Signing  
  • Select Enable Single Logout
Now if you try to "login with SAML from WSO2 Identity Server" in Travelocity.com and Avis.com, you would get landed in the following default page.



Configuring the login pages at IS

The default login page you saw earlier is located at:

wso2is-5.0.0/repository/deployment/server/webapps/authenticationendpoint/login.jsp

A quick look at authenticationendpoint web application...

The login page that gets displayed during SAMLSSO, OAuth, OpenID and Passive-STS flows are located inside the webapp named authenticationendpoint.

The rational behind having it in a web app is:
  • to easily customize the page according to user requirements
  • if needed, place that whole web application in an external application server.
So how does IS know where this web application is located? It is pointed in the following configuration file:

wso2is-5.0.0/repository/conf/security/application-authentication.xml

<AuthenticationEndpointURL>/authenticationendpoint/login.do</AuthenticationEndpointURL>

By default it points to a location inside IS itself, thus the relative path is given. If it is needed to point to an external application, the full path should be given instead.

A very important note, if this web app is moved outside, we must ensure that no one can take a peek at the login credentials getting passed between this app and IS. Which means, that external location should be either inside a secured Intranet or transport should be HTTPS or take any other precaution needed to secure the communication.

Following is the structure of this web app...


In the default web application, when a request comes to the login page, it's first served by AuthenticationEndpoint servlet. After checking this is a SAMLSSO related request it's forwarded to SAMLSSOLogin servlet which finally forwards to the login.jsp. If you look inside the web.xml you would see how these are mapped.

The beauty is, all of these are customizable...the servlets...pages and everything!

You can get the source for authenticationendpoint webapp from [5]

The only restriction is to submit to IS what is already sent back by the pages inside the default web app. And of cause to point to the correct location via application-authentication.xml

Now to customize the pages..

There can be many ways to do this as you might have discerned from the overview of authenticationendpoint web app. Following are two such methods, first one: easy and quick, second one: not so easy and involves some code compiling, but neater.

Some important points first:

When a request comes to the said default login page, if you check the address bar you would notice several parameters are getting passed. Out of them, for this customization we are going to focus on the following two:

sessionDataKey : This is an identifier used by IS to maintain state information related to this particular request by the SP.

relyingParty : This is the value we gave for the "Issuer" field when we registered the SAML SSO SP (e.g. travelocity.com). This value will be used to display different login pages to different SPs.

Also, make sure following are applied when customizing the pages:

1. Form submissions should happen to "commonauth" servlet as a POST.
<form id="form" name="form" action="../../commonauth" method="POST">
2. Make sure to send back the "sessionDataKey" with the form submission, by using a hidden input field:
<input type="hidden" name="sessionDataKey" value="<%=request.getParameter("sessionDataKey")%>"/>

With that background let's dive into steps of the two methods:

Method 1: Using a JSP for redirecting to SP relevant pages

1. Rename the existing 'login.jsp' to 'default_login.jsp' 

2. Create a new file with the name 'login.jsp' including the following code:

<%  
String relyingParty = request.getParameter("relyingParty");

if (relyingParty.equals("travelocity.com")) {
 RequestDispatcher dispatcher = request.getRequestDispatcher("travelocity_login.jsp");
 dispatcher.forward(request, response);
} else {
 RequestDispatcher dispatcher = request.getRequestDispatcher("default_login.jsp");
 dispatcher.forward(request, response);
} 
  %>

What this basically does is forward to different login pages by checking the value of relyingParty parameter.

3. Get the 'travelocity_login.jsp' from [6] and place it at the same level as 'login.jsp'. Also, download the contents of 'css' and 'images' folders from that same link and put them inside the respective folders in the authenticationendpoint.

4. Now, try to login into Travelocity.com web app again. You would be presented with a different page this time!


If you try to access Avis.com, it will present the default login page of IS.

Method 2: Using a Servlet for redirecting to SP relevant pages

1. Check out the source code of authenticationendpoint web app from the svn location given above.

2. Modify the existing org.wso2.carbon.identity.application.authentication.endpoint.samlsso.SAMLSSOLogin.java located at src/main/java/org/wso2/carbon/identity/application/authentication/endpoint/samlsso/ as below:

public class SAMLSSOLogin extends HttpServlet {

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException,
            IOException {
     
        if(request.getRequestURI().contains("/samlsso_login.do")){
         String relyingParty = request.getParameter("relyingParty");
         
         if (relyingParty != null && !relyingParty.isEmpty()) {
          String rpPage = getServletConfig().getInitParameter(relyingParty);
          
          if (rpPage != null) {
           request.getRequestDispatcher(rpPage).forward(request, response);
           return;
          }
         }
            request.getRequestDispatcher("login.jsp").forward(request, response);
        } else if (request.getRequestURI().contains("/samlsso_redirect.do")){
            request.getRequestDispatcher("samlsso_redirect.jsp").forward(request, response);
        }  else if (request.getRequestURI().contains("/samlsso_notification.do")){
            request.getRequestDispatcher("samlsso_notification.jsp").forward(request, response);
        }
    }
}

3. Build the source and replace the existing authenticationendpoint.war at  wso2is-5.0.0/repository/deployment/server/webapps/ with the new war file. Also, delete the existing expanded authenticationendpoint folder at the same location.

4. Start the server.
 
5. Add init parameters to "SAML2SSO" servlet in web.xml, located in the expanded web app:
<servlet>
        <servlet-name>SAML2SSO</servlet-name>
        <servlet-class>org.wso2.carbon.identity.application.authentication.endpoint.samlsso.SAMLSSOLogin</servlet-class>
        
        <init-param> 
            <param-name>travelocity.com</param-name> 
            <param-value>travelocity_login.jsp</param-value> 
        </init-param> 
        
    </servlet>
You can add any new SP page like that. Just give the issuer as the "param-name" and the customized page location as the "param-value".

6. Do step 3 above Method 1 to get the customized pages and css files.

7. Try to access the Travelocity.com. You will be presented with the customized page!

Ref:
[1] http://wso2.com/products/identity-server/
[2] http://dulanja.blogspot.com/2014/01/wso2-is-samlsso-customizing-login-page.html
[3] https://svn.wso2.org/repos/wso2/people/dulanja/samples/customize_loginpage/travelocity.com.war
[4] https://svn.wso2.org/repos/wso2/people/dulanja/samples/customize_loginpage/avis.com.war
[5] https://svn.wso2.org/repos/wso2/carbon/platform/branches/turing/components/identity/org.wso2.carbon.identity.application.authentication.endpoint/4.2.2/  
[6] https://svn.wso2.org/repos/wso2/people/dulanja/samples/customize_loginpage/is-500/

Friday, January 17, 2014

[WSO2-IS SAMLSSO] Customizing the login page for different Service Providers

Usecase

Usually WSO2 Identity Server displays a default login page for all the Service Providers (SP) that are sending authentication requests to it. However, there might be a need to display different login pages for each SP.

This post explains how it can be done using IS 4.6.0 [1] But this will work for IS 4.5.0 as well.

Configuring 2 SPs - Travelocity.com & Avis.com

1.  Copy travelocity.com.war [2] and avis.com.war [3] to your application server (I used Tomcat 7)

These war files were built using the sample webapp source at [4]

2. Start the application server and access following URLs to make sure both apps are running:

Travelocity.com = http://localhost:8080/travelocity.com/index.jsp


Avis.com = http://localhost:8080/avis.com/index.jsp




Registering the 2 SPs at IS

1. Download WSO2 Identity Server and extract it (wso2is-4.6.0.zip).

2. Run the server by executing wso2is-4.6.0/bin/wso2carbon.sh if on a Unix based systems, or /bin/wso2carbon.bat if on Windows.

3. On the home page, under Manage section, click "SAML SSO" and then click "Register New Service Provider".

4. Fill following details on the registration page to register Travelocity.com:

  • Issuer: travelocity.com 
  • Assertion Consumer URL: http://localhost:8080/travelocity.com/samlsso-home.jsp
  • Select Enable Response Signing 
  • Select Enable Assertion Signing 
  • Select Enable Single Logout

5.Similarly, register Avis.com:
  • Issuer: avis.com 
  • Assertion Consumer URL: http://localhost:8080/avis.com/samlsso-home.jsp
  • Select Enable Response Signing 
  • Select Enable Assertion Signing  
  • Select Enable Single Logout

Now if you try to "login with SAML from WSO2 Identity Server" in Travelocity.com and Avis.com, you would get landed in the following default page.


Configuring the login pages at IS

The default login page you saw earlier is located at:

wso2is-4.6.0/repository/deployment/server/webapps/authenticationendpoint/samlsso/samlsso_login.jsp

A quick look at authenticationendpoint web application...

All the login pages of SAMLSSO, OAuth, OpenID and Passive-STS are located inside the webapp named authenticationendpoint. The rational behind this is to:
  • easily customize those pages according to user requirements
  • if needed, place that whole web application or part of it (e.g. only the SAML related stuff) in an external application server.
So how does the IS know where this web application is located? It is pointed in the following configuration file:

wso2is-4.6.0/repository/conf/security/application-authenticators.xml

<Authenticators>
    <Authenticator name="BasicAuthenticator" disabled="false" factor="1">
        <Status value="10" loginPage="/authenticationendpoint/login.do" />
    </Authenticator>
</Authenticators>

If someone wants to point to an external location then s/he should change the URL of the 'loginPage' attribute. By default it points to the location inside IS itself.

A very important note, if this web app is moved outside, we must ensure that no one can take peek at the login credentials and etc getting passed between this app and IS. Which means, that external location should be either inside a secured Intranet or transport should be HTTPS or take any other precaution needed to secure the communication.

Following is the structure of this web app. Since this blog post is on customizing the SAML SSO related pages, I have expanded the items related to that.


In the default web application, when a request comes to the login page it's first served by AuthenticationEndpoint servlet. After checking this is a SAMLSSO related request it's forwarded to SAMLSSOLogin servlet which finally forwards to the samlsso_login.jsp.  If you look inside the web.xml you would see how these are mapped.

The beauty is all of these are customizable...the servlets...pages and everything!

You can get the source for IS 4.6.0 from:

https://svn.wso2.org/repos/wso2/carbon/platform/branches/turing/components/identity/org.wso2.carbon.identity.application.authentication.endpoint/4.2.1

...and for IS 4.5.0 from:

https://svn.wso2.org/repos/wso2/carbon/platform/branches/turing/components/identity/org.wso2.carbon.identity.application.authentication.endpoint/4.2.0

The only requirement is to submit to IS what is already sent back by the pages inside the default web app. And of cause to point to the correct location via application-authenticators.xml

Now to customize the pages..

There can be many ways to do this as you might have discerned from the overview of authenticationendpoint web app. Following are two such methods, first one: easy and quick, second one: not so easy and involves some code compiling, but neater.

Some important points first:

When a request comes to the said default login page, if you check the address bar you would notice several parameters are getting passed. Out of them, for this customization we are going to focus on two:

sessionDataKey : This is an identifier used by IS to maintain state information related to this particular request by the SP.

issuer : This is the value we gave for the "Issuer" field when we registered the SP (e.g. travelocity.com). This value will be used to display different login pages to different SPs.

Also, make sure following are applied when customizing the pages:

1. Form submissions should happen to "commonauth" servlet as a POST.
<form id="form" name="form" action="../../commonauth" method="POST">
2. Make sure to send back the "sessionDataKey" with the form submission, by using a hidden input field:
<input type="hidden" name="sessionDataKey" value="<%=request.getParameter("sessionDataKey")%>"/>

With that background let's dive into steps of the two methods:

Method 1: Using a JSP for redirecting to SP relevant pages

1. Rename the existing 'samlsso_login.jsp' to 'default_login.jsp' 

2. Create a new file with the name 'samlsso_login.jsp' including the following code:

<%  
String issuer = request.getParameter("issuer");

if (issuer.equals("travelocity.com")) {
 RequestDispatcher dispatcher = request.getRequestDispatcher("travelocity_login.jsp");
 dispatcher.forward(request, response);
} else if (issuer.equals("avis.com")) {
 RequestDispatcher dispatcher = request.getRequestDispatcher("avis_login.jsp");
 dispatcher.forward(request, response);
} else {
 RequestDispatcher dispatcher = request.getRequestDispatcher("default_login.jsp");
 dispatcher.forward(request, response);
}
 %>

What this basically does is forward to different login pages by checking the value of issuer parameter.

3. Get the 'travelocity_login.jsp' [5] and 'avis_login.jsp' [6] and place them at the same level as 'samlsso_login.jsp' and 'default_login.jsp'

travelocity_login.jsp

<!doctype html>
<html lang="en-US">
<head>
 <meta charset="utf-8">
 <title>Login for Travelocity.com</title>
 <link rel="stylesheet" href="http://fonts.googleapis.com/css?family=Varela+Round">
 <link rel="stylesheet" href="samlsso/css/travelocity.css">
</head>
<body>
 <div id="login">
  <h2><span class="fontawesome-lock"></span>Sign In</h2>
  <form action="../../commonauth" method="POST">
   <fieldset>
    <p><label for="username">Username</label></p>
    <p><input type="username" id="username" name="username"></p>
    <p><label for="password">Password</label></p>
    <p><input type="password" id="password" name="password"></p>
    <input type="hidden" name="sessionDataKey" value="<%=request.getParameter("sessionDataKey")%>"/>
    <p><input type="submit" value="Sign In"></p>
   </fieldset>
  </form>
 </div>
</body> 
</html>

avis_login.jsp
<!doctype html>
<html lang="en-US">
<head>
 <meta charset="utf-8">
 <title>Login for Avis.com</title>
 <link rel="stylesheet" href="samlsso/css/avis.css">
</head>

<body>
 <form id="form" name="form" action="../../commonauth" method="POST"> 
  <div id="block"> 
   <label id="user" for="name">p</label>
   <input type="text" name="username" id="name" placeholder="Username" required/>
   <label id="pass" for="password">k</label>
   <input type="password" name="password" id="password" placeholder="Password" required /> 
   <input type="hidden" name="sessionDataKey" value="<%=request.getParameter("sessionDataKey")%>"/>
   <input type="submit" id="submit" name="submit" value="a"/>
  </div>
 </form>
 <div id="option"> 
  <p>Sign in</p> 
 </div>
</body>

4. Get 'travelocity.css' [7] and 'avis.css' [8] and place them in the 'css' folder. The two new pages will refer them for styling.

5. Now, try to login into Travelocity.com web app again. You would be presented with an all new login page!


Try Avis.com as well. You will now see a totally different page!


I have used free templates available at http://designify.me/web-design/10-beautiful-free-css-login-forms/ to create these two pages.

If any other SP tries to access, it will be presented with the default login page of IS.

Method 2: Using a Servlet for redirecting to SP relevant pages

1. Check out the source code of authenticationendpoint web app from the svn location given above.

2. Modify the existing org.wso2.carbon.identity.application.authentication.endpoint.samlsso.SAMLSSOLogin.java located at src/main/java/org/wso2/carbon/identity/application/authentication/endpoint/samlsso/ as below:

public class SAMLSSOLogin extends HttpServlet {

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException,
            IOException {
     
        if(request.getRequestURI().contains("/samlsso_login.do")){
         String issuer = request.getParameter("issuer");
         
         if (issuer != null && !issuer.isEmpty()) {
          String issuerPage = getServletConfig().getInitParameter(issuer);
          
          if (issuerPage != null) {
           request.getRequestDispatcher(issuerPage).forward(request, response);
           return;
          }
         }
            request.getRequestDispatcher("samlsso/samlsso_login.jsp").forward(request, response);
        } else if (request.getRequestURI().contains("/samlsso_redirect.do")){
            request.getRequestDispatcher("samlsso/samlsso_redirect.jsp").forward(request, response);
        }  else if (request.getRequestURI().contains("/samlsso_notification.do")){
            request.getRequestDispatcher("samlsso/samlsso_notification.jsp").forward(request, response);
        }
    }
}

3. Build the source and replace the existing authenticationendpoint.war at  wso2is-4.6.0/repository/deployment/server/webapps/ with the new war file. Also, delete the existing expanded authenticationendpoint folder at the same location.

4. Start the server.
 
5. Add init parameters to "SAML2SSO" servlet in web.xml, located in the expanded web app:
<servlet>
        <servlet-name>SAML2SSO</servlet-name>
        <servlet-class>org.wso2.carbon.identity.application.authentication.endpoint.samlsso.SAMLSSOLogin</servlet-class>
        
        <init-param> 
            <param-name>travelocity.com</param-name> 
            <param-value>samlsso/travelocity_login.jsp</param-value> 
        </init-param> 
        
        <init-param> 
            <param-name>avis.com</param-name> 
            <param-value>samlsso/avis_login.jsp</param-value> 
        </init-param> 
    </servlet>
You can add any new SP page like that. Just give the issuer as the "param-name" and the customized page location as the "param-value".

6. Do steps 3 and 4 of above Method 1 to get the customized pages and css files.

7. Try to access the SP. You will be presented with the customized page!

Ref:
[1] http://wso2.com/products/identity-server/
[2] https://svn.wso2.org/repos/wso2/people/dulanja/samples/customize_loginpage/travelocity.com.war
[3] https://svn.wso2.org/repos/wso2/people/dulanja/samples/customize_loginpage/avis.com.war
[4] https://svn.wso2.org/repos/wso2/carbon/platform/branches/turing/products/is/4.6.0/modules/samples/sso/SSOAgentSample
[5] https://svn.wso2.org/repos/wso2/people/dulanja/samples/customize_loginpage/travelocity_login.jsp
[6] https://svn.wso2.org/repos/wso2/people/dulanja/samples/customize_loginpage/avis_login.jsp
[7] https://svn.wso2.org/repos/wso2/people/dulanja/samples/customize_loginpage/travelocity.css
[8] https://svn.wso2.org/repos/wso2/people/dulanja/samples/customize_loginpage/avis.css
[9] https://svn.wso2.org/repos/wso2/carbon/platform/branches/turing/components/identity/org.wso2.carbon.identity.application.authentication.endpoint/4.2.1

Wednesday, December 18, 2013

[Code Snippet] Calling a REST operation protected with OAuth via HTTPClient


private void callRest(HttpServletRequest request, HttpServletResponse response) throws IOException {
 
 PostMethod post = new PostMethod("http://localhost:9764/jaxrs_basic/services/customers/customerservice/customers/name");
 
 post.addRequestHeader("Accept", "text/plain");
 post.addRequestHeader("Authorization", "Bearer " + oauthToken);
 
 try {
  RequestEntity myEntity = new StringRequestEntity("", "text/plain", "ISO-8859-1");
  post.setRequestEntity(myEntity);
  
  HttpClient httpclient = new HttpClient();
  int result = httpclient.executeMethod(post);
  
  System.out.println("Response status code: " + result);
  System.out.println("Response body: ");
  System.out.println(post.getResponseBodyAsString());
  
 } catch (Exception e) {
  e.printStackTrace();
 } finally {
  post.releaseConnection();
 }
}

[Code Snippet] Exchange SAML2 Token to an OAuth Token via HTTPClient & parse JWT using Gson


...

import org.apache.commons.httpclient.Credentials;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.UsernamePasswordCredentials;
import org.apache.commons.httpclient.auth.AuthScope;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.methods.RequestEntity;
import org.apache.commons.httpclient.methods.StringRequestEntity;

import com.google.gson.Gson;

...

private String exchangeSAMLTokenToOAuthToken(HttpServletRequest request, HttpServletResponse response) throws IOException{
  
 String clientId = properties.getProperty("OAuth.client.id");
 String clientSecret = properties.getProperty("OAuth.client.secret");
 String oauthTokenEndpoint = properties.getProperty("OAuth.token.endpoint");

 HttpClient httpclient = new HttpClient();
 httpclient.getParams().setAuthenticationPreemptive(true);
 Credentials defaultcreds = new UsernamePasswordCredentials(clientId, clientSecret);
 httpclient.getState().setCredentials(AuthScope.ANY, defaultcreds);

 PostMethod post = new PostMethod(oauthTokenEndpoint);
 post.addRequestHeader("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8");
 post.addParameter("grant_type", "urn:ietf:params:oauth:grant-type:saml2-bearer");
 post.addParameter("assertion", samlAssertion);
  
 try {
  httpclient.executeMethod(post);
  System.out.println("Response from OAuth Token endpoint: ");
  System.out.println(post.getResponseBodyAsString());
  Map jsonJavaRootObject = new Gson().fromJson(post.getResponseBodyAsString(), Map.class);
  return (String)jsonJavaRootObject.get("access_token");
 } catch (Exception e) {
  e.printStackTrace();
 } finally {
  post.releaseConnection();
 }

 return null;
}

Friday, December 13, 2013

Enabling SAML SSO for web apps hosted in WSO2 Application Server via a Tomcat Valve

Usecase

When a user tries to access a web application hosted on WSO2 Application Server (AS) he should be authenticated by WSO2 Identity Server via SAML SSO, and also his roles should be sent back in the SAML Response. Then the web application should be able to get the authentication and authorization details from the request's UserPrincipal object (i.e. principal name and check isUserInRole).

Later, when the user tries to access another web app on the same AS server, he should be auto logged in without presenting a login page.

I have created a sample to demonstrate this usecase. We can set it up and execute by following the below steps.

Configuring the Application Server side

1. Download WSO2 Application Server 4.5.0 [1] and extract it (wso2as-5.2.0.zip).

2. Copy org.wso2.carbon.sample.tomcat.valve.samlsso-1.0.0.jar [bin][src] to wso2as-5.2.0/repository/components/lib

3. Copy org.wso2.carbon.identity.sso.agent-1.0.0.jar [bin][src] to wso2as-5.2.0/repository/components/lib

4. Modify wso2as-5.2.0/repository/conf/tomcat/catalina-server.xml by adding following under "Host" section:
<Valve className="org.wso2.carbon.sample.tomcat.valve.samlsso.SAMLSSOValve"/>

5.  Copy foo-app.war [bin][src] and bar-app.war [bin][src] to wso2as-5.2.0/repository/deployment/server/webapps.

6. Open them in an archive manager and change the "saml2.config.file.path"  context param in /WEB-INF/web.xml to point to the location of the properties file that contains the configurations. By default this file is located in /WEB-INF/classes directory.

7. Also, in those same property files, change the value: KeyStore=[path to]/wso2as-5.2.0/repository/resources/security/wso2carbon.jks

8. Change the port "offset" to "1" in wso2as-5.2.0/repository/conf/carbon.xml.

9. Run the server by executing wso2as-5.2.0/bin/wso2carbon.sh if on a Unix based systems, or /bin/wso2carbon.bat if on Windows.

Configuring the Identity Server

1. Download WSO2 Identity Server 4.5.0 [2] and extract it (wso2is-4.5.0.zip).

2.  Run the server by executing wso2is-4.5.0/bin/wso2carbon.sh if on a Unix based systems, or /bin/wso2carbon.bat if on Windows.

3. On the home page, under Manage section, click "SAML SSO" and then click "Register New Service Provider".

4. Fill following details on the registration page:

* Issuer: foo-app
* Assertion Consumer URL: https://localhost:9444/foo-app/acs
* Select Enable Response Signing
* Select Enable Assertion Signing 
* Select Enable Single Logout
* Select Enable Attribute Profile 
* Add claim: "http://wso2.org/claims/role".
* Select Include Attributes in the Response Always

Finally click "Register".



5. bar-app should be also registered in the same manner. Just replace "bar" with "foo" in the above configurations.



Let's test this...

1. Access foo-app deployed in the AS via the web browser: https://localhost:9444/foo-app/

2. It gets redirected to IS login page. Give "admin":"admin" (username:password) and click "Sign In"


3. Browser gets redirected back to the web app; we have successfully authenticated via SAML SSO.


4. Now try to access https://localhost:9444/bar-app/. Though it gets redirected to IS, login page is not displayed since IS already has a session regarding this user.

User gets successfully authenticated to the bar-app also..

Ref:
[1] http://wso2.com/products/application-server/
[2] http://wso2.com/products/identity-server/