Wednesday, March 11, 2015

How to implement IDP (SSO) with Picketlink 2.5 using JSF login page

Hi All,
Thanks for reading my articles...

This article is a part of implementation about Picketlink SSO based on my last post:

 "How to Implement SSO using Picketlink 2.5, SEAM 2.x and JBoss AS/Wildfly" (2015-03-11)

You need to implement the code described in the link above to create the project...

So let's do it:

Take a look over idp implementation on my last article...

The login.jsp will be replaced by login.xhtml and some revisions will be necessary...

pom.xml

        <dependency>
             <groupId>org.jboss.spec.javax.faces</groupId>
             <artifactId>jboss-jsf-api_2.1_spec</artifactId>
             <scope>provided</scope>
        </dependency>

 

1 - login.xhtml

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 

<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:ui="http://java.sun.com/jsf/facelets">

<h:head>
    <title>
        <ui:insert name="title">IDP Picketlink SAML Identity Provider JSF 2.0</ui:insert>
    </title>
</h:head>

<body>
    <form id="login_form" name="login_form"
           action="j_security_check" method="post"
           enctype="application/x-www-form-urlencoded">

            <p>
                <b>IDP - JSF Login - PicketLink 2.5</b>
            </p>
            <p>Please login to proceed...</p>
          
            <div style="margin-left: 15px;">
                <p>          
                    <h:outputLabel for="j_username" value="Username"></h:outputLabel>
                    <br />
                    <h:inputText id="j_username" size="20" />
                </p>
                <p>
                    <h:outputLabel for="j_password" value="Password"></h:outputLabel>
                    <br />
                    <h:inputSecret id="j_password" size="20"/>
                </p>

                   <input type="submit" value="login" />
            </div>
    </form>

    </body>
</html>
 

