Sunday, December 16, 2012

Using WS-Policy in CXF projects


WS-Policy provides flexible mechanism to activate desired functionality on the client or service sides. Article describes how to define policies in custom CXF projects, implement policy-aware interceptors and explains some aspects of internal CXF design regarding WS-Policy.


How to define policies

There are basically 3 main possibilities to define WS-Policy in CXF projects:
  1. WSDL Policy attachment
  2. Spring configuration
  3. Dynamically via message context property
Let look into them in details.

WSDL Policy attachment
WS-Policies can be attached and referenced in WSDL elements. Web Services Policy 1.5 - Attachment standard describes all possible alternatives. WS-Policies can be placed inside WSDL itself or referenced as external documents. CXF will automatically recognize, read and use policies defined or referenced in WSDL. Sample of attached policy is shown below:
<wsdl:definitions name="HelloWorld" targetNamespace="http://apache.org/hello_world_soap_http"<wsdl:service name="SOAPService">
    <wsdl:port binding="tns:Greeter_SOAPBinding" name="SoapPort">
        <soap:address location="http://localhost:9000/SoapContext/SoapPort"/>
        <wsp:Policy xmlns:wsp="http://www.w3.org/ns/ws-policy">
             <wsam:Addressing xmlns:wsam="http://www.w3.org/2007/02/addressing/metadata">
                 <wsp:Policy/>
              </wsam:Addressing>
         </wsp:Policy>
    </wsdl:port>
</wsdl:service>
</wsdl:definitions>  

Spring configuration
It is possible to define policies directly in Spring configuration of client and service as jaxws feature. CFX will recognize and use configured WS-Policies:
Client:
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:jaxws="http://cxf.apache.org/jaxws"
       xmlns:cxf="http://cxf.apache.org/core"
       xmlns:p="http://cxf.apache.org/policy"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="
http://cxf.apache.org/core http://cxf.apache.org/schemas/core.xsd
http://cxf.apache.org/policy http://cxf.apache.org/schemas/policy.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"
http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd">
    <jaxws:client id="CRMServiceClient" name="{http://services.talend.org/CRMService}CRMServiceProvider"
            xmlns:serviceNamespace="http://services.talend.org/CRMService"
            serviceClass="org.talend.services.crmservice.CRMService"
            serviceName="serviceNamespace:CRMServiceProvider"
            endpointName="serviceNamespace:CRMServicePort"
            address="${endpoint.prefix}/CRMServiceProvider">
            <jaxws:features>
                <p:policies>
                    <wsp:PolicyReference xmlns:wsp="http://www.w3.org/ns/ws-policy" URI="classpath:/saml.policy"/>
                </p:policies>
            </jaxws:features>
    </jaxws:client>
</beans>
Service:
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:jaxws="http://cxf.apache.org/jaxws"
       xmlns:cxf="http://cxf.apache.org/core"
       xmlns:p="http://cxf.apache.org/policy"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="
http://cxf.apache.org/core http://cxf.apache.org/schemas/core.xsd
http://cxf.apache.org/policy http://cxf.apache.org/schemas/policy.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"
http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd">
    <jaxws:endpoint id="CRMService"
            xmlns:serviceNamespace="http://services.talend.org/CRMService"
            serviceName="serviceNamespace:CRMServiceProvider"
            endpointName="serviceNamespace:CRMServicePort"
            implementor="#CRMServiceBean"
            address="/CRMServiceProvider">
            <jaxws:features>
                <p:policies>
                    <wsp:PolicyReference xmlns:wsp="http://www.w3.org/ns/ws-policy" URI="classpath:/saml.policy"/>
                </p:policies>
            </jaxws:features>
    </jaxws:endpoint>
</beans>
Dynamically through message property
Sometimes policies cannot be configured statically, because they are obtained or calculated dynamically for concrete message (for example using Policy Server or Service Registry). For such cases CXF provide a possibility to load policy dynamically and set it into the message context property. It can be done for example in custom interceptor that fulfils the following:
  1. Get policy from external location and build it for current message.
  2. Parse WS-Policy XML using Neethi library.
  3. Store result Policy object into PolicyConstants.POLICY_OVERRIDE message content property.
    Important is that this custom policy interceptor is called before CXF PolicyInInterceptor or PolicyOutInterceptor. Than CXF will automatically recognize Policy stored into this property and use it with highest priority. 
 I have published a small sample illustrating how to apply policy dynamically.

