Skip to main content

Support for Programmatic Authorization in WebServices With Metro 1.3

Posted by kumarjayanti on September 15, 2008 at 6:57 AM PDT



Starting with  Promoted href="https://sailfin.dev.java.net/downloads/v1-b36.html">Build 36 
of  SailFin,  Metro 1.3 users can perform Programmatic
Authorization decisions inside their  SEI Implementations. 


1. What is the API to be used for Programmatic Authorization ?

 The API  has been there in JAXWS since the very
beginning,  javax.xml.ws.WebServiceContext; Specifically the
method isUserInRole(String role).


2. How are Roles defined ?

The Standard JavaEE methods for defining Roles for WebApplications
within the web.xml file can be used for WebServices as well.  For
example the web.xml below defines two roles doctor and patient.



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">

   

       

            30

       


   


   

       
index.jsp

       


   

        A
doctor


       
doctor

   


   

        A
patient


       
patient

   






3. How is the Principal to Role Mapping Handled ?


GlassFish provides facilities for doing this.  It is an entire
topic of its own and  most people writing WebApplications for
GlassFish know how to do this.  The following two links have more
details on this topic.



http://blogs.sun.com/monzillo/entry/principal_2_role_mapping_and

http://docs.sun.com/app/docs/doc/819-3672/beacr



4. What are the Configuration Steps to Enable Programmatic
Authorization


In general when making use of NON-SAML Security Mechanism(s) such as
UsernameToken, X509Certificate or Kerberos Tokens, the only thing one
needs to do before he/she can use the Programmatic Authorization API is
to make sure steps 2 and 3 above are taken care of (i.e define your
Roles and Define your Principal to Role Mapping). This is because the
Metro runtime can automatically establish the incoming Caller Identity
for those Mechanisms.  However for SAML based Mechanisms the
Runtime would not know what is the incoming Caller Identity since the
Principal and other Authorization Information would generally be inside
the SAML Assertion sent by the Caller and only the WebService can
decide what is the exact caller Identity.



So in Metro 1.3 we have extended the  SAMLAssertionValidator
interface to allow the SEI developer to decide and set the principal
that identifies the incoming caller (presumably after analyzing the
contents of the incoming SAML assertion and any other additional
Context). The new interface is
com.sun.xml.wss.impl.callback.SAMLValidator interface.  The sample
below would show an implementation of the interface.



5. How do i Integrate my UsernameToken/Certificate
Authentication  in Metro WebService with GlassFish Realms


When using UsernameToken or Certificate based scenarios, one can make
use of  GlassFish Realms, and the associated assing-groups
facilities to automatically assign additional  group memberships
to the incoming Caller.  These Groups may then be mapped to Roles
as appropriate within sun-web.xml or using
default-principal-to-role-mapping feature of GlassFish (see 3 above).



https://glassfish.dev.java.net/javaee5/docs/DG/beabg.html#beabo

https://glassfish.dev.java.net/javaee5/security/faq.html#diffauthmodulerealm

http://blogs.sun.com/swchan/entry/jdbcrealm_in_glassfish

http://blogs.sun.com/swchan/entry/assign_groups



So a WebService making use of  UsernameToken authentication
mechanisms can declare the realm against which the username/password
needs to be authenticated.  The realm declaration however is not
under the  element of web.xml as defined
in  JavaEE. This is because the login-config element was meant for
WebApplications.




FORM
LDAPRealm

/pages/logon.jsp
/pages/logonError.jsp


Specifically  the auth-method is required inside a login-config
and those methods  FORM/BASIC etc are not applicable for SOAP
Message Security.  So the alternative way to define the realm for
your webapplication is currently to package your WAR file into an EAR
and define the realm inside sun-application.xml.  Netbeans can be
used to easily create a Web Application Packaged as an EAR. And then
one can add a sun-application.xml file defining the realm. See example
below.





Application Server 9.0 Java EE Application 5.0//EN'
'http://www.sun.com/software/appserver/dtds/sun-application_5_0-0.dtd'>



   JDBCRealm





In the above sample i have defined the realm to be the JDBCRealm
supported by GlassFish.



