posts - 23,comments - 12,trackbacks - 0
A common requirement of websites on corporate Intranets is NTLM HTTP authentication also sometimes referred to as Single Sign On (SSO). Microsoft Internet Explorer has the ability to negotiate NTLM password hashes over an HTTP session using base 64 encoded NTLMSSP messages. This is a staple feature of IIS but Java application servers too can use jCIFS to authenticate MSIE clients against a domain controller. This is a useful feature because many of the tasks surrounding user management now fall back to computer support and HR. It is not necessary to add and remove users as they join and leave the company. Perhaps most important from a user's perspective; they do not need to enter a username or password if their workstation is a member of the domain. The password hashes generated when they logged on to their workstation will be negotiated during the initial request for a session, passed through jCIFS, and validated against a PDC or BDC. This also makes the users domain, username, and password available for managing session information, profiles, preferences, etc. Using the jCIFS Servlet Filter it is trivial to add NTLM HTTP authentication support to your site. It is also possible to build custom authentication modules using the NtlmSsp classes directly. This Filter scales very well primarily because sessions are multiplexed over transports. But this functionality is not without caveats. Note: This functionality is a non-conformant extension to HTTP conceived entirely by Microsoft. It inappropriately uses HTTP headers and therefore may not work with all Servlet containers or may stop working with a new release of your application server. Also, this flavor of password encryption is not very secure so under no circumstances should it be used to authenticate clients on the Internet.

Note: Don't forget to restart the container after changing jCIFS init-parameters. JCIFS must use the container class loader and jCIFS properties are only read once when jCIFS classes are initialized.

Installation and Setup

Put the latest jCIFS jar file in the lib/ directory of your webapp [1]. Because jCIFS properties are loaded once when the jCIFS classes are first accessed, it is necessary to actually stop and restart the container if any jCIFS properties have been changed. Below are two web.xml configurations. Note that the jcifs.smb.client.domain and jcifs.smb.client.domainController properties are mutually exclusive.

Production web.xml Example

A minimalistic web.xml file with filter and filter-mapping directives might look like the following:
<filter>
    <filter-name>NtlmHttpFilter</filter-name>
    <filter-class>jcifs.http.NtlmHttpFilter</filter-class>

    <init-param>
        <param-name>jcifs.smb.client.domain</param-name>
        <param-value>NYC-USERS</param-value>
    </init-param>
    <init-param>
        <param-name>jcifs.netbios.wins</param-name>
        <param-value>10.169.10.77,10.169.10.66</param-value>
    </init-param>
</filter>

<filter-mapping>
    <filter-name>NtlmHttpFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>
This filter section illustrates the setup for authenticating clients against the domain NYC-USERS. This is suitable for large numbers of concurrent users as jCIFS will cycle through domain controllers and use an alternate WINS server if necessary.

The above will authenticate users accessing all content against the domain NYC-USERS. The WINS server 10.169.10.77 will be queried to resolve NYC-USERS to an IP address of a domain controller. If that WINS server is not responding, 10.169.10.66 will be queried.

Alternate web.xml Example

The below example filter section illistrates how to specify the IP address of the domain controller specifically using the jcifs.http.domainController property. The target machine does not need to be a real domain controller -- it could be just a workstation. Also illustrated below is the jcifs.smb.client.logonShare property. This will cause jCIFS to attempt to access the resource \\192.168.2.15\JCIFSACL when authenticating users. By creating that share and changing the Access Control List only certain users or groups of users will have access to your website.
<filter>
    <filter-name>NtlmHttpFilter</filter-name>
    <filter-class>jcifs.http.NtlmHttpFilter</filter-class>

    <init-param>
        <param-name>jcifs.http.domainController</param-name>
        <param-value>192.168.2.15</param-value>
    </init-param>
    <init-param>
        <param-name>jcifs.smb.client.logonShare</param-name>
        <param-value>JCIFSACL</param-value>
    </init-param>
</filter>

<filter-mapping>
    <filter-name>NtlmHttpFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>
This filter section illustrates the setup for authenticating against a JCIFSACL share for testing or a site with a small number of concurrent users (e.g. 1000)

