Wednesday, November 2, 2011

Propagate user identity in JAX-WS web service on WebSphere using WS-Security

 

Introduction


This article will demonstrate how the user identity (the subject's user principle in java security) of the client can be used for authentication and authorization in a remote web service.

WS-Security is an extension to SOAP and can be used for integrity, confidentiality and authentication, I will focus on the authentication part here, and how the user identity can also be used for authorization in the web service after authentication.

The WS-Security policies are attached to a JAX-WS client or provider after deployment. The developer of the web service does not need to know or be bothered how the authentication is done at runtime. The developer only needs to specify which roles (he defined) can access which pieces of code.

Building a sample


Let's get started with a sample straight away. The RAD 8 project interchange file ("archive", as it is called now) is available at the end of the article.

In RAD 8 we create a new dynamic web project named "MySecureWebService" with all default settings:


We create a new class MyWebServiceImpl in the package arendatwork.wssecurity:


The only method in MyWebServiceImpl is a classic hello world implementation:

public String helloWorld(String name){
    System.out.println("MyWebService says hello " + name);
    return "hello " + name;
}

If we right-click the dynamic web project, we can choose new > other, we can type web service to launch the web service wizard:



The MyWebServiceImpl is the java bean implementation for our bottom up creation.   Be sure to select JAX-WS as the implementation type (in this case my default runtime is WAS 8 and jax-ws is selected by default. If your runtime is WAS7, JAX-RPC is still the default selection).  RAD will publish the web service for you straight away.

Let's create a client!



We will also generate jsp's for the client, therefore we will first create a new dynamic web project to hold these files. Completely similar to creating the first dynamic web project for the web service, we create one for the client, except for the names of the web project (MySecureWebServiceClient) and the enterprise project (MySecureWebServiceClientEAR).
Right click the web service in the enterprise explorer and navigate to generate > client

Slide the slider all the way to the top, up to "Test client", we want those generated jsp files. Also pick the MySecureWebServiceClient project as client project. That way we keep it separate from the project with the web service. Click finish.

If the appserver was already started, RAD will deploy the project. If not, a prompt is shown to start the server. As soon as your test server is running the wizard can finish the deployment of the test client. A browser with the generated test client will open automatically. If not, point your browser to the TestClient.jsp (in my case http://localhost:9080/MySecureWebServiceClient/sampleMyWebServiceImplPortProxy/TestClient.jsp ) If the endpoint is not specified in the test jsp, update it before testing.


On my machine, the endpoint is http://localhost:9080/MySecureWebService/MyWebServiceImplService This can vary depending on your port (server config), context root (project config) and web service name. The input of Arend will result in "hello Arend", while the sysout reads "MyWebService says hello Arend". Perfect!

Adding security



Now we can place a ws-security layer on top of our service. We will specify a policy set that requires an LTPA token as authentication. LTPA tokens are a WebSphere / IBM single sign on mechanism. ( wikipedia link ) The sample policy set bindings of WebSphere already specify how to generate and consume LTPA tokens, so we can reuse those bindings for this article. Log in to the admin console of your WAS environment, and navigate to Services > Policy sets > Application policy sets and click new :


Give "LTPA Only" as name, and add ws-security as policy:
Click on the ws-security policy we just added. Click main policy. In the main policy screen, unmark "Message level protection" and click apply:


then click "request token policies". Here you can add the tokens that are required for the policy set. Let's add LTPA, and give it a name (for example "LTPAToken):


The policy set bindings define how the policy set should be executed at runtime. If the policy set requires an LTPA token in the header, the client binding should define how an LTPA token is generated, and the provider binding knows how to interpret the token. WebSphere 8 has some policy set bindings packaged for both clients and providers. If you navigate in the admin console to Service > Policy sets > General provider policy set bindings you can see the list:


We only require standard WebSphere LTPA token in our ws-security policy, so we can use the "Provider sample" binding for our provider, and the "Client sample" for the client, because these bindings specify how to handle the LTPA tokens (and more). To attach the policy set we created and the sample binding to the provider, navigate to Services > Service providers, and click on our service "MyWebServiceImplService". As you can see you can attach the policy set + binding on different levels - service, port, or method. I'll stick the LTPA Only policy set to the service here, together with de default binding, Sample provider.

 
Save the configuration. After a new policy set has been created, the server has to be restarted to make it usable, so do that before testing.
Important: if you are deploying your applications directly from your eclipse workspace, make sure the publishing settings for your WAS server is set to "Run server with resources on Server". If the "Run server with resources within the workspace" setting is enabled, the policy set bindings do not work! You can attach them to the providers and clients, you can save it, all looks right, but no policy is active at runtime...

Let's run our test client again. You will most likely get a message like this:
CWWSS5514E: An exception while processing WS-Security message: com.ibm.wsspi.wssecurity.core.SoapSecurityException: CWWSS6521E: The Login failed because of an exception: javax.security.auth.login.LoginException: Login Failure: all modules ignored

What happened? Well, we didn't log in to our test client, so no LTPA token is generated! We can quickly setup basic authentication for our test client, so that a user principle is known when the soap message is created.

A requirement for security in your web application, is that global security is enabled in the WAS appserver AND application security is enabled. By right-clicking on the security roles section of the web deployment descriptor, we can add a role "authenticated".



After opening the web deployment descriptor, we can also add security constraints to /*. Only our role "authenticated" can access /*.


It is convenient to map the role "authenticated" directly to "all authenticated users" in the ear file. Right click on the MySecureWebServiceClientEar project, select JAVA EE > generate websphere bindings deployment descriptor. In the META-INF folder of the ear file, ibm-application-bnd.xml is generated. Add the security role "authenticated" and map it to the special subject "all authenticated users":




Now redeploy the MySecureWebServiceClientEar. If it goes right, your browser will prompt for a username and password when you access the test client page. When we hit the web service, we get our response as expected. Authentication is in place!

By adding
   
@Resource
    WebServiceContext context;

to the web service implementation, we can have access to the context, which will hold the userprinciple.
(I also needed to add the @WebService annotation to the class, otherwise the @Resource did not get injected, and context was null)
@WebService
  public class MyWebServiceImpl

We will add this line to our implementation:
System.out.println("MyWebService was called by " + context.getUserPrincipal());

If we login to the test client page with wasadmin, we expect the line "MyWebService was called by wasadmin". After all, we authenticated with wasadmin, right? However, it prints "MyWebService was called by null".

The Caller



The reason why the principal is null, is because we did not specify the Caller in the provider binding. This caller entry tells the appserver which
token should be used for the java security in the application itself. By setting the ltpa token our caller, we can propagate the user identity.
To set the caller, goto Services > Policy sets > General provider policy set bindings , and click on the "Provider sample" binding.
Remember that we used this default policy set binding for our sample. Click on the policy WS-Security, and click Caller:


Click new to create a new caller
Choose a name for your caller (for example "LTPA", it can be anything you like).
For "Caller identity local part" give the value "LTPAv2" and for "Caller identity namespace URI" the value "http://www.ibm.com/websphere/appserver/tokentype".
These are not chosen randomly. They specify which part of the header contains the identity information.



The possible values for the tokentypes out of the box defined in websphere application server, are listed in the infocenter:
http://publib.boulder.ibm.com/infocenter/wasinfo/v7r0/index.jsp?topic=/com.ibm.websphere.express.doc/info/exp/ae/uwbs_wsspsbcald.html

It is possible to define multiple Callers, because it is possible to send different types of tokens. The order of the callers is the order of priority.
As soon as a token in the list of callers is found, that one is used for the identity.


The server should be restarted before we can test the sample, because the policy binding has changed.

"MyWebService was called by wasadmin"

It should work now; the userprinciple can be checked for a certain role.


Sample files


The sample files are available here