Note:  The SailFin Builds of GlassFish support a new
JDBCDigestRealm.  However the PasswordDigest Authentication
Mechanism of  Metro is still not integrated with the
JDBCDigestRealm. This would come soon in near future releases of Metro.

6.Sample SEI Impl showing Programmatic Authorization


As a sample this document makes use of  the  style="font-weight: bold;">SAML Authorization over SSL 
security mechanism configured from NetBeans for my Metro 1.3
WebService.  The idea is that the NameIdentifier of the SAML
Assertion would be used to decide the incoming Caller Principal. 
The sample defines two roles "doctor"and "patient".  Here is how
the web.xml would look like :





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">

   

       

            30

       


   


   

       
index.jsp

       


   

        A
doctor


       
doctor

   


   

        A
patient


       
patient

  


 



In this sample, for the sake of simplicity,  i would just
make use of  explicit principal to role mapping in
sun-web.xml.  This approach is cumbersome and does not scale as
the number of different registered users of a WebService can easily run
into thousands.  Please see the other approaches of doing P2R
Mapping as described by the links in section 3 above. So here is my
sun-web.xml





Application Server 9.0 Servlet 2.5//EN"
"http://www.sun.com/software/appserver/dtds/sun-web-app_2_5-0.dtd">



  /SAMLSSL

 

    doctor

    DrRobert

 


 

    patient

    MrSick

 


 

 

   

      Keep a copy of the
generated servlet class' java code.


   


 






Here is the code that i have implemented inside my SAMLValidator. 
The main logic here is to look for a NameIdentifier with name  style="font-weight: bold;">DrRobert  and only if that is
found i would set a Principal as understood by glassfish.  Note
that the sample uses a Proprietary GlassFish Principal Class. In near
future release of Metro we would add a Metro API that would allow
setting the principal inside the Subject.  The Subject is the one
that the GlassFish Container would use for subsequent authorization
decisions.  Below is the simplistic code of my SAML Validator. In
reality the decision code inside the SAMLValidator can be complex,
making use of other context information and may lookup some databases
etc.



package test;



import com.sun.xml.wss.XWSSecurityException;

import
com.sun.xml.wss.impl.callback.SAMLAssertionValidator.SAMLValidationException;

import com.sun.xml.wss.saml.util.SAMLUtil;

import java.security.Principal;

import java.util.Map;

import java.util.logging.Level;

import java.util.logging.Logger;

import javax.security.auth.Subject;

import javax.xml.stream.XMLStreamException;

import javax.xml.stream.XMLStreamReader;

import org.w3c.dom.Element;

import org.w3c.dom.Node;

import org.w3c.dom.NodeList;



