WSO2 AS JAX-RS

From EWIKI
Jump to navigation Jump to search

To implement a REST service with WSO2 Application Server is easy, if you use WSO2 Developer Studio.

I found no example with authentication and authorization in annotation style. The service is based on Apache CXF. So here is what I found to do...

REST Service

ref: Jersey User Guide

Create an Eclipse Project

In WSO2 Studio do:

  1. File > New > other
  2. WSO2 > Service Hosting > Project Types > JAX-RS Service Project
  3. Choose "Create a new JAX-RS Service"
  4. Sert project name, package name and class name (=service endpoint)
  5. Click "Finish"

Minimum Service Implementation

Then implement a service class (unsecured public accessible), e.g.:

 package de.mh.crm;

 import javax.ws.rs.*;
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;

 @Path("/ServiceCatalog")
 @Produces("application/json")
 public class ServiceCatalog {

	@GET	
	public Response getCatalog() {
		Catalog productCatalog = new Catalog();
		productCatalog.getItemList().add( new CatalogItem( "Product ABC" ) );
		productCatalog.getItemList().add( new CatalogItem( "Product XY" ) ); 
		return Response.ok().entity( productCatalog ).type( MediaType.APPLICATION_JSON ).build();
	}
	
 }

Some important things for coding the result class:

  1. The root value class must be annotated as XmlRootElement -- even if Eclipse gives a warning like
    Access restriction: The type XmlRootElement is not accessible due to restriction on required library /usr/lib/jvm/java-7-openjdk-i386/jre/lib/rt.jar
  2. properties are only serialized, when they have getters and setters
 import javax.xml.bind.annotation.XmlRootElement;

 @XmlRootElement
 public class Catalog {
      ...
 }

Build it

Create the WAR (web archive) using the WSO2 Studio by right clicking the project ad select "Export the project as a deployable archive".

BTW: The WAR is a ZIP file with the general layout:

index.html
*.jsp
WEB-INF/
    web.xml
    cxf-servlet.xml
    lib/
          *.jar
    classes/
          .../*.class
META-INF/
    MANIFEST.MF
    webapp-classloading.xml
images/


In the carbon console you can nor load the WAR using Applications > Add > Jax-WS/JAX-RS.

TODO: Automate build and deployment in WSO AS.

Security Set Up

  1. Get WSO2 Application Server (v5.2.0)

LDAP Set Up

Create sample structure:

dn: ou=groups,dc=example,dc=com
changetype: add
objectClass: organizationalUnit
objectClass: top
ou: groups
#
dn: ou=people,dc=example,dc=com
changetype: add
objectClass: organizationalUnit
objectClass: top
ou: people
#
dn: cn=mh,ou=people,dc=example,dc=com
changetype: add
objectClass: inetOrgPerson
objectClass: organizationalPerson
objectClass: person
objectClass: top
sn: Markus
cn: mh
#
dn: cn=mh,ou=people,dc=example,dc=com
changetype: modify
add: uid
uid: mh
-
#
dn: cn=mh,ou=people,dc=example,dc=com
changetype: modify
add: userPassword
userPassword:: ..........
-
#
dn: cn=ServiceAdmins,ou=groups,dc=example,dc=com
changetype: add
objectClass: groupOfUniqueNames
objectClass: top
uniqueMember: cn=mh,ou=people,dc=example,dc=com
cn: ServiceAdmins

Set up LDAP as user store in WSO2 AS

File: wso2as-5.2.0/repository/deployment/server/userstores/example.xml

 <?xml version="1.0" encoding="UTF-8"?><UserStoreManager class="org.wso2.carbon.user.core.ldap.ReadWriteLDAPUserStoreManager">
     <Property name="ConnectionName">uid=admin,ou=system</Property>
     <Property name="ConnectionURL">ldap://localhost:10389</Property>
     <Property name="ConnectionPassword">......</Property>
     <Property name="UserSearchBase">ou=people,dc=example,dc=com</Property>
     <Property name="Disabled">false</Property>
     <Property name="UserNameListFilter">(objectClass=person)</Property>
     <Property name="UserNameAttribute">uid</Property>
     <Property name="UserNameSearchFilter">(&(objectClass=person)(uid=?))</Property>
     <Property name="UserEntryObjectClass">inetOrgPerson</Property>
     <Property name="GroupEntryObjectClass">groupOfNames</Property>
     <Property name="ReadGroups">true</Property>
     <Property name="GroupSearchBase">ou=Groups,dc=wso2,dc=org</Property>
     <Property name="GroupNameAttribute">cn</Property>
     <Property name="GroupNameListFilter">(objectClass=groupOfUniqueNames)</Property>
     <Property name="MembershipAttribute">member</Property>
     <Property name="GroupNameSearchFilter">(&(objectClass=groupOfUniqueNames)(cn=?))</Property>
     <Property name="MaxUserNameListLength">100</Property>
     <Property name="MaxRoleNameListLength">100</Property>
     <Property name="UserRolesCacheEnabled">true</Property>
     <Property name="SCIMEnabled">false</Property>
     <Property name="PasswordHashMethod">SHA</Property>
     <Property name="UserDNPattern">uid={0},ou=Users,dc=wso2,dc=org</Property>
     <Property name="PasswordJavaScriptRegEx">^[\S]{5,30}$</Property>
     <Property name="UserNameJavaScriptRegEx">^[\S]{3,30}$</Property>
     <Property name="UserNameJavaRegEx">[a-zA-Z0-9._-|//]{3,30}$</Property>
     <Property name="RoleNameJavaScriptRegEx">^[\S]{3,30}$</Property>
     <Property name="RoleNameJavaRegEx">[a-zA-Z0-9._-|//]{3,30}$</Property>
     <Property name="WriteGroups">true</Property>
     <Property name="EmptyRolesAllowed">true</Property>
     <Property name="DomainName">example</Property>
     <Property name="Description"/>
 </UserStoreManager>

Security Implementation

WARNING: Currently this example does not work!! Don't know why :-(

CXF Security Interceptor Configuration

Add bold parts in src/main/webapp/WEB-INF/cxf-servlet.xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" 
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jaxrs="http://cxf.apache.org/jaxrs" 
  xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd http://cxf.apache.org/bindings/soap http://cxf.apache.org/schemas/configuration/soap.xsd http://cxf.apache.org/jaxrs http://cxf.apache.org/schemas/jaxrs.xsd">
    
    <bean id="CrmSelfServiceBean" class="com.tsystems.concar.crm.CrmSelfService"/>
    
    <bean id="authorizationInterceptor" class="org.apache.cxf.interceptor.security.SecureAnnotationsInterceptor">
        <property name="securedObject" ref="CrmSelfServiceBean" />
    </bean>
    
    <jaxrs:server id="CrmSelfService" address="/crm_self_service">
       <jaxrs:inInterceptors>
           <ref bean="authorizationInterceptor" />
       </jaxrs:inInterceptors>
       <jaxrs:serviceBeans>
           <ref  bean="CrmSelfServiceBean"/>
       </jaxrs:serviceBeans>
    </jaxrs:server>
</beans>

TODO: is aJAASLoginInterceptor configuration additionally required?

Maven Dependenciey for Annotations

Extend pom.xml by:

    <dependency>
	  <groupId>javax.annotation</groupId>
	  <artifactId>jsr250-api</artifactId>
	  <version>1.0</version>
    </dependency>

Implementing an Endpoint Authorization

Now you can use @RolesAllowed this in the Java service class:

   @POST	
   @RolesAllowed("catalog-admin")
   public Response addToCatalog( CatalogItem newItem  ) {
       ...
   }

Adding roles to web.xml

...
<security-constraint>
  <web-resource-collection>
     <web-resource-name>cxf services</web-resource-name>
     <url-pattern>/services/*</url-pattern>
  </web-resource-collection>
  <auth-constraint>
     <role-name>catalog-admin</role-name>
  </auth-constraint>
</security-constraint>
 
<login-config>
     <auth-method>BASIC</auth-method>
     <realm-name>myservice</realm-name>
</login-config>
...

Build

Unfortunately the WSO2 Studio will ignore with the additional dependency. So you have to build the WAR on the command line:

mvn package

First Test

Added "test" user to the WSO2 AS and a group "catalog-admin" with "test" as member.

  • Calling via GET the result is OK
  • Calling via POST and Authorization=Basic I get a

Warning: If you use a RESTclient as browser plug in and you are logged in into the Carbon console in another tab, the BasicAuth credentials are mixed up and you may get the wrong login, even if BasicAuth HTTP header attribute is explicitly set.

Dead ends (and errors)

I also tried to add a JAASLoginInterceptor to the cxf-servlet.xml

   ...
   <bean id="authenticationInterceptor" class="org.apache.cxf.interceptor.security.JAASLoginInterceptor">
          <property name="contextName" value="jaasContext"/>
   </bean> 
 
   <jaxrs:server id="CrmSelfService" address="/crm_self_service">
       <jaxrs:inInterceptors>
          <ref bean="authenticationInterceptor" />
          <ref bean="authorizationInterceptor" />
       </jaxrs:inInterceptors>
       ...
   </jaxrs:server>


... now I get: <ns1:XMLFault xmlns:ns1="http://cxf.apache.org/bindings/xformat"><ns1:faultstring xmlns:ns1="http://cxf.apache.org/bindings/xformat">java.lang.SecurityException: C:\Java\WSO2AS~1.1\bin\..\repository\conf\security\jaas.conf (Datei oder Verzeichnis nicht vorhanden)</ns1:faultstring></ns1:XMLFault>

Untried Options

  1. oAuth
    1. CXF OAuth 1.0 extension
    2. JAX-RS: OAuth2 i.e. "Protecting resources with OAuth filters"
    3. globus-oauth-demo project
  2. Don't know if this is a good architecture: write my own basic auth interceptor
  3. This shoud work, but it is oversized: XACML Fine Grained Authorization
  4. Security Token Service:
    1. The Web Services Trust Model (WS-Trust)

Remote Deployment

ref WSO2 Admin Services Client

Links