Windows Integrated Security and Java Web Applications

In my previous post I was explaining how to use an Active Directory server to authenticate a user. Indeed, I was trying to make the system authenticate the user using the Windows credentials that she already entered when logging onto her workstation.

Some years ago I was working with IIS and it was only a matter of configuration of the server to enable that for browsers that were supporting the appropriate protocol (others would be using HTTP basic).
One of the advantages of that protocol is that the user’s password is never sent over the wire. I found out this protocol is named SPNEGO and is an extension to the HTTP Negotiate protocol.

Since negotiation must occur between the browser and the server, if the server does not natively implement that protocol you cannot use the standard security APIs like custom registries or JAAS.
The solution is then to disable the server standard authentication mechanism and implement a filter that will negotiate, using SPNEGO, with the browser.

In the principle it looks easy but one still need to implement SPNEGO and bridge with Windows, because it’s Windows that finally authenticates the user.

After some goggling I found that the jCIFS library and its extension jCIFS-Ext have the necessary support to help me do the job. In fact everything is already there, even the filter: jcifs.http.AuthenticationFilter.

So first, let’s configure the security constraints for our web-app. In the web.xml we must have the following:

<security-constraint>
    <web-resource-collection>
        <web-resource-name>Any resource</web-resource-name>
        <description>Any resource</description>
        <url-pattern>/*</url-pattern>
    </web-resource-collection>
    <user-data-constraint>
        <transport-guarantee>CONFIDENTIAL</transport-guarantee>
    </user-data-constraint>
</security>

I do not define any role nor any authentication method because I don’t actually want the server to do the authentication by himself. Nevertheless, I define that I want confidentiality on those URLs.
I do that because I will configure my filter to fall-back to HTTP Basic if the browser does not support SPNEGO or HTTP Negotiate and I do not want the password to travel unencrypted on the net.

I hope this imply that if the application is not served over HTTPS there will be a problem, but I actually correctly configured my server to serve the application over HTTPS so I did not test this behaviour.

The second step is to configure the filter itself, the jCIFS-Ext filter has undocumented parameters so I had to go through the code to find them:

<filter>
    <filter-name>AuthenticationFilter</filter-name>
    <display-name>AuthenticationFilter</display-name>
    <description>SPNEGO Authentication Filter</description>
    <filter-class>jcifs.http.AuthenticationFilter</filter-class>
    <init-param>
        <param-name>jcifs.smb.client.domain</param-name>
        <param-value>MyDomain</param-value>
        <description>The name of the Windows domain.</description>
    </init-param>
    <init-param>
        <param-name>jcifs.http.domainController</param-name>
        <param-value>mydomain.com</param-value>
        <description>The address of the Windows
            domain controller.</description>
    </init-param>
    <init-param>
        <param-name>jcifs.http.enableNegotiate</param-name>
        <param-value>true</param-value>
        <description>If the browser does not support SPNEGO,
            fallback to HTTP Negotiate.</description>
    </init-param>
    <init-param>
        <param-name>jcifs.http.enableBasic</param-name>
        <param-value>true</param-value>
        <description>If the browser does not support SPNEGO
            nor HTTP Negotiate, fallback to HTTP Basic
            but only if the connection is secure.</description>
    </init-param>
    <init-param>
        <param-name>jcifs.http.insecureBasic</param-name>
        <param-value>false</param-value>
        <description>Never fallback to HTTP Basic when the
            connection is insecure.</description>
    </init-param>
    <init-param>
        <param-name>jcifs.http.basicRealm</param-name>
        <param-value>mydomain</param-value>
        <description>The name of the domain in case of
            HTTP Basic authentication.
            Used only for display to the user.</description>
    </init-param>
</filter>
<filter-mapping>
    <filter-name>AuthenticationFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

“Et voilà“, now your application should automatically authenticate the user based on its Windows credentials. I said should because there are some prerequisites:

  • on the browser side, Windows integrated security must be enabled
  • on the server side your platform must actually support Kerberos for the filter to properly work.

However, the former is a matter of configuration and the latter is a matter of slightly changing the code of the filter.

Configuring an Internet Explorer Browser

To configure an Internet Explorer browser to use Windows authentication, follow these procedures in Internet Explorer:

  1. Configure Local Intranet Domains
    1. In Internet Explorer, select Tools > Internet Options.
    2. Select the Security tab.
    3. Select Local intranet and click Sites.
    4. In the Local intranet popup, ensure that the “Include all sites that bypass the proxy server” and “Include all local (intranet) sites not listed in other zones” options are checked.
    5. Click Advanced.
    6. In the Local intranet (Advanced) dialog box, add all relative domain names that will be used for Integrator server instances participating in the SSO configuration (for example, myhost.example.com) and click OK.
  2. Configure Intranet Authentication
    1. Select Tools > Internet Options.
    2. Select the Security tab.
    3. Select Local intranet and click Custom Level…
    4. In the Security Settings dialog box, scroll to the User Authentication section.
    5. Select Automatic logon only in Intranet zone. This option prevents users from having to re-enter logon credentials, which is a key piece to this solution.
    6. Click OK.
  3. Verify the Proxy Settings (If you have a proxy server enabled)
    1. Select Tools > Internet Options.
    2. Select the Connections tab and click LAN Settings.
    3. Verify that the proxy server address and port number are correct.
    4. Click Advanced.
    5. In the Proxy Settings dialog box, ensure that all desired domain names are entered in the Exceptions field.
    6. Click OK to close the Proxy Settings dialog box.
  4. Set Integrated Authentication for Internet Explorer 6.0 (In addition to the previous settings, one additional setting is required if you are running Internet Explorer 6.0)
    1. In Internet Explorer, select Tools > Internet Options.
    2. Select the Advanced tab.
    3. Scroll to the Security section.
    4. Make sure that Enable Integrated Windows Authentication option is checked and click OK.
    5. If this option was not checked, restart the computer.

Despite all of this configuration I encountered some cases where this was not working at all in IE and I was unable to spot the problem, so you might be falling into this category. The symptoms are that the negociation process takes place but the browser does not answer the last challenge and no error message is displayed at all.

Configuring a Mozilla Firefox Browser

To configure an Mozilla Firefox browser to use Windows authentication, follow these procedures in Mozilla Firefox:

  1. Type about:config in the address bar of the browser and press return (a big list of properties should be displayed in the browser window).
  2. Type “network” in the filter box.
  3. Double-click on the network.automatic-ntlm-auth.trusted-uris property and enter “mydomain.com” (if there is already a value you can add a comma to separate both entries)

The value for this preference is a comma-separated list of URI fragments. This sample string shows the three legal kinds of fragments: https://, http://www.example.com, test.com

The first fragment says, “Trust all URLs with an https scheme.” The second fragment (a full URL) says, “Trust this particular web site.” The third fragment is interpreted to mean http://anything.test.com, so any web site that is a subdomain of test.com, including test.com itself, will also be trusted.

I did not encounter any problem with Firefox which is what I call a paradox…

Changing the filter to use NTLM instead of Kerberos

Actually the change must not occur in the filter but in the class jcifs.spnego.Authentication which comes with jCIFS-Ext. This class tries to determine if the system supports Kerberos but uses introspection, looking for some Java classes that enable Kerberos support in Java.
Nevertheless, those classes can be there without the actual system supporting Kerberos (which is the case where I work).

Fortunately, modifying the behaviour is not too much complicated, just change line 57 of this class:

private static final boolean KERBEROS_SUPPORTED = getKerberosSupport();

to the following:

private static final boolean KERBEROS_SUPPORTED = false;

And then the filter will use NTLM instead of Kerberos.

I hope the next posts will be shorter :-P

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.