2 - /WEB-INF/web.xml
...

    <servlet>
        <servlet-name>Faces Servlet</servlet-name>
        <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>Faces Servlet</servlet-name>
        <url-pattern>*.xhtml</url-pattern>
    </servlet-mapping>

 ...

    <login-config>
        <auth-method>FORM</auth-method>
        <realm-name>PicketLink IDP JSF Application</realm-name>
        <form-login-config>
            <form-login-page>/login.xhtml</form-login-page>
            <form-error-page>/login.xhtml</form-error-page>
        </form-login-config>
    </login-config>



 3 - /WEB-INF/picketlink.xml

 <PicketLink xmlns="urn:picketlink:identity-federation:config:2.1">
    <PicketLinkIDP xmlns="urn:picketlink:identity-federation:config:2.1"
        ServerEnvironment="tomcat" BindingType="REDIRECT" RelayState="someURL">
  
    <IdentityURL>${idp.url::http://localhost:9080/idp-jsf/}</IdentityURL>
        <Trust>
            <Domains>localhost</Domains>
        </Trust>
    </PicketLinkIDP>
  
    <Handlers xmlns="urn:picketlink:identity-federation:handler:config:2.1">
        <Handler
            class="org.picketlink.identity.federation.web.handlers.saml2.SAML2IssuerTrustHandler" />
        <Handler
            class="org.picketlink.identity.federation.web.handlers.saml2.SAML2LogOutHandler" />

        <Handler class="org.picketlink.identity.federation.web.handlers.saml2.SAML2AuthenticationHandler">
                    <Option Key="CLOCK_SKEW_MILIS" Value="30000"/>
        </Handler>  
      
      
        <Handler
            class="org.picketlink.identity.federation.web.handlers.saml2.RolesGenerationHandler" />
    </Handlers>
  
    <PicketLinkSTS xmlns="urn:picketlink:identity-federation:config:2.1" TokenTimeout="5000" ClockSkew="0">
        <TokenProviders>
            <TokenProvider
                ProviderClass="org.picketlink.identity.federation.core.saml.v1.providers.SAML11AssertionTokenProvider"
                TokenType="urn:oasis:names:tc:SAML:1.0:assertion"
                TokenElement="Assertion" TokenElementNS="urn:oasis:names:tc:SAML:1.0:assertion" />
            <TokenProvider
                ProviderClass="org.picketlink.identity.federation.core.saml.v2.providers.SAML20AssertionTokenProvider"
                TokenType="urn:oasis:names:tc:SAML:2.0:assertion"
                TokenElement="Assertion" TokenElementNS="urn:oasis:names:tc:SAML:2.0:assertion" />
        </TokenProviders>
    </PicketLinkSTS>
</PicketLink>


 OR just replace the ${idp.url} property described on my last article inside picketlink.xml 


  4 - /WEB-INF/jboss-web.xml

<jboss-web>
  <context-root>idp-jsf</context-root>
  <security-domain>idp</security-domain>
  <valve>
     <class-name>org.picketlink.identity.federation.bindings.tomcat.idp.IDPWebBrowserSSOValve</class-name>
   </valve>
</jboss-web>
 

 That's it...



How to Implement SSO using Picketlink 2.5, SEAM 2.x and JBoss AS/Wildfly

Hi Everyone,
Thanks for reading my first article...
I will demonstrate how to write a code that implements SAML Authentication with Picketlink and how to use the SEAM framework events to get attributes propagated by IDP. 
First of all, take a look at links for more informations about Picketlink:

     https://docs.jboss.org/author/display/PLINK/PicketLink+Quickstarts
     https://github.com/picketlink
     https://github.com/jboss-developer/jboss-picketlink-quickstarts

So, let's do it:

1 - IDP

1.a. pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>br.fred.pickelink.saml.sample</groupId>
    <artifactId>saml-idp</artifactId>
    <version>1.0.0-SNAPSHOT</version>
    <packaging>war</packaging>
    <name>saml-idp</name>
    <description>IDP: Web Project (war)</description>

    <!--
    <repositories>
        <repository>
            <id>jboss-picketlink</id>
            <url>http://....</url>
            <releases>
                <enabled>true</enabled>
            </releases>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </repository>
    </repositories>
    -->

    <properties>
        <version.picketlink>2.5.3.Final</version.picketlink>
        <version.picketlink.javaee.bom>2.5.3.Final</version.picketlink.javaee.bom>

        <version.junit>4.8.2</version.junit>

        <!-- Indicate the defaut server/binding version to package the applications -->
        <binding>jboss</binding>
        <binding-version>as7</binding-version>
      
        <project.build.sourceEncoding>ISO-8859-1</project.build.sourceEncoding>
        <version.compiler.plugin>2.3.1</version.compiler.plugin>
      
        <maven.compiler.target>1.7</maven.compiler.target>
        <maven.compiler.source>1.7</maven.compiler.source>
       
        <version.org.jboss.bom>1.0.7.Final</version.org.jboss.bom>
        <version.org.jboss.as.plugins.maven.plugin>7.3.Final</version.org.jboss.as.plugins.maven.plugin>

    </properties>

    <dependencyManagement>
          <dependencies>
          <dependency>
          <groupId>org.jboss.bom</groupId>
          <artifactId>jboss-javaee-6.0-with-hibernate3</artifactId>
          <version>${version.org.jboss.bom}</version>
          <type>pom</type>
          <scope>import</scope>
          </dependency>
      </dependencies>
       </dependencyManagement>
   
    <dependencies>
        <dependency>
            <groupId>org.picketlink.distribution</groupId>
            <artifactId>picketlink-jbas7</artifactId>
            <version>2.5.3.Final</version>
            <scope>provided</scope>
        </dependency>

        <dependency>
          <groupId>org.picketlink</groupId>
          <artifactId>picketlink-api</artifactId>
          <version>2.5.3.Final</version>
          <scope>provided</scope>
        </dependency>
   
        <dependency>
          <groupId>org.picketlink</groupId>
          <artifactId>picketlink-impl</artifactId>
          <version>2.5.3.Final</version>
          <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>org.picketlink</groupId>
            <artifactId>picketlink-federation</artifactId>
            <version>2.5.3.Final</version>
            <scope>provided</scope>
        </dependency>

        <dependency>
          <groupId>org.jboss.spec.javax.faces</groupId>
          <artifactId>jboss-jsf-api_2.1_spec</artifactId>
          <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>org.jboss.web</groupId>
            <artifactId>jbossweb</artifactId>
            <version>7.2.2.Final</version>
            <scope>provided</scope>
        </dependency>

        <dependency>
        <groupId>org.jboss.spec.javax.ejb</groupId>
        <artifactId>jboss-ejb-api_3.1_spec</artifactId>
        <scope>provided</scope>
        </dependency>
 

        <dependency>
        <groupId>org.hibernate.javax.persistence</groupId>
        <artifactId>hibernate-jpa-2.0-api</artifactId>
        <scope>provided</scope>
        </dependency>
 

        <dependency>
        <groupId>org.jboss.spec.javax.ws.rs</groupId>
        <artifactId>jboss-jaxrs-api_1.1_spec</artifactId>
        <scope>provided</scope>
        </dependency>
        <dependency>
        <groupId>javax.enterprise</groupId>
        <artifactId>cdi-api</artifactId>
        <scope>provided</scope>
        </dependency>
 

        <dependency>
        <groupId>org.jboss.spec.javax.security.jacc</groupId>
        <artifactId>jboss-jacc-api_1.4_spec</artifactId>
        <scope>provided</scope>
        </dependency>
 

        <dependency>
        <groupId>org.jboss.spec.javax.annotation</groupId>
        <artifactId>jboss-annotations-api_1.1_spec</artifactId>
        <scope>provided</scope>
        </dependency>
 

        <dependency>
        <groupId>org.jboss.spec.javax.servlet</groupId>
        <artifactId>jboss-servlet-api_3.0_spec</artifactId>
        <scope>provided</scope>
        </dependency>

      </dependencies>

    <build>
        <finalName>${project.artifactId}</finalName>
        <plugins>
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>${version.compiler.plugin}</version>
                <configuration>
                    <source>${maven.compiler.source}</source>
                    <target>${maven.compiler.target}</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>
 


1.b. Security IDP Domain
Now, we are going to define the security domain/constraints for JBoss standalone.xml or domain.xml (Security Subsystem)

 <security-domain name="idp" cache-type="default">
        <authentication>
            <login-module code="Remoting" flag="optional">
                <module-option name="password-stacking" value="useFirstPass"/>
            </login-module>
            <login-module code="RealmDirect" flag="required">
                <module-option name="password-stacking" value="useFirstPass"/>
            </login-module>
        </authentication>
 </security-domain>


Note: $JBOSS_HOME/bin/add_user.sh for adding users (user-properties file)


1.c. web.xml

<?xml version="1.0"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
    version="2.5">

    <display-name>IDP</display-name>

    <description>
        IDP Web Application for the PicketLink project
    </description>

    <listener>
        <listener-class>org.picketlink.identity.federation.web.listeners.IDPHttpSessionListener</listener-class>
    </listener>

    <security-constraint>
        <web-resource-collection>
            <web-resource-name>Images</web-resource-name>
            <url-pattern>/images/*</url-pattern>
        </web-resource-collection>
        <web-resource-collection>
            <web-resource-name>CSS</web-resource-name>
            <url-pattern>/css/*</url-pattern>
        </web-resource-collection>
    </security-constraint>

    <security-constraint>
        <web-resource-collection>
            <web-resource-name>Manager command</web-resource-name>
            <url-pattern>/*</url-pattern>
        </web-resource-collection>
        <auth-constraint>
            <role-name>*</role-name>
        </auth-constraint>
    </security-constraint>

    <login-config>
        <auth-method>FORM</auth-method>
        <realm-name>PicketLink IDP Application</realm-name>
        <form-login-config>
            <form-login-page>/jsp/login.jsp</form-login-page>
            <form-error-page>/jsp/login.jsp</form-error-page>
        </form-login-config>
    </login-config>

    <security-role>
        <role-name>*</role-name>
    </security-role>
</web-app>
 

1.d. login.jsp

<html>
<head>
<title>IDP Login</title>
<link rel="StyleSheet" href="css/idp.css" type="text/css">
</head>

<body>
    <div  style="margin-bottom: 180px; border: 1px solid #000000; width: 380px; height: 250px; background-color: #F8F8F8; align: center;">
        <form id="login_form" name="login_form" method="post"
            action="j_security_check" enctype="application/x-www-form-urlencoded">
            <center>
                <p>
                    Welcome <b>IDP</b>
                </p>
                <p>Please, login...</p>
            </center>

            <div style="margin-left: 20px;">
                <p>
                    <label for="username">Username</label><br /> <input id="username"
                        type="text" name="j_username" size="20" />
                </p>
                <p>
                    <label for="password">Password</label><br /> <input id="password"
                        type="password" name="j_password" value="" size="20" />
                </p>

                <p>
                      <select id="selecionar" name="selecionar" size="1" style="display: initial; width:180px;">
                        <option value="1">param1</option>
                        <option value="2">param2</option>
                    </select>
               </p>

                <center>
                    <input id="submit" type="submit" name="submit" value="Login"
                        class="buttonmed" />
                </center>
            </div>
          
        </form>
    </div>
</body>
</html>



1.e. /hosted/index.jsp 
After authentication process (hosted page - welcome)...


<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">

<html>
<head>
    <title>Welcome to IDP</title>
</head>

<body>
    <p>
        IDP Home <b>Auth OK !</b>
    </p>

    <div>
        <p>
            <ul>
                <li>Go to...
                    <a href="xxxx">
                        Send
                    </a>
                </li>
            </ul>
        </p>
    </div>
</body>
</html>
 



1.e. /WEB-INF/jboss-web.xml
Now, I will demostrate how to configure picketlink (setup)...

<jboss-web>
    <context-root>idp</context-root>
    <!-- from security subsystem -->

    <security-domain>idp</security-domain>
    <valve>
        <!-- class-name>org.picketlink.identity.federation.bindings.tomcat.idp.IDPWebBrowserSSOValve</class-name-->

        <!-- My SP Valve class to control parameters ... -->
        <class-name>br.fred.picketlink.saml.sample.IDPSSOValve</class-name>
    </valve>
</jboss-web> 




1.f. /WEB-INF/picketlink.xml
Picketlink (setup)...

<PicketLink xmlns="urn:picketlink:identity-federation:config:2.1">
    <PicketLinkIDP xmlns="urn:picketlink:identity-federation:config:2.1"   AttributeManager="br.fred.picketlink.saml.sample.EngineAttributeManager">

        <IdentityURL>${idp.url::http://localhost:9080/idp/}</IdentityURL>
        <Trust>
            <Domains>locahost, localhost:9080</Domains>
        </Trust>
    </PicketLinkIDP>

    <PicketLinkSTS xmlns="urn:picketlink:identity-federation:config:1.0" TokenTimeout="30000" ClockSkew="0">
        <TokenProviders>
            <TokenProvider ProviderClass="org.picketlink.identity.federation.core.saml.v2.providers.SAML20AssertionTokenProvider"
                TokenType="urn:oasis:names:tc:SAML:2.0:assertion" TokenElement="Assertion"
                TokenElementNS="urn:oasis:names:tc:SAML:2.0:assertion" />
        </TokenProviders>
    </PicketLinkSTS>

    <Handlers xmlns="urn:picketlink:identity-federation:handler:config:2.1">
        <Handler class="org.picketlink.identity.federation.web.handlers.saml2.SAML2IssuerTrustHandler" />
        <Handler class="org.picketlink.identity.federation.web.handlers.saml2.SAML2LogOutHandler" />
        <Handler class="org.picketlink.identity.federation.web.handlers.saml2.SAML2AuthenticationHandler"/>

        <Handler class="org.picketlink.identity.federation.web.handlers.saml2.SAML2AttributeHandler">
            <Option Key="ATTRIBUTE_MANAGER" Value="br.fred.picketlink.saml.sample.EngineAttributeManager"/>
            <Option Key="ATTRIBUTE_KEYS" Value="selecionar" />
        </Handler>

        
         <!-- my specific handler -->
        <Handler class="br.fred.picketlink.saml.sample.IDPSAMLHandler"/>

        <Handler class="org.picketlink.identity.federation.web.handlers.saml2.RolesGenerationHandler" />
    </Handlers>

</PicketLink>




1.g. /WEB-INF/jboss-deployment-structure.xml
 Class loading...

<jboss-deployment-structure>
    <deployment>
        <exclusions>
        </exclusions>
   
        <dependencies>
            <module name="org.picketlink" />
            <module name="org.picketlink.config" />
            <module name="org.picketlink.common"/>
            <module name="org.picketlink.core"/>
            <module name="org.picketlink.core.api"/>
            <module name="org.picketlink.idm.api" />
            <module name="org.picketlink.federation" />
        </dependencies>
    </deployment>
</jboss-deployment-structure>



1.h. Attribute Manager Class
Class that is responsible for handling attributes. See picketlink.xml for class declaration...

 package br.fred.picketlink.saml.sample;

import java.security.Principal;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.security.jacc.PolicyContextException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

import org.picketlink.identity.federation.core.interfaces.AttributeManager;
import org.picketlink.identity.federation.web.constants.GeneralConstants;

public class EngineAttributeManager implements AttributeManager {

    private static final String PARAM_SELECT = "selecionar";
    private static final Logger logger       = Logger.getLogger(EngineAttributeManager.class.getName());

    @Override
    public Map<String, Object> getAttributes(Principal userPrincipal, List<String> attributeKeys) {
        Map<String,Object> attributes = new HashMap<String, Object>();
        HttpServletRequest request    = null;

        try {
            request = (HttpServletRequest) javax.security.jacc.PolicyContext.getContext(
                    "javax.servlet.http.HttpServletRequest");

            HttpSession session = request.getSession();

            Object param = findAttributesSession(PARAM_SELECT, session);
            if (param == null) {
                param = findParametersRequest(PARAM_SELECT, request);
            }

            //  test !!!! please remove this line
            logger.info("Atributo contido nos parametros propagados ? " +
                    attributeKeys.contains(PARAM_SELECT));
          
               logger.info("Atributo definido pelo manager: " + PARAM_SELECT + " - valor: " + param);
               attributes.put(PARAM_SELECT, param);

        } catch (PolicyContextException e) {
            logger.log(Level.WARNING, "Error...", e);
        }

        logger.info("** ATTRIBUTES SIZE: " + attributes.size());
        return attributes;
    }

    @SuppressWarnings("unchecked")
    protected Object findAttributesSession(final String parameter, HttpSession session) {
        StringBuffer sb = new StringBuffer("\n\n ###############################################");

        Enumeration<String> enums = session.getAttributeNames();
        String keyn = null;
        while (enums.hasMoreElements()) {
            keyn = enums.nextElement();
            sb.append("\n Key: " + keyn + " - value: " + session.getAttribute(keyn));

            if (keyn.equalsIgnoreCase(parameter)) {
                  sb.append("\n Session Parameter found key: " + keyn + " - value: " + session.getAttribute(keyn));
                  sb.append("\n ###############################################");

                  logger.info(sb.toString());

               return session.getAttribute(keyn);
            }
        }

        Object customAttributes = session.getAttribute(GeneralConstants.ATTRIBUTES);
        if ( customAttributes != null ) {

           Map<String, Object> attributesMap = (Map<String, Object>) customAttributes;
         
           sb.append("\n\n IDP ATTRIBUTES (GeneralConstants.ATTRIBUTES): ");
         
           for ( String key : attributesMap.keySet() ) {

               Object attribute = attributesMap.get(key);

                  sb.append("\n Session Attribute Key: " + key + " - value: " + attribute);
        
                  if (key.equalsIgnoreCase(parameter)) {
                      sb.append("\n Session Attribute found key: " + key + " - value: " + attribute);
                   sb.append("\n ###############################################");
                   logger.info(sb.toString());

                   return attribute;
                  }
           }
        }

        sb.append("\n ###############################################");
        logger.info(sb.toString());

        return null;
    }

    protected Object findParametersRequest(final String parameter, HttpServletRequest request) {
        StringBuffer sb = new StringBuffer("\n\n ###############################################");

        Enumeration<String> enums = request.getParameterNames();
        String key = null;
        while (enums.hasMoreElements()) {
            key = enums.nextElement();
            sb.append("\n Request Parameter Key: " + key + " - value: " + request.getParameter(key));

            if (key.equalsIgnoreCase(parameter)) {
                sb.append("\n Request Parameter found key: " + key + " - value: " + request.getParameter(key));
                sb.append("\n ###############################################");
                logger.info(sb.toString());

                return request.getParameter(key);
            }
        }

       sb.append("\n ###############################################");
       logger.info(sb.toString());

       return null;
    }
}



1.i. IDP Handler Class 
After authentication, it will be possible handle saml informations...

package br.fred.picketlink.saml.sample;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

import org.picketlink.common.exceptions.ProcessingException;
import org.picketlink.identity.federation.core.saml.v2.interfaces.SAML2HandlerRequest;
import org.picketlink.identity.federation.core.saml.v2.interfaces.SAML2HandlerResponse;
import org.picketlink.identity.federation.saml.v2.protocol.AuthnRequestType;
import org.picketlink.identity.federation.web.handlers.saml2.BaseSAML2Handler;

public class IDPSAMLHandler extends BaseSAML2Handler {

    @Override
    public void handleRequestType(SAML2HandlerRequest request,
            SAML2HandlerResponse response) throws ProcessingException {

        logger.info("IDP - handleRequestType");

        // protecao para o logout e fora do fluxo de sessao
        if (request.getSAML2Object() instanceof AuthnRequestType == false)
            return;

        if (response.getDestination() != null) {

               logger.info("who i am - url destination");
        }
    }
}


1.j. IDP Valve Class
To Handle parameters...

package br.fred.picketlink.saml.sample;

import java.io.IOException;
import java.util.logging.Logger;

import javax.servlet.ServletException;

import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;
import org.picketlink.identity.federation.bindings.tomcat.idp.IDPWebBrowserSSOValve;

public class IDPSSOValve extends IDPWebBrowserSSOValve {

    private static final String PARAM_SELECT = "selecionar";
    private static final Logger logger       = Logger.getLogger(IDPSSOValve.class.getName());

    @Override
    public void invoke(Request request, Response response) throws IOException, ServletException {
        logger.info("IDP Valve - inkoke");
       
        final String param = request.getParameter(PARAM_SELECT);
        if (param != null ) {
            logger.info("IDP Valve - inkoke - param: " + param);
            request.getSession().setAttribute(PARAM_SELECT, param);

            // now the engine manager take care of parameters...
        }
       
        super.invoke(request, response);
    }
}




1.k. That's it! IDP is working...
Deploy your IDP war file for JBoss AS 7 (EAP 6.3) or Wildfly




2 - SP (Service Provider)
An application that uses IDP for authentication...

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>br.fred.picketlink.saml.sample</groupId>
    <artifactId>saml-sp</artifactId>
    <version>1.0.0-SNAPSHOT</version>
    <packaging>war</packaging>
    <name>saml-sp</name>
    <description>SP: Projeto Service Provider (war)</description>

    <!-- <repositories> <repository> <id>jboss-picketlink</id> <url>http://....</url>
        <releases> <enabled>true</enabled> </releases> <snapshots> <enabled>false</enabled>
        </snapshots> </repository> </repositories> -->

    <properties>
        <version.picketlink>2.5.3.Final</version.picketlink>
        <version.picketlink.javaee.bom>2.5.3.Final</version.picketlink.javaee.bom>

        <version.junit>4.8.2</version.junit>

        <!-- Indicate the defaut server/binding version to package the applications -->
        <binding>jboss</binding>
        <binding-version>as7</binding-version>

        <project.build.sourceEncoding>ISO-8859-1</project.build.sourceEncoding>
        <version.compiler.plugin>2.3.1</version.compiler.plugin>

        <maven.compiler.target>1.7</maven.compiler.target>
        <maven.compiler.source>1.7</maven.compiler.source>

        <version.org.jboss.bom>1.0.7.Final</version.org.jboss.bom>
        <version.org.jboss.as.plugins.maven.plugin>7.3.Final</version.org.jboss.as.plugins.maven.plugin>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.jboss.bom</groupId>
                <artifactId>jboss-javaee-6.0-with-hibernate3</artifactId>
                <version>${version.org.jboss.bom}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>

        <dependency>
            <groupId>org.picketlink.distribution</groupId>
            <artifactId>picketlink-jbas7</artifactId>
            <version>2.5.3.Final</version>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>org.picketlink</groupId>
            <artifactId>picketlink-api</artifactId>
            <version>2.5.3.Final</version>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>org.picketlink</groupId>
            <artifactId>picketlink-impl</artifactId>
            <version>2.5.3.Final</version>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>org.picketlink</groupId>
            <artifactId>picketlink-federation</artifactId>
            <version>2.5.3.Final</version>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>org.jboss.spec.javax.faces</groupId>
            <artifactId>jboss-jsf-api_2.1_spec</artifactId>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>org.jboss.web</groupId>
            <artifactId>jbossweb</artifactId>
            <version>7.2.2.Final</version>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>org.jboss.spec.javax.ejb</groupId>
            <artifactId>jboss-ejb-api_3.1_spec</artifactId>
            <scope>provided</scope>
        </dependency>
       
        <dependency>
            <groupId>org.hibernate.javax.persistence</groupId>
            <artifactId>hibernate-jpa-2.0-api</artifactId>
            <scope>provided</scope>
        </dependency>
       
        <dependency>
            <groupId>org.jboss.spec.javax.ws.rs</groupId>
            <artifactId>jboss-jaxrs-api_1.1_spec</artifactId>
            <scope>provided</scope>
        </dependency>
       
        <dependency>
            <groupId>javax.enterprise</groupId>
            <artifactId>cdi-api</artifactId>
            <scope>provided</scope>
        </dependency>
       
        <dependency>
            <groupId>org.jboss.spec.javax.security.jacc</groupId>
            <artifactId>jboss-jacc-api_1.4_spec</artifactId>
            <scope>provided</scope>
        </dependency>
       
        <dependency>
            <groupId>org.jboss.spec.javax.annotation</groupId>
            <artifactId>jboss-annotations-api_1.1_spec</artifactId>
            <scope>provided</scope>
        </dependency>
       
        <dependency>
            <groupId>org.jboss.spec.javax.servlet</groupId>
            <artifactId>jboss-servlet-api_3.0_spec</artifactId>
            <scope>provided</scope>
        </dependency>
       
        <dependency>
            <groupId>org.jboss.seam</groupId>
            <artifactId>jboss-seam</artifactId>
            <version>2.2.2.Final</version>
            <scope>compile</scope>
        </dependency>
    </dependencies>

    <build>
        <finalName>${project.artifactId}</finalName>
        <plugins>
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>${version.compiler.plugin}</version>
                <configuration>
                    <source>${maven.compiler.source}</source>
                    <target>${maven.compiler.target}</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>



2.a. Security Domain
It is necessary to define security domain to control SP Authentication over Picketlink SAML protocol


    <security-domain name="sp" cache-type="default">
        <authentication>
            <login-module code="org.picketlink.identity.federation.bindings.jboss.auth.SAML2LoginModule" flag="required"/>
        </authentication>
    </security-domain>




2.b. /WEB-INF/web.xml

<?xml version="1.0"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
    version="2.5">
    <display-name>PicketLink SP Application</display-name>
    <description>
        SP Teste Application
    </description>

    <!-- Define a Security Constraint on this Application -->
    <security-constraint>
        <web-resource-collection>
            <web-resource-name>SP Application</web-resource-name>
            <url-pattern>/*</url-pattern>
        </web-resource-collection>
        <auth-constraint>
            <role-name>*</role-name>
        </auth-constraint>
    </security-constraint>

    <!-- Define a security constraint that gives unlimted access to freezone -->
    <security-constraint>
        <web-resource-collection>
            <web-resource-name>images</web-resource-name>
            <url-pattern>/images/*</url-pattern>
        </web-resource-collection>
        <web-resource-collection>
            <web-resource-name>css</web-resource-name>
            <url-pattern>/css/*</url-pattern>
        </web-resource-collection>
    </security-constraint>

    <!-- Define the Login Configuration for this Application -->
    <login-config>
        <auth-method>FORM</auth-method>
        <realm-name>SP Application</realm-name>
        <form-login-config>
            <form-login-page>/jsp/login.jsp</form-login-page>
            <form-error-page>/jsp/login.jsp</form-error-page>
        </form-login-config>
    </login-config>

    <!-- Security roles referenced by this web application -->
    <security-role>
        <description>
            The role that is required to log in to the SP Application
        </description>
        <role-name>*</role-name>
    </security-role>
</web-app>
 

2.c. /WEB-INF/picketlink.xml
Picketlink setup for SP...

<PicketLink xmlns="urn:picketlink:identity-federation:config:2.1">
    <PicketLinkSP xmlns="urn:picketlink:identity-federation:config:2.1"
        ServerEnvironment="tomcat" BindingType="REDIRECT" RelayState="someURL">
        <IdentityURL>${idp.url::http://localhost:9080/idp/}</IdentityURL>
        <ServiceURL>${sp.url::http://localhost:9080/sp/}</ServiceURL>
        <Trust>
            <Domains>localhost</Domains>
        </Trust>
    </PicketLinkSP>

    <Handlers xmlns="urn:picketlink:identity-federation:handler:config:2.1">
        <Handler class="org.picketlink.identity.federation.web.handlers.saml2.SAML2LogOutHandler" />
        <!-- Handler class="org.picketlink.identity.federation.web.handlers.saml2.SAML2AuthenticationHandler">
            <Option Key="CLOCK_SKEW_MILIS" Value="30000"/>
        </Handler-->

        <!-- my specific authentication handler -->
        <Handler class="br.fred.picketlink.saml.sample.SPSamlDynamicUrlAuthenticationHandler">
            <Option Key="CLOCK_SKEW_MILIS" Value="30000"/>
        </Handler>
        <Handler class="org.picketlink.identity.federation.web.handlers.saml2.RolesGenerationHandler" />
    </Handlers>
</PicketLink>




2.d. /WEB-INF/jboss-web.xml
Valve definition to SP...

<?xml version="1.0" encoding="UTF-8"?>
<jboss-web>
    <security-domain>sp</security-domain>
    <context-root>sp</context-root>
    <valve>
        <class-name>org.picketlink.identity.federation.bindings.tomcat.sp.ServiceProviderAuthenticator
        </class-name>
    </valve>
</jboss-web>


2.e. index.jsp
Home page after login...
 
<div align="center">
<h1>SP Saml Dashboard</h1>
<br/>
Welcome <b> <%= request.getUserPrincipal().getName()%> </b>.
<br/>
<a href="<%= request.getContextPath() %>/?GLO=true">LogOut</a>
</div>



2.f. Authentication  Handler Class
This is the handler class for authentication control between IDP and SP. I use this class to propagate the same url destination ip or hostname (dns)...

package br.fred.picketlink.saml.sample;

import static org.picketlink.common.util.StringUtil.isNotNull;

import java.net.URI;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

import org.jboss.security.audit.AuditLevel;
import org.picketlink.common.constants.GeneralConstants;
import org.picketlink.common.constants.JBossSAMLURIConstants;
import org.picketlink.common.constants.SAMLAuthenticationContextClass;
import org.picketlink.common.exceptions.ProcessingException;
import org.picketlink.common.util.StringUtil;
import org.picketlink.config.federation.SPType;
import org.picketlink.identity.federation.api.saml.v2.request.SAML2Request;
import org.picketlink.identity.federation.core.audit.PicketLinkAuditEvent;
import org.picketlink.identity.federation.core.audit.PicketLinkAuditEventType;
import org.picketlink.identity.federation.core.audit.PicketLinkAuditHelper;
import org.picketlink.identity.federation.core.saml.v2.common.IDGenerator;
import org.picketlink.identity.federation.core.saml.v2.interfaces.SAML2Handler;
import org.picketlink.identity.federation.core.saml.v2.interfaces.SAML2HandlerRequest;
import org.picketlink.identity.federation.core.saml.v2.interfaces.SAML2HandlerResponse;
import org.picketlink.identity.federation.core.saml.v2.interfaces.SAML2HandlerRequest.GENERATE_REQUEST_TYPE;
import org.picketlink.identity.federation.saml.v2.protocol.AuthnContextComparisonType;
import org.picketlink.identity.federation.saml.v2.protocol.AuthnRequestType;
import org.picketlink.identity.federation.saml.v2.protocol.RequestedAuthnContextType;
import org.picketlink.identity.federation.web.core.HTTPContext;
import org.picketlink.identity.federation.web.handlers.saml2.SAML2AuthenticationHandler;

/**
 *
 * @author Frederico

 *
 * @See:
 *     https://github.com/pedroigor/picketlink-quickstarts/blob/master/picketlink-federation-saml-dynamic-idp-resolution/service-provider/src/main/java/org/picketlink/quickstarts/federation/saml/DynamicIdPSAML2AuthenticationHandler.java
 *  https://developer.jboss.org/thread/200615
 */
public class SPSamlDynamicUrlAuthenticationHandler extends SAML2AuthenticationHandler {

    private final SAMLSPAuthenticationHandler sp = new SAMLSPAuthenticationHandler();

/*
    public void handleRequestType(SAML2HandlerRequest request, SAML2HandlerResponse response) throws ProcessingException {
        super.handleRequestType(request, response);

        if (getType() != HANDLER_TYPE.IDP) {
            sp.handleRequestType(request, response);
        }
    }

    @Override
    public void handleStatusResponseType(SAML2HandlerRequest request, SAML2HandlerResponse response) throws ProcessingException {
        super.handleStatusResponseType(request, response);

        if (getType() != HANDLER_TYPE.IDP) {
            sp.handleStatusResponseType(request, response);
        }
    }
*/
    @Override
    public void generateSAMLRequest(SAML2HandlerRequest request, SAML2HandlerResponse response) throws ProcessingException {
        if (GENERATE_REQUEST_TYPE.AUTH != request.getTypeOfRequestToBeGenerated())
            return;

        super.generateSAMLRequest(request, response);

        if (getType() != HANDLER_TYPE.IDP) {
           
            logger.info("BEGIN - SP.generateSAMLRequest");
           
            sp.generateSAMLRequest(request, response);
            response.setSendRequest(true);

            logger.info("END - SP.generateSAMLRequest - setSendRequest TRUE");
        }
    }

    private class SAMLSPAuthenticationHandler {

        public void generateSAMLRequest(SAML2HandlerRequest request, SAML2HandlerResponse response) throws ProcessingException {
            String issuerValue = request.getIssuer().getValue();
            SAML2Request samlRequest = new SAML2Request();
            String id = IDGenerator.create("ID_");

            /* original...
            String assertionConsumerURL = (String) handlerConfig.getParameter(SAML2Handler.ASSERTION_CONSUMER_URL);
            if (StringUtil.isNullOrEmpty(assertionConsumerURL)) {
                assertionConsumerURL = issuerValue;
            }
            */
            // issue solved...
            HTTPContext httpContext = (HTTPContext) request.getContext();
            String assertionConsumerURL = httpContext.getRequest().getRequestURL().toString();

            if (StringUtil.isNullOrEmpty(assertionConsumerURL)) {
                assertionConsumerURL = (String) handlerConfig.getParameter(SAML2Handler.ASSERTION_CONSUMER_URL);
            }

            if (StringUtil.isNullOrEmpty(assertionConsumerURL)) {
                assertionConsumerURL = issuerValue;
            }
            logger.info("** SP-generateSAMLRequest - assertionConsumerURL: " + assertionConsumerURL);
            defineUrlDestinationSession(assertionConsumerURL, request);
            //----------------------

            // Check if there is a nameid policy
            String nameIDFormat = (String) handlerConfig.getParameter(GeneralConstants.NAMEID_FORMAT);
            if (isNotNull(nameIDFormat)) {
                samlRequest.setNameIDFormat(nameIDFormat);
            }
            try {
                AuthnRequestType authn = samlRequest.createAuthnRequestType(
                        id, assertionConsumerURL, response.getDestination(), issuerValue);

                createRequestedAuthnContext(authn);

                String bindingType = getSPConfiguration().getBindingType();
                boolean isIdpUsesPostBinding = getSPConfiguration().isIdpUsesPostBinding();

                if (bindingType != null) {
                    if (bindingType.equals("POST") || isIdpUsesPostBinding) {
                        authn.setProtocolBinding(URI.create(JBossSAMLURIConstants.SAML_HTTP_POST_BINDING.get()));
                    } else if (bindingType.equals("REDIRECT")) {
                        authn.setProtocolBinding(URI.create(JBossSAMLURIConstants.SAML_HTTP_REDIRECT_BINDING.get()));
                    } else {
                        throw logger.samlInvalidProtocolBinding();
                    }
                }

                response.setResultingDocument(samlRequest.convert(authn));
                response.setSendRequest(true);

                Map<String, Object> requestOptions = request.getOptions();
                PicketLinkAuditHelper auditHelper = (PicketLinkAuditHelper) requestOptions.get(GeneralConstants.AUDIT_HELPER);
                if (auditHelper != null) {
                    PicketLinkAuditEvent auditEvent = new PicketLinkAuditEvent(AuditLevel.INFO);
                    auditEvent.setWhoIsAuditing((String) requestOptions.get(GeneralConstants.CONTEXT_PATH));
                    auditEvent.setType(PicketLinkAuditEventType.CREATED_ASSERTION);
                    auditEvent.setAssertionID(id);
                    auditHelper.audit(auditEvent);
                }

                // Save AuthnRequest ID into sharedState, so that we can later process it by another handler
                request.addOption(GeneralConstants.AUTH_REQUEST_ID, id);
            } catch (Exception e) {
                throw logger.processingError(e);
            }
        }

        private void defineUrlDestinationSession(String urlDest, SAML2HandlerRequest request) {
            try {
                final String SP_DESTINATION_URL  = "sp_url_destination_url";
                HttpSession session = getHttpSession(request);

                // workaround!!!
                if (session == null) {
                    HttpServletRequest req =
                            (HttpServletRequest) javax.security.jacc.PolicyContext.getContext(
                            "javax.servlet.http.HttpServletRequest");

                    session = req.getSession(false);
                }
           
                session.setAttribute(
                    SP_DESTINATION_URL, urlDest);

            } catch (Exception ex) {
                throw new RuntimeException(ex);
            }
        }


        private SPType getSPConfiguration() {
            SPType spConfiguration = (SPType)     handlerChainConfig.getParameter(GeneralConstants.CONFIGURATION);

            if (spConfiguration == null) {
                throw logger.samlHandlerServiceProviderConfigNotFound();
            }

            return spConfiguration;
        }

        private void createRequestedAuthnContext(final AuthnRequestType authn) {
            String authnContextClasses = (String) handlerConfig.getParameter(GeneralConstants.AUTHN_CONTEXT_CLASSES);

            if (isNotNull(authnContextClasses)) {
                RequestedAuthnContextType requestAuthnContext = new RequestedAuthnContextType();

                for (String classFqn : authnContextClasses.split(",")) {
                    SAMLAuthenticationContextClass standardClass = SAMLAuthenticationContextClass.forAlias(classFqn);

                    if (standardClass != null) {
                        classFqn = standardClass.getFqn();
                    }

                    requestAuthnContext.addAuthnContextClassRef(classFqn);
                }

                if (!requestAuthnContext.getAuthnContextClassRef().isEmpty()) {
                    String comparison = (String) handlerConfig.getParameter(GeneralConstants.REQUESTED_AUTHN_CONTEXT_COMPARISON);

                    if (isNotNull(comparison)) {                     requestAuthnContext.setComparison(AuthnContextComparisonType.fromValue(comparison));
                    }
                    authn.setRequestedAuthnContext(requestAuthnContext);
                } else {
                    logger.debug("RequestedAuthnContext not set for AuthnRequest. No class was provided.");
                }
            }
        }
    }
}

 
2.g. Seam Observer Class
Now i will show how to get parameters and (user) principal after the authentication process, using the SEAM framework.
Imagine that you create a class XptoIdentity (that extends Identity - seam identity class).
For propagating the login information from IDP to the SP application is so simple:

- create a login method (override) on the XptoIdentity class.
- create an observer class as i posted below, and take care about security navigation (page.xml workflow)
 package br.fred.picketlink.saml.sample;

import java.io.IOException;
import java.io.Serializable;
import java.security.Principal;

import java.util.List;
import java.util.Map;

import javax.security.auth.login.LoginException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.jboss.seam.Component;
import org.jboss.seam.ScopeType;
import org.jboss.seam.annotations.Logger;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.annotations.Observer;
import org.jboss.seam.annotations.Scope;
import org.jboss.seam.log.Log;
import org.jboss.seam.security.Credentials;
import org.jboss.seam.security.Identity;

@Name("spAuthenticationFilter")
@Scope(ScopeType.SESSION)
public class SPSeamObserver implements Serializable {

    final static String SESSION_ATTRIBUTE_MAP = "SESSION_ATTRIBUTE_MAP";
    final static String PARAM_SELECT      = "selecionar";

    /**
     * serial
     */
    private static final long serialVersionUID = -7399762465116306988L;

    @Logger
    Log log;

    @Observer(Identity.EVENT_NOT_LOGGED_IN)
    public void notLoggedIn() {
        try {
            HttpServletRequest request = (HttpServletRequest) javax.security.jacc.PolicyContext.getContext(
                "javax.servlet.http.HttpServletRequest");
          
            processaSamlAuth(request, null, null);
          
        } catch (Exception ex) {
            ex.printStackTrace();
            throw new RuntimeException(ex);
        }

    }

    @Observer(Identity.EVENT_LOGGED_OUT)
    public void loggedOut() {
        try {
            HttpServletRequest request = (HttpServletRequest) javax.security.jacc.PolicyContext.getContext(
                "javax.servlet.http.HttpServletRequest");
          
            request.logout();

            Credentials credentials = (Credentials) Component.getInstance(org.jboss.seam.security.Credentials.class);
            credentials.clear();
            credentials.invalidate();
          
            org.jboss.seam.web.Session.instance().invalidate();

        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    private void processaSamlAuth(HttpServletRequest request,
            HttpServletResponse response, FilterChain chain)
            throws IOException, ServletException {

        Principal userPrincipal = request.getUserPrincipal();
        if (userPrincipal != null && userPrincipal.getName() != null &&
                request.getSession().getAttribute("picketlink.principal") != null) {

            setupCredentialIdentity(request, userPrincipal);
        }
    }

    private void setupCredentialIdentity(final HttpServletRequest request,
            final Principal userPrincipal) throws ServletException, IOException {

        try {
            Identity identity = Identity.instance();

            identity.getCredentials().setUsername(userPrincipal.getName());

            setupParameter(identity, request);

        } catch (Exception ex) {
            throw new IOException(ex);
        }
    }

    @SuppressWarnings("unchecked")
    private void
setupParameter(Identity identity,
            HttpServletRequest request) {
        

        // map defined by IDP - Engine Manager...
        Map<String, List<Object>> sessionMap =
                (Map<String, List<Object>>) request.getSession(false).getAttribute(
                        SESSION_ATTRIBUTE_MAP);

        if (sessionMap != null && !sessionMap.isEmpty()) {

            if (sessionMap.get(PARAM_SELECT) != null) {
                Object param = sessionMap.get(PARAM_SELECT).get(0);
                request.getSession(false).setAttribute(PARAM_SELECT, param);
            }
        }
    }
}
 

2.h. SP Filter Class
If you just want to manager the parameters propagated by IDP, see the filter example below...

package br.fred.picketlink.saml.sample;

import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;

@WebFilter("/*")
public class SPSSOFilter implements Filter {
   
    Logger logger = Logger.getLogger(SPSSOFilter.class.getName());

    @Override
    public void destroy() {
        // TODO Auto-generated method stub

    }

    @Override
    public void init(FilterConfig arg0) throws ServletException {
        // TODO Auto-generated method stub
    }

    @Override
    public void doFilter(ServletRequest req, ServletResponse rep,
            FilterChain chain) throws IOException, ServletException {

        setupParameter((HttpServletRequest)req);
        chain.doFilter(req, rep);
    }
   
    final static String SESSION_ATTRIBUTE_MAP = "SESSION_ATTRIBUTE_MAP";
    final static String PARAM_SELECT          = "selecionar";

    @SuppressWarnings("unchecked")
    private void
setupParameter(HttpServletRequest request) {
       
        Map<String, List<Object>> sessionMap =
                (Map<String, List<Object>>) request.getSession(false).getAttribute(
                        SESSION_ATTRIBUTE_MAP);

        if (sessionMap != null && !sessionMap.isEmpty()) {

            if (sessionMap.get(PARAM_SELECT) != null) {
                Object param = sessionMap.get(PARAM_SELECT).get(0);
                logger.info("** Parameter value: " + param);
            }
        }
    }
}
 


2.i. That's it! SP is working...
Deploy your SP war file for JBoss AS 7 (EAP 6.3) or Wildfly

Try to access localhost:<port>/sp/ directly...
After authentication process, the idp redirects to the original url (destination): http://localhost:<port>/sp
Try to use http://127.0.0.1:<port>/sp...the same url ip will be got...this issue was solved with section 2.f.

Thanks a lot!
See you on the next article...