Sunday, October 17, 2010

Securing JAX-WS web services in Eclipse

In this tutorial, we're going to secure the web service created in previous blogposts. If you don't have done that already, please go through the previous two blogposts, because this blogpost will build on the web service created there. We're using container managed security to achieve security. The tutorial consists of three parts. In the first part, we will implement BASIC authentication. In the second part we extend BASIC authentication with Secure Socket Layer (SSL). In the third part, we replace BASIC authentication with mutual authentication using certificates.

PART 1: BASIC authentication

Steps:

  1. Create realm users
  2. Modify server application for BASIC authentication
  3. Modify client application for BASIC authentication

1. Create realm users
In this step we're creating users that are authorized to use the web service. The steps described here are only applicable for the Glassfish application server. I will describe the steps needed for the JBoss application server in a future blogpost.

First, make sure Glassfish is running, and go to the admin console or interface. Use the menu tree on the left to browse to: Security -> Realm -> file



Now in the next screen, click on "Manage Users".



This will take you to the screen "File Users". Click on "New..." to create a new user. In this example, create an user "peter" in the group "wsusers". The password is "qwerty1".



Finally, click "Finish". Now, we have completed creating an user in Glassfish.

2. Modify server application for BASIC authentication
In this step, we have to modify two files: web.xml and sun-web.xml. If you don't have these files, just create them under the WEB-INF directory. In the web.xml file, we protect the web service by adding: a security constraint, login configuration, and roles. To do this, we add the following code directly nested in <web-app>:

<!-- Security constraint for resource only accessible to role -->
<security-constraint>
<display-name>WebServiceSecurity</display-name>

<web-resource-collection>
<web-resource-name>Authorized users only</web-resource-name>
<url-pattern>/ExampleWSService</url-pattern>
<http-method>POST</http-method>
</web-resource-collection>

<auth-constraint>
<role-name>user</role-name>
</auth-constraint>

</security-constraint>

<!-- BASIC authorization -->
<login-config>
<auth-method>BASIC</auth-method>
</login-config>

<!-- Definition of role -->
<security-role>
<role-name>user</role-name>
</security-role>

In the file sun-web.xml, we map the user group created in the application server to the role "user". Put the following code directly under <sun-web-app>:

<security-role-mapping>
<role-name>user</role-name>
<group-name>wsusers</group-name>
</security-role-mapping>

At this point, you can deploy the application and test it with the web service client. If correct, a 404-authorization error will be displayed when the client tries to connect to the web service.

3. Modify client application for BASIC authentication
In this step, we're going to supply an username and password when connecting to a web service in the client. The altered start() method in the file WebServiceClient.java should look like this:

public void start() {
// This statement is not needed when run in container.
service = new ExampleWSService();
ExampleWS port = service.getExampleWSPort();

// Authentication
BindingProvider bindProv = (BindingProvider) port;
Map<String, Object> context = bindProv.getRequestContext();
context.put("javax.xml.ws.security.auth.username", "peter");
context.put("javax.xml.ws.security.auth.password", "qwerty1");

System.out.println(port.greet("Peter"));
System.out.println(port.multiply(3, 4));
}

If we run the client now, we notice that we can successfully connect to the web service without getting authorization errors.

PART 2: Implementing Secure Socket Layer (SSL)

Now, we continue to add Transport Level Security (TLS) using SSL to encrypt the transportation channel. This will prevent eavesdropping and tampering communication data between client and server.

Steps:

  1. Export server certificate from server keystore
  2. Create client truststore and import server certificate
  3. Modify server application for SSL
  4. Modify client application for SSL

1. Export server certificate from server keystore
Open a command prompt and navigate to the root directory of the client. Create a new directory called "server" with the command "mkdir server" and navigate into it. This will be our working directory when manipulating the server keystore/truststore. Copy the following files from the Glassfish_installation/domains/domain1/config directory to the newly created server directory: cacerts.jks (truststore) and keystore.jks (keystore).

Now, we can use the keytool to export the server certificate from the server keystore using the default store password "changeit":

keytool -export -alias s1as -keystore keystore.jks
-storepass changeit -file server.cer

The exported server certificate is now written to the file server.cer.

2. Create client truststore and import server certificate
The next step is to create the client truststore and import the server certificate to it. Create a "client" directory in the root directory of the client, and navigate to it using the command prompt. Now use the keytool to create the client truststore and import the server certificate:

keytool -import -v -trustcacerts -alias s1as -keypass changeit
-file ../server/server.cer -keystore client_cacerts.jks
-storepass changeit

When prompted to trust the certificate, type "yes" and press enter to accept.


3. Modify server application for SSL
Add the following code under the <security-constraint>-element in web.xml to enable SSL:

<user-data-constraint>
<transport-guarantee>CONFIDENTIAL</transport-guarantee>
</user-data-constraint>