Create custom policy assertions and associate interceptors

It is quite easy to define own policy assertions and associate interceptors with it. Topic is already well described in CXF document http://cxf.apache.org/docs/developing-assertions.html, I just provide a list of steps necessary to do:
  1. Provide Assertion Builder class for custom assertion implementing AssertionBuilder<T> interface.
    Interface type can be Element, XMLStreamReader or OMElement.
    Interface contains two methods: build() and getKnownElements().
    Implementation of build() method should construct Assertion from the incoming type. It can be PrimitiveAssertion (without attributes or child elements), NestedPrimitiveAssertion (without attributes but with nested policy element) and JaxbAssertion (assertion described by any XML schema).
    getKnownElements() method must return QNames of assertion elements from which assertion can be built.
  2. Implement policy interceptor provider class extending AbstractPolicyInterceptorProvider class. The main task of policy interceptor provider is to say which interceptors must be activated for specified policy assertion. Policy interceptor provider constructor gives assertions QNames as argument of super constructor and adds corresponded interceptors using getters:
public class AuthorizationInterceptorProvider extends AbstractPolicyInterceptorProvider {
    private static final long serialVersionUID = -5248428637449096540L;
    private static final AuthorizationInInterceptor IN_AUTHZ_INTERCEPTOR = new AuthorizationInInterceptor();
    private static final AuthorizationInInterceptor OUT_AUTHZ_INTERCEPTOR = new AuthorizationOutInterceptor();
    private static final Collection<QName> ASSERTION_TYPES;
    static {
        ASSERTION_TYPES = new ArrayList<QName>();
        ASSERTION_TYPES.add(AuthorizationConstants.AUTHORIZATION_ASSERTION);
    }
    public AuthorizationInterceptorProvider() {
        super(ASSERTION_TYPES);
        getInInterceptors().add(IN_AUTHZ_INTERCEPTOR);        
        getOutInterceptors().add(OUT_AUTHZ_INTERCEPTOR);        
    }
}

Assertion builder and policy interceptor provider can be registered using CXF bus extension mechanism: just create a file META-INF/cxf/bus-extensions.txt containing the following:
org.company.AuthorizationInterceptorProvider::true
org.company.AuthorizationAssertionBuilder::true  
Boolean value at the end specifies lazy loading strategy.
CXF automatically recognizes the assertion builder and policy interceptor provider and store them into registries: AssertionBuilderRegistry and PolicyInterceptorProviderRegistry. Since CXF 2.6.0 it is possible to register multiple interceptor providers for single assertion.

How and where CXF processes policies

As I already mentioned, CXF provides two interceptors: org.apache.cxf.ws.policy.PolicyInInterceptor and org.apache.cxf.ws.policy.PolicyOutInterceptor. These interceptors are responsible to load policy from destination, parse, merge them and add all associated interceptors into message interceptor chain. Functionality of policy interceptors are represented on the following figure:


Briefly, policy interceptors make following steps:
  1. Check message property PolicyConstants.POLICY_OVERRIDE.
  2. If PolicyConstants.POLICY_OVERRIDE contains policy, it will be taken for further processing.
  3. If property is empty, policy will be asked from ServiceModel. Here CXF loads policies attached to WSDL or provided via Spring configuration.
  4. If any policy on step 2 or step 3 is found, EffectivePolicy will be created. Appropriate WS-policies will be merged for the current message and built into Neethi Policy object.
  5. All interceptors registered for result policy assertions will be added to message interceptor chain.
Additionally, CXF verifies satisfied policy assertions in PolicyVerfificationInInterceptor, PolicyVerificationInFaultInterceptor, PolicyVerificationOutInterceptor. If assertion is not processed and not satisfied in corresponded interceptor, than In- interceptors throw Fault and Out- interceptors provide appropriate log messages.
The practical using of WS-Policy is illustrated in ws_policy and ws_security CXF samples.