Either a jcifs.smb.client.domain or jcifs.smb.client.domainController property is required. This will be suitable to authenticate clients that are members of the specified domain as well as other domains with which it has trusts relationships.

Running the NtlmHttpAuthExample.java example should be a suitable test of the Filter.

NTLM HTTP Authentication Example

NYC-USERS\MIALLEN successfully logged in

Please submit some form data using POST

field1 = hello
The significance of the POST test is that after negotiating NTLM HTTP Authentication once, IE will not POST any form data until it has negotiated the password hashes again.

If the NTLM HTTP Authentication Filter is not enabled something like the following will be displayed:
null successfully logged in
Notice the user was permitted access. Unlike this example, developers might add an additional check to make sure getRemoteUser does not return null.

Non MSIE Clients and "Basic" Authentication

NTLM HTTP authentication is only supported by MSIE transparently. Mozilla 1.6 supports NTLM HTTP negotiation but it will always prompt the user for credentials by default (although the Mozilla documentation on Integrated Authentication describes how to make it transparent to the user for trusted sites). For other clients it is possible to use basic authentication to pass NTLM password credentials. This is strongly discouraged if SSL is not being used because it sends these credentials in plain text. It would not be difficult for another user to download and install a program to "snoop" LAN traffic and obtain other user's passwords.

Regardless, this functionality has been added to the NtlmHttpFilter and NtlmServlet (for pre 2.3 servlet containers) although it is disabled by default. To enable this capability set the jcifs.http.basicRealm, jcifs.http.enableBasic, and jcifs.http.insecureBasic properties described in the table below.

JCIFS Properties Meaningful to NTLM HTTP Authentication

All parameters that begin with 'jcifs.' will be set as jCIFS properties which means that any jCIFS properties may be used as init parameters. These properties must be set before jCIFS classes are used. For a complete list of jCIFS properties refer to the overview page of the API documentation. Here is a select subset of jCIFS properties with additional notes in the context of NTLM HTTP Authentication.

jcifs.smb.client.domain The NT domain against which clients should be authenticated. Generally it is necessary to also set the jcifs.netbios.wins parameter or a domain controller may not be found. This parameter will be ignored for NTLM HTTP authentication purposes if a jcifs.http.domainController property is specified (although they can be used together for "preauthenctication" as described in the SMB Signatures and Windows 2003 section below).
jcifs.http.domainController The IP address of any SMB server that should be used to authenticate HTTP clients with the NtlmHttpFilter class. If this is not specified the jcifs.smb.client.domain 0x1C NetBIOS group name will be queried. If these queries fail an UnknownHostException will be thrown. It is not necessary for this to specify a real domain controller. The IP address of a workstation will do for simple purposes.
jcifs.http.basicRelm The realm for basic authentication. This property defaults to 'jCIFS'.
jcifs.http.enableBasic Setting this property to true enables basic authentication over HTTPS only.
jcifs.http.insecureBasic Setting this property to true enables basic authentication over plain HTTP. This configuration passes user credentials in plain text over the network. It should not be used in environment where security is required.
jcifs.http.loadBalance If a jcifs.smb.client.domain property is specified (and domainController is not specified) the NtlmHttpFilter will query for domain controllers by name. If this property is true the Filter will rotate through the list of domain controllers when authenticating users. The default value is true. The jcifs.netbios.lookupRespLimit property can also be used to limit the number of domain controllers used.
jcifs.netbios.lookupRespLimit The 0x1C NetBIOS name query returns a list of domain controllers. It is believed that the servers at the top of this list should be favored. This property limits the range of servers returned by name queries. The default value is 5 meaning the top 5 domain controllers will be used.
jcifs.netbios.wins The IP address of the WINS server. This is required when accessing hosts on different subnets (like a domain controller by name) and it is highly recommended if a wins server is available.
jcifs.smb.client.laddr The ip address of the local interface the client should bind to if it is different from the default. For example if jCIFS is used to authenticate clients on one interface and the domain controller for those clients is accessible only on another interface of a webserver with two NICs it may be necessary to specify which interface jCIFS should use.
jcifs.netbios.laddr The ip address of the local interface the client should bind to for name queries if it is different from the default. Likely set to the same as the above property.
jcifs.smb.client.attrExpirationPeriod Attributes of a file are cached for attrExpirationPeriod milliseconds. The default is 5000 but the NetworkExplorer servlet will attempt to set this property to 120000. Otherwise, when listing large directories, the attributes of SmbFiles may expire within the default period resulting in a large number of additional network messages and severe performance degradation.
jcifs.smb.client.soTimeout To prevent the client from holding server resources unnecessarily, sockets are closed after this time period if there is no activity. This time is specified in milliseconds. The default is 15000 however when NTLM HTTP Authentication is used, the NtlmHttpFilter will attempt to set this value to 5 minutes so that frequent calls to SmbSession.logon() do not provoke redundant messages being submitted to the domain controller. If it is not desirable to cache password hashes set this value back to 15000.
jcifs.netbios.cachePolicy When a NetBIOS name is resolved with the NbtAddress class it is cached to reduce redundant name queries. This property controls how long, in seconds, these names are cached. The default is 30 seconds, 0 is no caching, and -1 is forever. When NTLM HTTP Authentication is used, NtlmHttpFilter will attempt to set this value to 20 minutes so that frequent queries for a domain controller will be cached.

