Pushpalanka

About Pushpalanka

Pushpalanka is an undergraduate in Computer Science and Engineering and working on variety of middle-ware solutions. She is an open-source enthusiastic having interests in the fields of Big Data, Distributed Systems and Web Security.

Adding Custom Claims to the SAML Response – (How to Write a Custom Claim Handler for WSO2 Identity Server)

Overview

The latest release of WSO2 Identity Server (version 5.0.0), is armed with an “application authentication framework” which provides lot of flexibility in authenticating users from various service providers who are using heterogeneous protocols. It has several extension points, which can be used to cater several customized requirements commonly found in enterprise systems. With this post, I am going to share the details on making use of one such extension point.
 
 
 

Functionality to be Extended

When SAML Single Sign On is used in enterprise systems it is through the SAML Response that the relying party get to know whether the user is authenticated or not. At this point relying party is not aware of other attributes of the authenticated user which it may need for business and authorization purposes. To provide these attribute details for the relying party, SAML specification has allowed to send attributes as well in the SAML Response. WSO2 Identity Server supports this out of the box via the GUI provided for administrators. You can refer [1] for the details on this functionality and configuration details.

The flexibility provided by this particular extension, comes handy when we have a requirement to add additional attributes to the SAML Response, apart from the attributes available in the underline user store. There may be external data sources we need to look, in order to provide all the attributes requested by the relying parties.

In the sample I am to describe here, we will be looking into a scenario where the system needs to provide some local attributes of the user which are stored in user store, with some additional attributes I expect to be retrieved from an external data source.
Following SAML Response is what we need to send to the relying party from WSO2 IS.

<saml2p:Response Destination="https://localhost:9444/acs" ID="faibaccbcepemkackalbbjkihlegenhhigcdjbjk"
                 InResponseTo="kbedjkocfjdaaadgmjeipbegnclbelfffbpbophe" IssueInstant="2014-07-17T13:15:05.032Z"
                 Version="2.0" xmlns:saml2p="urn:oasis:names:tc:SAML:2.0:protocol"
                 xmlns:xs="http://www.w3.org/2001/XMLSchema">
    <saml2:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity"
                  xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion">localhost
    </saml2:Issuer>
    <ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
        ..........
    </ds:Signature>
    <saml2p:Status>
        <saml2p:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/>
    </saml2p:Status>
    <saml2:Assertion ID="phmbbieedpcfdhcignelnepkemobepgaaipbjjdk" IssueInstant="2014-07-17T13:15:05.032Z" Version="2.0"
                     xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion" 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:Signature>
        <saml2:Subject>
          <saml2:NameID Format="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress">Administrator</saml2:NameID>
            <saml2:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
                <saml2:SubjectConfirmationData InResponseTo="kbedjkocfjdaaadgmjeipbegnclbelfffbpbophe"
                                               NotOnOrAfter="2014-07-17T13:20:05.032Z"
                                               Recipient="https://localhost:9444/acs"/>
            </saml2:SubjectConfirmation>
        </saml2:Subject>
        <saml2:Conditions NotBefore="2014-07-17T13:15:05.032Z" NotOnOrAfter="2014-07-17T13:20:05.032Z">
            <saml2:AudienceRestriction>
                <saml2:Audience>carbonServer2</saml2:Audience>
            </saml2:AudienceRestriction>
        </saml2:Conditions>
        <saml2:AuthnStatement AuthnInstant="2014-07-17T13:15:05.033Z">
            <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">
                    Internal/carbonServer2,Internal/everyone
                </saml2:AttributeValue>
            </saml2:Attribute>
            <saml2:AttributeStatement>
                <saml2:Attribute Name="http://pushpalanka.org/claims/keplerNumber"
                                 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">
                        E90836W19881010
                    </saml2:AttributeValue>
                </saml2:Attribute>
                <saml2:Attribute Name="http://pushpalanka.org/claims/status"
                                 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">
                        active
                    </saml2:AttributeValue>
                </saml2:Attribute>
            </saml2:AttributeStatement>
        </saml2:AttributeStatement>
    </saml2:Assertion>
</saml2p:Response>

In this response we are having one local attribute, which is role and two additional attributes http://pushpalanka.org/claims/keplerNumber and http://pushpalanka.org/claims/status which have been retrieved from some other method we can define in our extension.

