In the sample code presented so far, you've used Web services to enable connectivity between a service that was published in either .NET or Java, and you then saw how clients could consume that service.
However, if you used these samples in a production environment, you'd probably need to think about some kind of authentication. Chances are, unless you were making a public Web service, you'd want to know who was using the service and for what purpose. Ideally, you need to authenticate each of the clients that are consuming Web services and configure authorization to allow access only to certain individuals and/or groups.
One of the easiest ways of doing this is to take advantage of the underlying transport that Web services rely on. In all the samples that you've seen, HTTP has been the transport used to communicate between the client and server components . HTTP has a framework that allows usernames, passwords, and domains to be used for authenticating the connection. Web services published using either .NET or GLUE can take advantage of HTTP authentication.
By viewing the properties in IIS for a .NET Web service, you can see how this type of authentication can be applied. Figure 6.3 shows the properties dialog box of the virtual directory used for the .NET Web service that was built and hosted in Chapter 5. (You can reach this properties dialog box by launching Internet Information Services Manager, navigating through the Web Sites tree to the dotNETWebService directory, and then right-clicking and selecting the Properties option.)
If you didn't create the dotNETWebService Web service in the previous chapter, I recommend that you do this now because in this section we'll apply security settings based on that example. Use the instructions in the "Creating the Web Service Using the NAnt Script" section of the previous chapter to do this.
Within the authentication dialog box, you can set the authentication methods for the directory, specify restrictions based on IP address or domain name , and secure the virtual directory by using Secure Sockets Layer (SSL). Here, we'll change the authentication method and controls for the current directory in order to show how this affects both .NET and Java calling clients.
Clicking the Edit button in the Authentication And Access Control panel (which is named Anonymous Access And Authentication Control in Microsoft Windows XP Professional) shows the authentication mechanisms that are in place for the directory. By default, as shown in Figure 6.4, the directory is configured for anonymous access ”which allows anyone to call the Web service without being challenged for credentials. The Anonymous Access option links to an account that's automatically created on the system when IIS is installed. (The account used by default will be IUSR_MACHINENAME , where MACHINENAME is the host name of the server.) Any operation that's performed by the Web service runs under the context of this user account.
Now let's assume that to use the sample code into a production environment, we'll want to restrict the Web service to authenticated clients. To do this, you have a number of ways of securing the virtual directory, including these:
Integrated Windows authentication Can be used to validate the access against a local Windows store and works well if no suitable access to a Windows domain exists.
Digest authentication An authentication method that's compatible for Windows servers participating in an Active Directory setup.
Basic authentication As the name suggests, this is the most basic form of authentication and one in which the username and password are passed in clear text.
.NET Passport authentication Allows users to use existing Passport accounts to authenticate against resources on the server. This option isn't available for the Windows XP Professional operating system.
For additional information on these types of authentication, consult the documentation that accompanies your installation of IIS. If the Help files are installed, this can normally be accessed by navigating to http://localhost/iishelp .
I'm frequently asked how to authenticate against an existing Lightweight Directory Access Protocol (LDAP) directory. From my experience working with customer projects, I've found that native LDAP directories (such as Sun ONE Directory Server) tend to be more common in environments that have applications based on J2EE. The authentication options exposed via the IIS authentication console do not natively support a type of connection to a native LDAP store.
Although a number of third-party implementations can be used to get around this, one successful way to solve this is with the inclusion of a standalone Active Directory server. Active Directory supports LDAP v3.0. As a result, one of the options of integrating an Active Directory domain into an existing environment can be to use LDAP replication methods to take data from one LDAP directory into Active Directory.
As shown in Figure 6.5, this can be a read-only copy of the existing directory, but once the replication is complete, IIS can integrate with the Active Directory server, recognizing the directory as a valid domain.
Let's now take a look at what's required to secure a Web service hosted in NET. In the properties dialog box of the dotNETWebService virtual directory, disable anonymous authentication and enable basic authentication, as shown in Figure 6.6. In addition, set the realm to BasicRealm . This will be required when you test the Java client. Accept the warning that indicates that passwords will be sent as clear text, and click OK to commit the change.
Setting these credentials for each of the clients allows them to access the Web service, based on having an account either on the local machine or possibly in a directory.
To further refine the security settings, you might want to select certain individuals or groups within your organization who will be either granted or denied access. To configure this, edit the web.config file for the Web service. This configuration file supports a section for authorization and enables you to allow or deny users based on username or group . This authorization typically maps to the local users on a machine or to an active directory within the enterprise.
<system.web> <authorization> <allow users="Administrator"/> <allow users="MYDOMAIN\Simon"/> <deny roles="MYDOMAIN\PublicUsers"/> </authorization> </system.web>
Feel free to adjust any of these settings to test various authorization scenarios applicable to your own environment. When testing the sample code, however, the default settings are fine.
To test that the Web service is secure ”so that it will allow only authenticated clients to connect ”you can now run the existing .NET and Java clients to see how this configuration affects them.
Before you do this, however, consider how this will affect the build processes. In Chapter 5, both the .NET and Java clients used an Ant/NAnt script to compile the code and to run the WSDL tools in order to generate the proxy. Because you've applied security at the transport level, this can cause problems. And because the Web services have been secured at the transport level, it's no longer possible to view the WSDL contract without supplying some form of credentials. The WSDL is critical in generating the proxy files for the client; therefore, you must supply valid credentials to the build scripts.
To do this, edit the default.build file in the C:\Interoperability\Samples\Point\WebServices\dotNET\dotNETClient.Nant directory (for the .NET client) and the build.xml file in the C:\Interoperability\Samples\Point\WebServices\dotNET\JavaClient directory (for the Java client). For each of the build scripts, locate the username, password, and domain properties toward the top of the build file:
<property name="Username" value="Username"/> <property name="Password" value="Password"/> <property name="Domain" value="Domain"/>
For each of these settings, set the values to the credentials of a user who'll be able to call the Web service ”for example, a user in the domain or a user in a group (if authorization was applied in the web.config file). If no domain is present in your environment, you should use the machine name instead for the Domain property.
The NAnt/Ant build files are valid XML documents. Therefore, if you have a password that includes one of the five XML entity characters (', ", <, >, and &), you must replace the entity character with the corresponding XML entity reference; otherwise , the build script will not parse correctly.
Once applied, save the build scripts and rebuild both the clients to ensure that the proxy files are created successfully.
Making the modification to the build script will allow the WSDL tools to generate the proxy, but when the clients run, you should still experience what happens when an unauthenticated Web services client tries to connect.
To test this, from a command prompt, navigate to the directory that contains the .NET client (C:\Interoperability\Samples\Point\WebServices\dotNET\dotNETClient.Nant). From there, run the client. An exception should be raised:
Unhandled Exception: System.Net.WebException: The request failed with HTTP status 401: Unauthorized.
If you're testing this using Windows XP Professional, you'll see an exception of the same type, although the text will be slightly different. Second, try the same thing by launching the Java client in the C:\Interoperability\Samples\Point\WebServices\dotNET\JavaClient directory. You'll see an exception similar to the following:
Exception in thread "main" electric.registry.RegistryException: could not bind to path: http://localhost/dotNETWebService/StockService.asmx?WSDL
Now that you've secured the .NET Web service, both the .NET client and the Java client fail to authenticate because they are not passing any authentication details to the .NET Web service. This failure appears as a different exception type for each of the two platforms.
We need to apply some settings so that both clients are able to authenticate. But before we do that, let's look at securing a Java Web service.
You might have tried accessing the published WSDL of the .NET Web service ( http://localhost/dotNETWebService/StockService.asmx?WSDL ) through Microsoft Internet Explorer instead of via our two clients. If you did this, you no doubt noticed that you were prompted for credentials within the browser.
You of course can configure similar authentication and authorization requirements for the Java Web service. Because you don't rely on an underlying Web server or administration tool when using GLUE, configuration is performed via a configuration file or in the class that publishes the Web service. The rest of this section outlines the basic principles involved. In practice, however, be sure to consult the GLUE documentation for the complete details.
In GLUE, both authentication and authorization of Web services are performed by selecting one of three realms: Basic Realm , ACLRealm , or JAASRealm . A Basic Realm is simply the place where the username, password, and domain are stored in memory. All information stored in a Basic Realm is lost after the realm is destroyed . An ACLRealm allows authentication information to be stored in an XML file, local to the application itself. This access control list (ACL) XML file takes on the following format:
<acl> <!--list of user/password/role entries--> <user> <name>admin</name> <password>changeme</password> <role>administrator</role> </user> </acl>
Notice how the name, password, and an optional role are used with the file to provide credentials at run time. An ACLRealm can hold 0 or more role entries for each user.
The final realm, JAASRealm, allows authentication against any implementation of the Java Authentication and Authorization Service (JAAS) standard login module ( javax.security.auth.LoginModule ). Many JAAS modules are available for enterprise directories, including LDAP and Active Directory. Using a third-party JAAS module, you can enable authentication based on the same directory as the one used in the .NET sample shown earlier.
The easiest realm to configure is the ACLRealm, and it is also the default. The authentication section in the config.xml file declares an ACLRealm named acl . This realm specifies an acl.xml file that is loaded from the WEB-INF\security directory of a deployed application:
<realm> <constructor> <class>electric.security.acl.ACLRealm</class> <args> <name>acl</name> <!-- path can be relative to WEB-INF\ or absolute --> <path>security\acl.xml</path> </args> </constructor> </realm>
Once a realm has been decided upon, the process of actually locking down the Web service is configured either through modification of the config.xml file or by using a number of runtime APIs when the Web service is published. This process of locking down the Web service is performed by using what's known as a guard .
Within the service descriptor, the guard is configured by setting the < authenticate/ > element and uses the < role > element to specify the roles that are allowed to access the service. (Recall how a number of roles can be configured in the acl.xml file.)
To enable the guard as part of the runtime APIs, a guard property can be set as part of the HTTP context. (As you'll see shortly, enabling the guard in this manner uses the same process as the client in order to set the username and password.)
httpContext.setProperty("guard", new Role("administrators"));
Now that you've seen how Web services can be configured to prevent unauthorized access, let's examine how to add authentication credentials to both the .NET and Java clients (to allow them to call the Web service again).
In .NET, you can add manual credentials as part of the Web service call by setting the Credentials property in the Web service declaration. For example:
FinancialServices.StockService ss = new FinancialServices.StockService(); ss.Credentials = new System.Net.NetworkCredential("Username", "Password", "Domain");
You set the credentials to a new instance of System.Net.NetworkCredential and pass the username, password, and domain name that you want to use. (The domain name is optional for accounts that are on the same machine.)
Besides specifying the credentials manually, by configuring integrated authentication (in addition to the basic authentication option within IIS), you have the option of using default credentials ” that is, instructing the .NET client to use the credentials of the user who is currently logged on to the Windows operating system. After setting integrated authentication within the properties of a virtual directory in IIS, you can specify this with the following code:
FinancialServices.StockService ss = new FinancialServices.StockService(); ss.Credentials = System.Net.CredentialCache.DefaultCredentials;
This can be particularly useful for .NET Windows Forms and console applications because it allows authentication to be passed through, based on the credentials of the logged-on user.
For applications that require interoperability between .NET and Java, however, be aware that using integrated authentication and the default credentials just shown will work only for a .NET client calling a .NET Web service. Because this is the case, it can be worth setting both basic and integrated authentication options within the virtual directory properties in IIS to allow for a potential mix of .NET and Java clients.
Using GLUE, authentication details are passed from the Java client to the .NET Web service via a similar method. First, a new context is created, the credentials are applied, and this context is passed as a parameter to the bind method:
import electric.util.Context; Context context = new Context(); context.setProperty("authUser", "domain\username"); context.setProperty("authPassword", "password"); String url = "http://localhost/dotNETWebService/StockService.asmx?WSDL"; IStockServiceSoap FinancialServices = (IStockServiceSoap)Registry.bind(url, IStockServiceSoap.class, context);
The domain, username, and password are set within the authUser and authPassword fields of the context. This context is then passed to the Registry.bind method.
This section has shown how HTTP authentication can be applied to a Web service and how .NET and Java clients can use basic authentication in order to access these Web services. For the .NET client, we were also able to use the credentials of the logged-on user when accessing a .NET Web service.
HTTP authentication for Web services is certainly not without its problems. First, the username and password are passed in clear text between the client and the service ”meaning that anyone with access to a network analyzer could potentially pick up the credentials on the wire. This can be overcome by using HTTPS instead, but doing so offers only point-to-point security for Web services.
In Chapter 13, "Web Services Interoperability, Part 1: Security," we'll look at how a new specification known as WS-Security can be used to secure a Web service at a message level (as opposed to the transport level).