Must Restart The Container

If you change any jcifs properties or replace an existing jcifs jar file with a different one, the container must be restarted. This is because most jcifs properties are retrieved only once when classes are first loaded.

Tomcat

Tomcat requires that all filter directives be adjacent to one another, all filter-mapping directives appear adjacent to one another, all servlet directives ... and so on. This is because Tomcat validates the web.xml against the deployment descriptor DTD.

MalformedURLException: unknown protocol: smb

If you get the following exception try upgrading to jcifs-0.7.0b12 or later. Also read the FAQ.
Exception MalformedURLException: unknown protocol: smb
      at java.net.URL.(URL.java:480)
      at java.net.URL.(URL.java:376)
      at java.net.URL.(URL.java:330)
      at jcifs.smb.SmbFile.(SmbFile.java:355)
      ...

Transparent Authentication and the Network Password Dialog

If the Filter is working properly the Network Password Dialog should never appear. However there are several requirements that must be met for a web browser to transparently negotiate credentials using NTLM HTTP authenication. If any of these requirements are not met, the default behavior is to present the user with the Network Password dialog. The requirements are:

  1. The client must be logged into the Windows NT domain identified by the jcifs.smb.client.domain parameter (or the domain of the host identified by the jcifs.smb.client.domainController parameter if it is used instead). The client may also be logged into a domain that has a trust relationship with the target domain. Indeed it is not uncommon to configure workstations to join a different domain from those of users. Note that Windows 95/98/ME systems cannot really join a domain but can be configured to do so enough to participate in transparent NTLM HTTP authentication.
  2. Only Internet Explorer will negotiate NTLM HTTP authentication transparently. Mozilla will always prompt the user for credentials (someone please notify us when/if this is not true anymore). At the time this FAQ was written it was not known which other browsers, if any, can negotiate NTLM HTTP authenication transparently.
  3. Either the target URL must contain a server in the local domain (e.g. ws1.mycompany.com) or the client's security settings must be changed (e.g. Tools > Internet Options > Security > Local Intranet > Sites > Advanced > add your site). If the URL does not contain a URL in the defined IntrAnet zone (e.g. not an IP address), Internet Explorer will assume that the server is in the IntErnet zone and present the user with the Network Password dialog. It would be very bad if a server on the Internet could convince IE to send it your NTLM password hashes. These hashes are easily cracked with brute force dictionary attacks. To prevent this scenario, IE tries to distinguish between Intranet sites and Internet sites. Here are some important notes to consider when deploying a site with NTLM HTTP Authentication regardless of whether or not jCIFS is used to do it.
  4. The user's credentials must be valid. For example if the account has expired, been disabled or is locked out the Network Password dialog will appear. To determine which error was at fault it will be necessary to modify the NtlmHttpFilter to inspect the SmbAuthException in doFilter.
  5. The jCIFS client must support the lmCompatibility level necessary for communication with the domain controller. If the server does not permit NTLMv1 try to set jcifs.smb.lmCompatibility = 3.