public class MySAMLValidator implements
com.sun.xml.wss.impl.callback.SAMLValidator {



    //It is enough to just handle this new Method

    public void validate(XMLStreamReader arg0, Map arg1,
Subject arg2) throws SAMLValidationException {

        try {

           

           
Element domSamlAssertion = SAMLUtil.createSAMLAssertion(arg0);

            //Do
a SAML:Conditions validation to make sure the SAML assertion is Valid

           
SAMLUtil.validateTimeInConditionsStatement(domSamlAssertion);

           
//get hold of the SAML:NameIdentifier element

           
NodeList nidList =
domSamlAssertion.getElementsByTagNameNS("urn:oasis:names:tc:SAML:1.0:assertion","NameIdentifier");

            Node
nid = null;

            if
(nidList.getLength() > 0) {

               
nid = nidList.item(0);

            }

           
String child = nid.getFirstChild().getNodeValue();

           
//set
the Subject Principal's upon successful validaton.


           
// Set the Principal into the Subject if the NameIdentifier is a Valid
Doctor

           
//TODO: Provider Metro API to create and add a Principal to subject.

            if
(child.startsWith("CN=DrRobert")) {

                  
Principal p = new
com.sun.enterprise.deployment.PrincipalImpl("DrRobert");

                  
arg2.getPrincipals().add(p);

            }



        } catch
(XWSSecurityException ex) {

           
Logger.getLogger(MySAMLValidator.class.getName()).log(Level.SEVERE,
null, ex);

           
throw new SAMLValidationException(ex);

        } catch (XMLStreamException
ex) {

           
Logger.getLogger(MySAMLValidator.class.getName()).log(Level.SEVERE,
null, ex);

           
throw new SAMLValidationException(ex);

        }

    }



    public void validate(Element arg0, Map arg1, Subject
arg2) throws SAMLValidationException {

        throw new
UnsupportedOperationException("SAMLValidator : Operation Should not be
Called1.");

    }

   

    //Older methods which do not take a subject argument

    public void validate(Element arg0) throws
SAMLValidationException {

        throw new
UnsupportedOperationException("SAMLValidator : Operation Should not be
Called2.");

    }

    //Older methods which do not take a subject argument

    public void validate(XMLStreamReader arg0) throws
SAMLValidationException {

        throw new
UnsupportedOperationException("SAMLValidator : Operation Should not be
Called3. Because we overrider the new Methods");

    }

   

}




And here is the Security Policy of my WSIT Configuration File for the
sample  WebService.  Note the configuration of the
SAMLValidator.



 


       

           


               
xmlns:wsaws="http://www.w3.org/2006/05/addressing/wsdl"/>

               


                   


                       


                           


                               


                           


                       


                       


                           


                               


                           


                       


                       


                       


                           


                               


                           


                       


                   


               


               


                   


                       


                   


               


               


                   


                       
sp:IncludeToken="http://schemas.xmlsoap.org/ws/2005/07/securitypolicy/IncludeToken/AlwaysToRecipient">

                           


                               


                           


                       


                   


               
 

             
wspp:visibility="private">

               
classname="test.MySAMLValidator" />
style="font-weight: bold;">
            


           


       


   




And Finally here is the code in my SEI implementation class where i am
querying the Roles of the Incoming Caller :



package test;



import javax.annotation.Resource;

import javax.jws.WebMethod;

import javax.jws.WebParam;

import javax.jws.WebService;

import javax.xml.ws.WebServiceContext;





@WebService()

public class SAMLService {



    @Resource

    private WebServiceContext context;

    /**

     * Web service operation

     */

    @WebMethod(operationName = "operation")

    public String operation(@WebParam(name = "parameter")

    String parameter) {

        //TODO write your
implementation code here:

        Boolean bool =
context.isUserInRole("doctor");

       
System.out.println("context.isUserInRole("doctor")=" + bool);

        Boolean isPatient =
context.isUserInRole("patient");

       
System.out.println("context.isUserInRole("patient")=" + isPatient);

        return "Hello " + parameter;

    }



}

7. SAMPLE WebService Client

The WebSerivce Client would need to use SSL, this can be achieved by
specifying a https url for retrieving the WSDL, when creating the
WebServiceReference within NetBeans (For example, in this sample i
specified, https://somehost:8181/SAMLSSL/SAMLServiceService?wsdl). The
WebService Client would need to implement the SAML CallbackHandler
which would send a SAML Assertion containing NameIdentifier as
"DrRobert".  Here is the code for the Client Side SAML
CallbackHandler, which creates a Dummy SAML Assertion for now. In
reality the CallbackHandler can authenticate to  a Security Token
Service to obtain a Token.



package test;



import com.sun.xml.wss.impl.callback.SAMLCallback;

import com.sun.xml.wss.saml.Assertion;

import com.sun.xml.wss.saml.Conditions;

import com.sun.xml.wss.saml.NameIdentifier;

import com.sun.xml.wss.saml.SAMLAssertionFactory;

import com.sun.xml.wss.saml.Subject;

import com.sun.xml.wss.saml.SubjectConfirmation;

import java.io.IOException;

import java.util.GregorianCalendar;

import java.util.LinkedList;

import java.util.List;



import javax.security.auth.callback.Callback;

import javax.security.auth.callback.CallbackHandler;

import javax.security.auth.callback.UnsupportedCallbackException;

import org.w3c.dom.Element;





public class MySAMLCBH implements CallbackHandler {



    private  UnsupportedCallbackException
unsupported =

           
        new
UnsupportedCallbackException(null,

           
            "Unsupported
Callback Type Encountered");

   

    private  static Element svAssertion = null;

    public static final String senderVouchesConfirmation
=

    "urn:oasis:names:tc:SAML:1.0:cm:sender-vouches";

    

    public void handle(Callback[] callbacks) throws
IOException, UnsupportedCallbackException {

         for (int i=0; i <
callbacks.length; i++) {

            if
(callbacks[i] instanceof SAMLCallback) {

               
try{

                   
SAMLCallback samlCallback = (SAMLCallback)callbacks[i];

                   
if
(samlCallback.getConfirmationMethod().equals(samlCallback.SV_ASSERTION_TYPE)){

                           
samlCallback.setAssertionElement(createSVSAMLAssertion());

                           
svAssertion=samlCallback.getAssertionElement();

                   
}else{

                           
throw new Exception("SAML Assertion Type is not matched.");

                   
}

               
}catch(Exception ex){

                       
ex.printStackTrace();

               
}

            }
else {

               
throw unsupported;

            }

        }

    }

   

    private static Element createSVSAMLAssertion() {

        Assertion assertion = null;

        try {

            //
create the assertion id

           
String assertionID = String.valueOf(System.currentTimeMillis());

           
String issuer = "CN=Assertion Issuer,OU=AI,O=Assertion
Issuer,L=Waltham,ST=MA,C=US";

           

           

           
GregorianCalendar c = new GregorianCalendar();

            long
beforeTime = c.getTimeInMillis();

            //
roll the time by one hour

            long
offsetHours = 60*60*1000;



           
c.setTimeInMillis(beforeTime - offsetHours);

           
GregorianCalendar before= (GregorianCalendar)c.clone();

           

            c =
new GregorianCalendar();

            long
afterTime = c.getTimeInMillis();

           
c.setTimeInMillis(afterTime + offsetHours);

           
GregorianCalendar after = (GregorianCalendar)c.clone();

           

           
GregorianCalendar issueInstant = new GregorianCalendar();

            //
statements

            List
statements = new LinkedList();





           
SAMLAssertionFactory factory =
SAMLAssertionFactory.newInstance(SAMLAssertionFactory.SAML1_1);



           
NameIdentifier nmId =

           
factory.createNameIdentifier(

           
"CN=DrRobert,OU=SU,O=SAML User,L=Los Angeles,ST=CA,C=US",

           
null, "urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName");



           
SubjectConfirmation scf =

           
factory.createSubjectConfirmation("urn:oasis:names:tc:SAML:1.0:cm:sender-vouches");

          

 

           
Subject subj = factory.createSubject(nmId, scf);

          

            List
attributes = new LinkedList();



            List
attributeValues = new LinkedList();

           
attributeValues.add("ATTRIBUTE1");

           
attributes.add( factory.createAttribute(

               
"attribute1",

               
"urn:com:sun:xml:wss:attribute",

                
attributeValues));



           
statements.add(

           
factory.createAttributeStatement(subj, attributes));

           

           
Conditions conditions = factory.createConditions(before, after, null,
null, null);

           

           
assertion = factory.createAssertion(assertionID, issuer, issueInstant,

           
conditions, null, statements);



           
return assertion.toElement(null);

        } catch (Exception e) {

           
throw new RuntimeException(e);

        }

    }



}




Here is the Metro configuration file for the WebSerivce Client
.  Note the configuration of  SAMLCallbackHandler (Note :
this step can be achieved within Netbeans by specifying the name of the
SAML CallbackHandler)



 


       

           


               


                   
classname="test.MySAMLCBH"/>

               


           


       






So when i run the client, here is what is printed out of the Role Query
on the Server (as seen in GlassFish server.log file)



[#|2008-09-15T17:24:13.186+0530|INFO|sun-appserver9.1|javax.enterprise.system.stream.out|_ThreadID=17;_ThreadName=httpSSLWorkerThread-8181-0;|

context.isUserInRole("doctor")=true|#]

[#|2008-09-15T17:24:13.186+0530|WARNING|sun-appserver9.1|javax.enterprise.system.core.security|_ThreadID=17;_ThreadName=httpSSLWorkerThread-8181-0;_RequestID=d360250d-55e8-4c7e-bb6a-08af7722525d;|SEC5052:
null Subject used in SecurityContext construction.|#]

[#|2008-09-15T17:24:13.186+0530|INFO|sun-appserver9.1|javax.enterprise.system.stream.out|_ThreadID=17;_ThreadName=httpSSLWorkerThread-8181-0;|

context.isUserInRole("patient")=false|#]



Anyone interested in the NetBeans Project(s) for the client and server
can contact me (vbkumar.jayanti@sun.com)


Future

We are currently working on Support for Java EE 5 defined 
Declarative Authorization based on Annotations @RolesAllowed, 
@DeclareRoles etc. for Servlet based webservices.  Note that 
Declarative method level authorization using these annotations is
already supported in GlassFish for EJB WebServices.











Related Topics >>

Comments

You could split your processing. The Validation of the SAML assertion IMO should happen inside the Validator so that you can reject an invalid assertion thereby disallowing the invocation of the business method. Since passing over the assertion again inside the business method is expensive, it is best you gather credential information required to authorize the request within the validator.

update?

Hi, you wrote "In near future release of Metro we would add a Metro API that would allow setting the principal inside the Subject. " Has there been an update on this yet, or is still the recommendation using com.sun.enterprise.deployment.PrincipalImpl? Thanks, Martijn

Kumar, just to confirm, in cases where permission to execute a web service call is dependent both on an attribute defined in the SAML token and a value within the SOAP body, the validation should occur within the service implementation bean and *not* the SAMLValidator, correct? For example, if I have a WithdrawMoneyFromAccount web service operation, and if the amount to be withdrawn is greater than $100 the person making the request must have the Manager attribute defined in his SAML token ($100 or less anyone can make the SOAP call), where should this be checked--as far as I can tell, I *cannot* read the SOAP body from within the SAMLValidator, so I will need to read the SAML token within the SIB[1], correct? Thanks! [1] http://weblogs.java.net/blog/kumarjayanti/archive/2007/12/accessing_the_...

I just corrected one mistake in the javadoc. It says the assertion as a DOM Element whereas it should have said " The assertion as an XMLStreamReader" Thanks

The Signature of the SAML Validation Method shows : public void validate(XMLStreamReader arg0, Map arg1, Subject arg2) throws SAMLValidationException So you are expected to throw a new SAMLValidationException whenever the validator finds something wrong with the Assertion. The sample code i have pasted actually throws SAMLValidationException. As for what all things need to be checked, the answer is everything other than the SAML Enveloped Singature. The Enveloped Signature for a HOK assertion is verified by the runtime before it calls the Validator. As for the arguments, here is what the javadocs say : validate void validate(javax.xml.stream.XMLStreamReader assertion, java.util.Map runtimeProps, javax.security.auth.Subject clientSubject) throws SAMLAssertionValidator.SAMLValidationException Parameters: assertion - The assertion as an XMLStreamReader runtimeProps - the runtime properties associated with this request clientSubject - the Subject of the sender which can be updated after validation with principal/credential information Throws: SAMLAssertionValidator.SAMLValidationException The javadocs are pushed here : https://xwss.dev.java.net/servlets/ProjectDocumentList?folderID=5501&exp... If you download xwss-3.0.zip it would also have the Javadoc bundle inside it. I will try and see if i can publish it on the XWSS homepage instead of having people to download the entire xwss bundle. Thanks.

It would be nice Kumar if you would elaborate a bit on the SAMLValidator[1] in a future posting (I also asked Jiandong for help here)--namely, what are the different types of things a SAMLValidator can/should check for, and what are the exceptions that should be thrown (or other processing that needs to be done) for the different types of validation errors, and what processing should be done if everything is OK? (Your example is GlassFish-specific--although I'm not sure what "arg2" represents--but I'm looking for what generally should occur in this method if a SAML token passes validation--is it sufficient to just return with no exceptions thrown?) [1] http://www.nabble.com/Need-SAMLValidator-example-tt20558503.html#a20558503

Sorry for the above mistake... What i mean is need tutorial if can provied on Messege Authorization over SSL Thanks then above tutorial

Wow, the best JAAS tutorial.. I have seen.. Well done. I am actually looking tutorials like this which give brife idea about JAAS and how to authorize is what most of the tutorials lack. I have one question to ask is How do we do authorization using Messege authentication over SSL ?

The NetBeans Projects for the WebService Client and WebService can be downloaded from : Client : https://xwss.dev.java.net/files/documents/4864/114593/SAMLSSLClient.zip Service : https://xwss.dev.java.net/files/documents/4864/114592/SAMLSSL.zip