When you run the client now, you'll get a 302-error, indicating that the resource has moved.

4. Modify client application for SSL
Edit ExampleWSService.java (generated by wsimport) and lookup the WSDL-URL in the code. Replace the URL with:

https://localhost:8181/webservice/ExampleWSService?wsdl

Save and use the following VM parameters to run the client:

-Djavax.net.ssl.trustStore=client/client_cacerts.jks
-Djavax.net.ssl.trustStorePassword=changeit

You can configure the VM parameters in Eclipse here: Run -> Run Configurations



PART 3: Mutual authentication

In the final part, we're replacing the BASIC authentication with mutual authentication with certificates.

Steps:

  1. Create client keystore
  2. Export client certificate
  3. Import client certificate into server truststore
  4. Modify server application for mutual authentication
  5. Modify client application for mutual authentication
  6. Configure Glassfish for mutual authentication


1. Create client keystore
Now, create a client keystore to store its own certificate.

keytool -genkey -alias client -keypass changeit
-storepass changeit -keystore client_keystore.jks

Use the following values to answer questions when prompted:



2. Export client certificate
Navigate to the "client" directory with the command prompt. The client certificate is needed on the server side. So we need to export it from the client keystore using the following command:

keytool -export -alias client -keystore client_keystore.jks
-storepass changeit -file client.cer


3. Import client certificate into server truststore
Navigate to the "server" directory and import the client certificate with the command:

keytool -import -v -trustcacerts -alias client
-keystore cacerts.jks -keypass changeit
-file ../client/client.cer

Use the default password "changeit" when asked, and finally type "yes" to accept the certificate.

4. Modify server application for mutual authentication
The file web.xml has to be changed for mutual authentication configuration. Lookup the <auth-method>-element and replace BASIC with CLIENT-CERT.

The sun-web.xml file has also to be modified. Instead of mapping the role to a realm group, we have to map the role to a certificate with the <principal-name>-element:

<sun-web-app>
<security-role-mapping>
<role-name>user</role-name>
<!-- <group-name>wsusers</group-name> -->
<principal-name>CN=Name, OU=Department,
O=Organization, L=City, ST=State,
C=nl</principal-name>
</security-role-mapping>
</sun-web-app>


5. Modify client application for mutual authentication
The username and password we used in the client code for part 1 are not used anymore. So we can remove that part.

The client is now finished, and can be run with the following VM-settings:

-Djavax.net.ssl.keyStore=client/client_keystore.jks
-Djavax.net.ssl.keyStorePassword=changeit




6. Configure Glassfish for mutual authentication
If you experience connection problems when using the client, you're probably using the default Glassfish installation. By default, mutual authentication is disabled. To enable mutual authentication, go to: Configuration -> Network Listeners -> http-listener-2. Click the SSL tab. Make sure "SSL3" is not checked. The checkboxes "TLS" and "Client Authentication" should be checked.



Everything should be working now. In a future blogpost, I will show you how to configure realm users for JBoss.

8 comments:

  1. Thanks a lot for this tutorial. Everything is very well explained. But, I have a problem with the third part. It does not work. Should the cacerts.jks not be copied back to the glassfish/domain/domain1/config directory when the client key is added to it. Maybe I am wrong because it still didn't work for me. What do I wrong?

    ReplyDelete
  2. The cacerts.jks file is the truststore located in the directory you mentioned. So, if you import the client key to it, it should work.

    ReplyDelete
  3. Thanks Cheng. Yes, but if I just import it to the cacerts.jks located in the server directory and not in the cacerts.jks in the glassfish config directory the certificate is missing and therefore it would not be able to work (am I right). I also tried out so many things. What I recognized is you mentioned not to check SSL3. I have now SSL3 checked but I removed the security-role-mapping in the sun-web.xml and the and part and now it works. Do I miss something?
    Thanks a lot.

    ReplyDelete
  4. What I wanted to say is that I remove the security-role-mapping in the sun-web.xml and the auth-constraint and the security-role part in the web.xml and now it works. so I copied the cacerts.jks with the imported client.cer key to the glassfish config location overwrited the original cacerts.jks. seams to work no.

    ReplyDelete
  5. Mutual authentication means if you're connected (using the keys) you are authenticated both ways. So BASIC authentication is not needed, which means you have to remove the roles in you web.xml.

    If you import the client certificate directly into the truststore in domain1/config, you don't have to overwrite it. I assume you made a copy of it and imported the client certificate into the copy.

    ReplyDelete
  6. Thanks for tutorial.
    but i've had error when create file web.xml and adding row in file sun-web.xml
    what is the solution??
    thank you very much

    ReplyDelete
  7. thanks Cheng a lot for this article. I tried the client and server but on different machines. The glassfish server certificate is self-signed. I import it into the client truststore as you said. When I run my test client.jar, it comes with an error: "PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target"

    Do you have any ideas?

    ReplyDelete