The web service examples used so far in this book have not attempted to provide any security measures to ensure that they can only be accessed by authorized users, or to provide some level of assurance to the client that the server to which it might be about to pass sensitive information is the one to which it thinks it is connected. At the time of this writing, security for web services is the subject of several in-progress JSRs, such as JSR 105 (XML Digital Signature APIs; see http://jcp.org/jsr/detail/105.jsp) and JSR 106 (XML Digital Encryption APIs; see http://jcp.org/jsr/detail/106.jsp). Until these JSRs are completed and their implementations become part of the Java platform, you can still make use of the authentication mechanisms already provided for HTTP to add a level of security to your service. In this section, you'll see how to configure the client and server parts of the service to use both HTTP basic authentication, which is relatively weak, and HTTPS, which is much more robust but is slightly more difficult to set up.
HTTP basic authentication is a simple mechanism that requires the client to supply a username and password to gain access to a service. The authentication information is encoded and sent in an HTTP header to the server, which can then verify whether the user should have access to the service at the URL specified in the request. Although it is easy to configure basic authentication, it is a very weak mechanism because the data exchanged by the client and server is not encrypted and there is no protection against unauthorized modification. Furthermore, the algorithm used to encode the username and password is trivial, thus making it a simple matter for a snooper to discover what they are. Nevertheless, in an internal corporate network, basic authentication may be sufficient.[13]
[13] Basic authentication is what is happening when you log onto a web site for which you preregister for access to member-only areas. The dialog box that pops up to get your username and password is the browser's way to get the information required for the authentication header in the HTTP request.
To demonstrate how to use basic authentication, we'll add it to the book image web service. Configuring basic authentication requires three steps:
Define the role or roles that are allowed access to some or all of the web service and the set of users that belong to those roles.
Define the URLs within the web service that require protection, and specify which roles should be able to use them.
Configure the web service client to send the appropriate authentication information when accessing the service.
The first step involves setting up authentication information for the web container that hosts the web service. The details of this process for the J2EE 1.4 reference implementation and the Tomcat web container in the JWSDP are covered in Chapter 1, where we added a role called JWSGroup together with a couple of users allowed to access that role.
Defining a new role does not add any protection. To achieve this, it is necessary to include authorization information in the web.xml file for the web service. The authorization information defines which of the web service's URLs are to be protected and which role or roles are to be allowed to access those URLs. Allowing a role to access a URL has the effect of making it possible for all of the users in that role to access the URL, provided that they can authenticate themselves to the web container by supplying the correct password.
The book image web service can be accessed without requiring authentication at almost any URL that starts with http://localhost:8000/BookImageService. In order to illustrate basic authentication, the web.xml file for this service is also configured so that only users in the bookimageservice role can acccess the protected URL http://localhost:8000/BookImageService/basicAuth. The portion of the web.xml file that specifies this restriction is shown in bold in Example 3-18.[14]
[14] The result of this configuration is rather unusual because the web service now has both protected and unprotected URLs, all of which provide access to the same service. In reality, you are most likely to protect all access to the web service by precisely defining which URLs it responds to and mapping them all to the appropriate roles. Here, both a protected and an unprotected service are provided so that we can use it to illustrate the SAAJ APIs without having to first introduce basic authentication.
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/j2ee/dtds/web-app_2_3.dtd"> <web-app> <display-name>SAAJ Book Image Service</display-name> <description>SAAJ Book Image Service</description> <servlet> <servlet-name>BookImageService</servlet-name> <display-name>Servlet for the SAAJ book Image Service</display-name> <servlet-class>ora.jwsnut.chapter3.bookimageservice.BookImageServlet </servlet-class> </servlet> <servlet-mapping> <servlet-name>BookImageService</servlet-name> <url-pattern>/*</url-pattern> </servlet-mapping> <security-constraint> <web-resource-collection> <web-resource-name>SAAJ Book Image Service</web-resource-name> <url-pattern>/basicAuth/*</url-pattern> </web-resource-collection> <auth-constraint> <role-name>JWSGroup</role-name> </auth-constraint> </security-constraint> <login-config> <auth-method>BASIC</auth-method> <realm-name>Book Image Service</realm-name> </login-config> </web-app>
The security-constraint element specifies which URLs are to be protected (using the url-pattern element) and the role or roles allowed access to them (in the auth-constraint element). It is also necessary to specify how the identity of the caller is to be determined. This is achieved by the login-config element, which requires the use of basic authentication.
You can verify that this constraint is active by attempting to access the service with your web browser. With the J2EE 1.4 server running, point your browser at the URL http://localhost:8000/BookImageService. You should see an error page resulting from the fact that the service does not support access using the HTTP GET method. If, however, you use the URL http://localhost:8000/BookImageService/basicAuth, you are instead prompted to enter a username and password. Only after you correctly enter these do you see the same error page. If you are using the JWSDP with the Tomcat web server, use port number 8080 instead of 8000 in these URLs.
When it is run using the Ant target run-client, the Java client for the book image service that we used earlier in this chapter uses the URL http://localhost:8000/BookImageService to access the web service; therefore, it does not need to supply a username and password. The client gets the URL from its command line, so it is possible to arrange for it to access the protected URL http://localhost:8000/BookImageService/basicAuth instead. If you do this, however, you get an exception, since the web server expects to receive a username and a password to validate access to this URL, and refuses access if it does not get them (or if they are incorrect). SAAJ uses a slightly different URL syntax to allow the username and password to be included with the URL, which looks like this:
In the case of the book image service, for a user called JWSUserName with the password JWSPassword, the appropriate URL is:
The Ant project file for this example includes a target that can be used to run the client with this URL. To try it out, open a command window, make chapter3\bookimageservice (relative to the installation directory of the book's example source code) your working directory, and type the command:
ant run-basicauth-client
Since JWSUserName has access to the role JWSGroup, the web container allows the client access to the service, based on the auth-constraint element in the web.xml file. To prove that access by unauthorized users will be refused, change the value of the USERNAME property in the jwsnutExamples.properties file in your home directory so that it does not correspond to a valid user, and then run the example again.
If you need more security than basic authentication can provide (as you almost certainly will if you intend to publish your web service on the Internet), you can arrange for it to be accessible over HTTPS instead of HTTP. By using HTTPS, you ensure that all the data exchanged between the client and the server is encrypted and protected against unauthorized modification. The client can also be sure that the server it is actually connected to is the one that it thinks it is connected to, and that the server itself can be trusted. This assurance is possible because the SSL protocol used by HTTPS delivers the server's certificate to the client. This certificate can be verified against a set of trusted certificates held by the client. The certificate also contains information, such as the server's hostname, which can be checked to ensure that the certificate belongs to the server that sent it.
In order to use HTTPS, you need to have the Java Secure Sockets Extension (JSSE) installed on both the client and server systems. If you are using Java 2 Version 1.4, JSSE is part of the core distribution so you do not need to take any extra steps. Otherwise, you should download and install the latest version of the JSSE from http://java.sun.com before proceeding.
|
The means by which you enable HTTPS support in your web server is vendor-dependent. In this section, we describe how to do this for the Tomcat web server distributed with the JWSDP. If you are using a third-party web server, you need to consult your vendor's documentation for the appropriate procedure. In particular, if you are using the J2EE 1.4 reference implementation instead of the JWSDP Tomcat web server, then you can skip this section because it has HTTPS enabled by default.
In the following descriptions, we use the shorthand ${EXAMPLES_ROOT} to refer to the directory in which you installed the example source code for this book, and we use ${JAVA_HOME} for the installation directory of J2SE. The first step is to create the certificate that the web server sends to any client that connects to it over HTTPS. To create this certificate, open a command window on serverhost, make ${EXAMPLES_ROOT} your working directory, and type the following command (all on one line):
keytool -genkey -alias server -keyalg RSA -keypass changeit -storepass changeit -keystore server.keystore -dname "CN=serverhost"
The certificate is created in a new keystore whose name is given by the -keystore argument, which in this case is stored in the installation directory of the example source code for the sake of convenience. The -storepass argument supplies the password used to access the keystore. Here, we use the default JRE keystore password. You can choose a different one, as long as you supply the same password when configuring the server to access the keystore, as described shortly.
The -dname argument can be used to supply a set of attributes that identify the certificate and its owning organization. Here, we set only the CN attribute, which specifies the name of the host to which the certificate belongs. It is important that you use the correct hostname because the client may extract this attribute and check that it matches the name of the host to which it thinks it is connected.[15]
[15] Obviously, in a real-world environment, it is not a good idea to use CN=localhost, but this might be appropriate for testing purposes.
The keytool command creates a self-signed certificate, which is, strictly speaking, appropriate only for development and testing purposes. In the real world, you should instead apply to a certificate authority for a properly signed server certificate, and then import it into the keystore using the keytool -import argument. For further details on the use of the -import argument (and on the keytool command in general), see Java in a Nutshell, by David Flanagan (O'Reilly).
The second step required to activate HTTPS is to enable it in the Tomcat web server by editing the server.xml file, which you'll find in its conf directory. Open this file in an editor and add the lines shown in bold in the following code section, substituting the pathname of the example source code installation directory in the value of the keyStoreFile attribute:
<Service className="org.apache.catalina.core.StandardService" debug="0" name="Java Web Services Developer Pack"> <Connector className="org.apache.coyote.tomcat4.CoyoteConnector" acceptCount="10" ........> <Factory className="org.apache.catalina.net.DefaultServerSocketFactory" /> </Connector> <!-- Added for SSL --> <Connector className="org.apache.catalina.connector.http.HttpConnector" port="8443" minProcessors="5" maxProcessors="75" enableLookups="false" acceptCount="10" connectionTimeout="60000" debug="0" scheme="https" secure="true"> <Factory className="org.apache.catalina.net.SSLServerSocketFactory" keystoreFile="${EXAMPLES_ROOT}\server.keystore" keystorePass="changeit" clientAuth="false" protocol="TLS"/> </Connector> <!-- End of SSL section -->
Note the following:
SSL is enabled by adding a second Connector element to the Service element for "Java Web Services Developer Pack." There is also another Service element in this file for internal services � make sure you modify the correct
Service
element.
The filename supplied using the keystoreFile attribute must be the name of the keystore in which the certificate was created (or imported) by keytool. Similarly, the correct keystore password must be supplied using the keystorePass attribute.
HTTPS is enabled on port 8443, rather than port 8080. This is one of the two port numbers used by convention for HTTPS; the other is 443 (which, on Unix-based systems, is accessible only to privileged processes).
Having completed these steps, you can check that all is well by restarting the Tomcat web server and pointing your browser at the URL https://serverhost:8443 (note that the protocol is https instead of http and the port number is 8443). Your browser will probably ask you to confirm that you accept the server's certificate and will then display the web server's home page. If you have not obtained and installed a certificate from a certificate authority, you will probably be warned that the certificate is from a company that you have chosen not to trust. There is no need to be concerned about this � it is the result of using a self-signed certificate, which relies upon itself for its trust (and therefore cannot be trusted at all).
Now let's move to the client system. In order to use the book image web service client with HTTPS, you have to give it the appropriate URL. If you want to use basic authentication together with HTTPS, use the following URL:
To use HTTPS on its own, use:
Port number 7000 is used because this is the port on which the J2EE reference implementations listen for HTTPS connections. If you are using the JWSDP with the Tomcat web server, then the port number is 8443 instead of 7000.
When you use either of these URLs, the client connects over HTTPS and expects to receive the server's certificate and validate it. How does the validation work? The complete process is complex and not of any great interest from a web service development viewpoint. However, one of the following two conditions must hold:
The server's certificate must be installed in a keystore on the client machine to which the client application has access.
The server's certificate must be issued by a trusted authority whose certificate is installed in the client machine's keystore. In this case, the certificate itself does not need to be in the client's keystore.
In the first case, the certificate for the issuing authority is almost certainly found in the certificate store that is supplied with the JRE, which can be found at ${JAVA_HOME}\jre\lib\security\cacerts; therefore, there is not any further work to do on the client system. If you created your own self-signed certificate, then you need to import it into a keystore that is accessible to the client.
Although you could import certificates directly into the JRE keystore, we will instead create and use a private keystore in order to demonstrate how simple it is to do this. This also has the advantage that you can experiment with certificates without the possibility of damaging your JRE. On the client machine, copy the cacerts file from the JRE to the installation directory of this book's source code and rename it client.keystore:
copy ${JAVA_HOME}\jre\lib\security\cacerts ${EXAMPLES_ROOT}\client.keystore
Next, if you are using a self-signed certificate, you need to get a copy of it and import it into the newly created keystore; you can skip this step if the server is using a certificate issued by a trusted authority. To get the certificate, go to the server machine and proceed as follows:
If you are using the JWSDP Tomcat web server, go to the directory containing the keystore (server.keystore) created in the previous section and type the command:
keytool -export -alias server -storepass changeit -keystore server. keystore -file server.cer
If you are using the J2EE reference implementation, go to the directory lib\security below the J2EE installation directory and type the command:
keytool -export -alias server -storepass changeit -keystore keystore.jks -file server.cer
Copy the newly created file server.cer from the server machine to the ${EXAMPLES_ROOT} directory on the client machine and import it there using the following command:
keytool -import -v -trustcacerts -alias JWSNutshell -storepass changeit -keystore client.keystore -file server.cer
Reply when asked if you want to trust this certificate. Note that you can use any valid name for the alias, as long as it does not clash with one already in use in the keystore. The fact that you do not have to import a server certificate obtained from a trusted certification authority in this way is a great advantage, of course, because it means that clients that want to connect to your service do not need to get a copy of your certificate in advance.
In order to run the client application with HTTPS, it is necessary to supply the correct URL and arrange for it to use the keystore that has just been created to look for certificates. To point the application at the correct keystore, the two system properties listed in Table 3-4 need to be set.
Property |
Description |
---|---|
javax.net.ssl.trustStore |
The pathname of the keystore. In this case, this is ${EXAMPLES_ROOT}\client.keystore. |
javax.net.ssl.trustStorePassword |
The password needed to access the keystore. By default, this password is changeit. |
A target that runs the web service over HTTPS using basic authentication by setting the appropriate values for both of these properties has been included in the Ant buildfile for this example:
<target name="run-httpsserver-client" if="client.present" depends="init"> <java classname="${CLIENT_CLASS_NAME}" fork="yes"> <sysproperty key="javax.net.ssl.trustStore" value="${EXAMPLES_ROOT}/client.keystore"/> <sysproperty key="javax.net.ssl.trustStorePassword" value="changeit"/> <arg line="${CLIENT_HTTPS_SERVER_AUTH_ARGS}"/> <classpath refid="run.path"/> </java> </target>
The property CLIENT_HTTPS_SERVER_AUTH_ARGS is set using properties in the jwsnutExamples.properties file to the appropriate URL for the service, which in this case is:
(or port 7000 if you are using the J2EE reference implementation).
To use this target, open a command window, make chapter3\bookimageservice (relative to the installation directory of the book's example source code) your working directory, and then type the command:
ant run-httpsserver-client
You should see the application start up and run as usual, although there will probably be a slight delay because of the additional overhead required to set up an HTTPS connection. If you'd like to see the details of the setup process, use the target run-httpsserver-client-debug instead.