How?

  1. Implement the customized logic to get the external claims. There are just two facts we need to note at this effort.
    • The custom implementation should either implement the interface ‘org.wso2.carbon.identity.application.authentication.framework.handler.claims.ClaimHandler’ or extend the default implementation of the interface ‘org.wso2.carbon.identity.application.authentication.framework.handler.claims.impl.DefaultClaimHandler’.
    • The map returned at the method, ‘public Map<String, String> handleClaimMappings’ should contain all the attributes we want to add to the SAML Response.

    Following is the sample code I was written, adhering to the above. The external claims may have been queried from a database, read from a file or using any other mechanism as required.

    public class CustomClaimHandler implements ClaimHandler {
    
        private static Log log = LogFactory.getLog(CustomClaimHandler.class);
        private static volatile CustomClaimHandler instance;
        private String connectionURL = null;
        private String userName = null;
        private String password = null;
        private String jdbcDriver = null;
        private String sql = null;
    
    
        public static CustomClaimHandler getInstance() {
            if (instance == null) {
                synchronized (CustomClaimHandler.class) {
                    if (instance == null) {
                        instance = new CustomClaimHandler();
                    }
                }
            }
            return instance;
        }
    
        public Map<String, String> handleClaimMappings(StepConfig stepConfig,
                                                       AuthenticationContext context, Map<String, String> remoteAttributes,
                                                       boolean isFederatedClaims) throws FrameworkException {
    
            String authenticatedUser = null;
    
            if (stepConfig != null) {
                //calling from StepBasedSequenceHandler
                authenticatedUser = stepConfig.getAuthenticatedUser();
            } else {
                //calling from RequestPathBasedSequenceHandler
                authenticatedUser = context.getSequenceConfig().getAuthenticatedUser();
            }
    
            Map<String, String> claims = handleLocalClaims(authenticatedUser, context);
            claims.putAll(handleExternalClaims(authenticatedUser));
    
            return claims;
        }
    
    
        /**
         * @param context
         * @return
         * @throws FrameworkException
         */
        protected Map<String, String> handleLocalClaims(String authenticatedUser,
                                                        AuthenticationContext context) throws FrameworkException {
    	....
        }
    
        private Map<String, String> getFilteredAttributes(Map<String, String> allAttributes,
                                                          Map<String, String> requestedClaimMappings, boolean isStandardDialect) {
    	....
        }
    
        protected String getDialectUri(String clientType, boolean claimMappingDefined) {
    	....
        }
    
        /**
         * Added method to retrieve claims from external sources. This results will be merged to the local claims when
         * returning final claim list, to be added to the SAML response, that is sent back to the SP.
         *
         * @param authenticatedUser : The user for whom we require claim values
         * @return
         */
        private Map<String, String> handleExternalClaims(String authenticatedUser) throws FrameworkException {
            Map<String, String> externalClaims = new HashMap<String, String>();
            externalClaims.put("http://pushpalanka.org/claims/keplerNumber","E90836W19881010");
            externalClaims.put("http://pushpalanka.org/claims/status","active");
            return externalClaims;
        }
    }
  2. Drop the compiled OSGI bundle at IS_HOME/repository/components/dropins. (We developed this as a OSGI bundle as we need to get local claims as well using RealmService. You can find the complete bundle and source code here)
  3. Point WSO2 Identity Server to use the new custom implementation we have.

In IS_HOME/repository/conf/security/application­authentication.xml configure the new handler name. (in ‘ApplicationAuthentication.Extensions.ClaimHandler’ element.)

   <ClaimHandler>com.wso2.sample.claim.handler.CustomClaimHandler</ClaimHandler>

Now if look at the generated SAML Response, we will see the external attributes added.

Cheers!

[1] – https://docs.wso2.com/display/IS500/Adding+a+Service+Provider

Do you want to know how to develop your skillset to become a Java Rockstar?

Subscribe to our newsletter to start Rocking right now!

To get you started we give you two of our best selling eBooks for FREE!

JPA Mini Book

Learn how to leverage the power of JPA in order to create robust and flexible Java applications. With this Mini Book, you will get introduced to JPA and smoothly transition to more advanced concepts.

JVM Troubleshooting Guide

The Java virtual machine is really the foundation of any Java EE platform. Learn how to master it with this advanced guide!

Given email address is already subscribed, thank you!
Oops. Something went wrong. Please try again later.
Please provide a valid email address.
Thank you, your sign-up request was successful! Please check your e-mail inbox.
Please complete the CAPTCHA.
Please fill in the required fields.

Leave a Reply


1 + = nine



Java Code Geeks and all content copyright © 2010-2014, Exelixis Media Ltd | Terms of Use | Privacy Policy | Contact
All trademarks and registered trademarks appearing on Java Code Geeks are the property of their respective owners.
Java is a trademark or registered trademark of Oracle Corporation in the United States and other countries.
Java Code Geeks is not connected to Oracle Corporation and is not sponsored by Oracle Corporation.
Do you want to know how to develop your skillset and become a ...
Java Rockstar?

Subscribe to our newsletter to start Rocking right now!

To get you started we give you two of our best selling eBooks for FREE!

Get ready to Rock!
You can download the complementary eBooks using the links below:
Close