Personal Workstation AD Security Policy

If your Active Directory security policy requires that users only log into the domain from their personal workstations JCIFS will fail to authenticate and the server security log will have entries like "\\JCIFS10_40_4A cannot be authorized". This occurs because the domain controller is failing to resolve the dynamically generated "calling name" submitted by the client during protocol negotiation. To get around this it is necessary to set the jcifs.netbios.hostname property to a valid NetBIOS name that can be resolved by the NetBIOS name service (e.g. WINS) and add that name to the AD security policy as a permitted client.

For example, you can set this property using an init-paremeter in the web.xml file for the NTLM HTTP filter as follows:
<init-parameter>
    <parameter-name>jcifs.netbios.hostname</parameter-name>
    <parameter-value>MYHOSTNAME</parameter-value>
</init-parameter>

HTTP POST and Protecting Sub-Content

Once IE has negotiated NTLM HTTP authentication it will proactively renegotiate NTLM for POST requests for all content associated with the server (based on IP?). Therefore when using HTTP POST requests it is not possible to restrict access to some content on the server as IE will attempt and fail to negotiate NTLM (standard IE error page?). This is a protocol limitation and there does not appear to be a way to convince IE to stop proactively negotiating for POST requests once it has been determined that the server is capable of negotiating NTLM authentication.

SMB Signatures and Windows 2003

If the domain controller against which you are authenticating clients requires SMB signatures (Windows 2003 does by default), it is recommended that you provide init-parameters for the jcifs.smb.client.{domain,username,password} to perform "preauthentication" for each transport to a domain contoller so that a proper SMB signing key will be generated. In fact, this may be necessary for proper operation; it has been observed that NT 4.0 does not check the signatures of authentication requests but the behavior has not been confirmed in all environments. If the Filter works for the first authentication but fails with a second user shortly thereafter this would suggest that signing was established but subsequent authentications are failing due to the lack of a good signing key. Additionally, without a proper signing key certain requests (SMB_COM_TREE_DISCONNECT and SMB_COM_LOGOFF_ANDX) will cause signature verification failures (although they are harmless to the client).

A third solution for signature issues is to change the jcifs.smb.client.ssnLimit to 1. This will require that every authentication uses a separate transport. Because the MAC signing key is only used on SMB communication occuring after the initial authentication, signing will be effectively ignored. However, this solution will significantly reduce scalability as each authentication will open it's own transport. For this reason the second solution of using a "workstation account" to preauthenticate transports is considered the superior method and should be used by default for servers that requires signatures.

NTLM HTTP Authentication Protocol Details

The NTLM HTTP Authentication process is described well in these documents:

http://davenport.sourceforge.net/ntlm.html
http://www.innovation.ch/java/ntlm.html

The process can be summarized as a 3 request/response "handshake". So doGet() will be called three times. The first is the initial request. A 401 Unauthorized is sent back to which IE submits a special message encoded in a header. Another 401 Unauthorized is sent back after which IE submits the password hashes. This is where jCIFS comes in. The password hashes alone are useless. You must check their authenticity against the password database on a server somewhere (actually you can specify the IP of a plain workstation or any other SMB server). Otherwise a user who's workstation is not a member of the domain will get a password dialog into which they could put anything and it would let them in. This is what pretty much all the examples seen in various forums do. Don't be fooled.

[1] Due to restrictions in how protocol handlers are loaded, if the SMB URL protocol handler is to be used (meaning you want to access SMB resources with smb:// URLs) within your application it is necessary for the jCIFS jar to be loaded by the System class loader. This can usually be achived by placing it in the container lib/ directory. However, for containers that load servlet classes in a child classloaders (Tomcat) this too will cause problems as jCIFS will not be able to load javax.servlet.* classes. To get the filter and the URL protocol handler to operate together requires some experimentation and depends on the container being used.
posted on 2005-08-20 13:45 my java 阅读(2822) 评论(0)  编辑  收藏 所属分类: java身份认证转帖

只有注册用户登录后才能发表评论。


网站导航: