001 /** 002 * Copyright (C) 2009 "Darwin V. Felix" <darwinfelix@users.sourceforge.net> 003 * 004 * This library is free software; you can redistribute it and/or 005 * modify it under the terms of the GNU Lesser General Public 006 * License as published by the Free Software Foundation; either 007 * version 2.1 of the License, or (at your option) any later version. 008 * 009 * This library is distributed in the hope that it will be useful, 010 * but WITHOUT ANY WARRANTY; without even the implied warranty of 011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 012 * Lesser General Public License for more details. 013 * 014 * You should have received a copy of the GNU Lesser General Public 015 * License along with this library; if not, write to the Free Software 016 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 017 */ 018 019 package net.sourceforge.spnego; 020 021 import java.io.FileNotFoundException; 022 import java.io.IOException; 023 import java.net.URISyntaxException; 024 import java.security.PrivilegedActionException; 025 import java.util.logging.Logger; 026 027 import javax.security.auth.login.LoginException; 028 import javax.servlet.Filter; 029 import javax.servlet.FilterChain; 030 import javax.servlet.FilterConfig; 031 import javax.servlet.ServletException; 032 import javax.servlet.ServletRequest; 033 import javax.servlet.ServletResponse; 034 import javax.servlet.http.HttpServletRequest; 035 import javax.servlet.http.HttpServletResponse; 036 037 import org.ietf.jgss.GSSException; 038 039 /** 040 * Http Servlet Filter that provides <a 041 * href="http://en.wikipedia.org/wiki/SPNEGO" target="_blank">SPNEGO</a> authentication. 042 * It allows servlet containers like Tomcat and JBoss to transparently/silently 043 * authenticate HTTP clients like Microsoft Internet Explorer (MSIE). 044 * 045 * <p> 046 * This feature in MSIE is sometimes referred to as single sign-on and/or 047 * Integrated Windows Authentication. In general, there are at least two 048 * authentication mechanisms that allow an HTTP server and an HTTP client 049 * to achieve single sign-on: <b>NTLM</b> and <b>Kerberos/SPNEGO</b>. 050 * </p> 051 * 052 * <p> 053 * <b>NTLM</b><br /> 054 * MSIE has the ability to negotiate NTLM password hashes over an HTTP session 055 * using Base 64 encoded NTLMSSP messages. This is a staple feature of Microsoft's 056 * Internet Information Server (IIS). Open source libraries exists (ie. jCIFS) that 057 * provide NTLM-based authentication capabilities to Servlet Containers. jCIFS uses 058 * NTLM and Microsoft's Active Directory (AD) to authenticate MSIE clients. 059 * </p> 060 * 061 * <p> 062 * <b>{@code SpnegoHttpFilter} does NOT support NTLM (tokens).</b> 063 * </p> 064 * 065 * <p> 066 * <b>Kerberos/SPNEGO</b><br /> 067 * Kerberos is an authentication protocol that is implemented in AD. The protocol 068 * does not negotiate passwords between a client and a server but rather uses tokens 069 * to securely prove/authenticate to one another over an un-secure network. 070 * </p> 071 * 072 * <p> 073 * <b><code>SpnegoHttpFilter</code> does support Kerberos but through the 074 * pseudo-mechanism <code>SPNEGO</code></b>. 075 * <ul> 076 * <li><a href="http://en.wikipedia.org/wiki/SPNEGO" target="_blank">Wikipedia: SPNEGO</a></li> 077 * <li><a href="http://www.ietf.org/rfc/rfc4178.txt" target="_blank">IETF RFC: 4178</a></li> 078 * </ul> 079 * </p> 080 * 081 * <p> 082 * <b>Localhost Support</b><br /> 083 * The Kerberos protocol requires that a service must have a Principal Name (SPN) 084 * specified. However, there are some use-cases where it may not be practical to 085 * specify an SPN (ie. Tomcat running on a developer's machine). The DNS 086 * http://localhost is supported but must be configured in the servlet filter's 087 * init params in the web.xml file. 088 * </p> 089 * 090 * <p><b>Modifying the web.xml file</b></p> 091 * 092 * <p>Here's an example configuration:</p> 093 * 094 * <p> 095 * <pre><code> <filter> 096 * <filter-name>SpnegoHttpFilter</filter-name> 097 * <filter-class>net.sourceforge.spnego.SpnegoHttpFilter</filter-class> 098 * 099 * <init-param> 100 * <param-name>spnego.allow.basic</param-name> 101 * <param-value>true</param-value> 102 * </init-param> 103 * 104 * <init-param> 105 * <param-name>spnego.allow.localhost</param-name> 106 * <param-value>true</param-value> 107 * </init-param> 108 * 109 * <init-param> 110 * <param-name>spnego.allow.unsecure.basic</param-name> 111 * <param-value>true</param-value> 112 * </init-param> 113 * 114 * <init-param> 115 * <param-name>spnego.login.client.module</param-name> 116 * <param-value>spnego-client</param-value> 117 * </init-param> 118 * 119 * <init-param> 120 * <param-name>spnego.krb5.conf</param-name> 121 * <param-value>krb5.conf</param-value> 122 * </init-param> 123 * 124 * <init-param> 125 * <param-name>spnego.login.conf</param-name> 126 * <param-value>login.conf</param-value> 127 * </init-param> 128 * 129 * <init-param> 130 * <param-name>spnego.preauth.username</param-name> 131 * <param-value>Zeus</param-value> 132 * </init-param> 133 * 134 * <init-param> 135 * <param-name>spnego.preauth.password</param-name> 136 * <param-value>Zeus_Password</param-value> 137 * </init-param> 138 * 139 * <init-param> 140 * <param-name>spnego.login.server.module</param-name> 141 * <param-value>spnego-server</param-value> 142 * </init-param> 143 * 144 * <init-param> 145 * <param-name>spnego.prompt.ntlm</param-name> 146 * <param-value>true</param-value> 147 * </init-param> 148 * 149 * <init-param> 150 * <param-name>spnego.logger.level</param-name> 151 * <param-value>1</param-value> 152 * </init-param> 153 * </filter> 154 *</code></pre> 155 * </p> 156 * 157 * <p><b>Example usage on web page</b></p> 158 * 159 * <p><pre> <html> 160 * <head> 161 * <title>Hello SPNEGO Example</title> 162 * </head> 163 * <body> 164 * Hello <%= request.getRemoteUser() %> ! 165 * </body> 166 * </html> 167 * </pre> 168 * </p> 169 * 170 * <p> 171 * Take a look at the <a href="http://spnego.sourceforge.net/reference_docs.html" 172 * target="_blank">reference docs</a> for other configuration parameters. 173 * </p> 174 * 175 * <p>See more usage examples at 176 * <a href="http://spnego.sourceforge.net" target="_blank">http://spnego.sourceforge.net</a> 177 * </p> 178 * 179 * @author Darwin V. Felix 180 * 181 */ 182 public final class SpnegoHttpFilter implements Filter { 183 184 private static final Logger LOGGER = Logger.getLogger(Constants.LOGGER_NAME); 185 186 /** Object for performing Basic and SPNEGO authentication. */ 187 private transient SpnegoAuthenticator authenticator = null; 188 189 public void init(final FilterConfig filterConfig) throws ServletException { 190 191 try { 192 // set some System properties 193 final SpnegoFilterConfig config = SpnegoFilterConfig.getInstance(filterConfig); 194 195 // pre-authenticate 196 this.authenticator = new SpnegoAuthenticator(config); 197 } catch (final LoginException le) { 198 throw new ServletException(le); 199 } catch (final GSSException gsse) { 200 throw new ServletException(gsse); 201 } catch (final PrivilegedActionException pae) { 202 throw new ServletException(pae); 203 } catch (final FileNotFoundException fnfe) { 204 throw new ServletException(fnfe); 205 } catch (final URISyntaxException uri) { 206 throw new ServletException(uri); 207 } 208 } 209 210 @Override 211 public void destroy() { 212 if (null != this.authenticator) { 213 this.authenticator.dispose(); 214 this.authenticator = null; 215 } 216 } 217 218 @Override 219 public void doFilter(final ServletRequest request, final ServletResponse response 220 , final FilterChain chain) throws IOException, ServletException { 221 222 final HttpServletRequest httpRequest = (HttpServletRequest) request; 223 final SpnegoHttpServletResponse spnegoResponse = new SpnegoHttpServletResponse( 224 (HttpServletResponse) response); 225 226 // client/caller principal 227 final SpnegoPrincipal principal; 228 try { 229 principal = this.authenticator.authenticate(httpRequest, spnegoResponse); 230 } catch (GSSException gsse) { 231 LOGGER.severe("HTTP Authorization Header=" 232 + httpRequest.getHeader(Constants.AUTHZ_HEADER)); 233 throw new ServletException(gsse); 234 } 235 236 // context/auth loop not yet complete 237 if (spnegoResponse.isStatusSet()) { 238 return; 239 } 240 241 // assert 242 if (null == principal) { 243 LOGGER.severe("Principal was null."); 244 spnegoResponse.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, true); 245 return; 246 } 247 248 LOGGER.fine("principal=" + principal); 249 250 chain.doFilter(new SpnegoHttpServletRequest(httpRequest, principal), response); 251 } 252 253 /** 254 * Defines constants and parameter names that are used in the 255 * web.xml file, and HTTP request headers, etc. 256 * 257 * <p> 258 * This class is primarily used internally or by implementers of 259 * custom http clients and by {@link SpnegoFilterConfig}. 260 * </p> 261 * 262 */ 263 public static final class Constants { 264 265 private Constants() { 266 // default private 267 } 268 269 /** 270 * Servlet init param name in web.xml <b>spnego.allow.basic</b>. 271 * 272 * <p>Set this value to <code>true</code> in web.xml if the filter 273 * should allow Basic Authentication.</p> 274 * 275 * <p>It is recommended that you only allow Basic Authentication 276 * if you have clients that cannot perform Kerberos authentication. 277 * Also, you should consider requiring SSL/TLS by setting 278 * <code>spnego.allow.unsecure.basic</code> to <code>false</code>.</p> 279 */ 280 public static final String ALLOW_BASIC = "spnego.allow.basic"; 281 282 /** 283 * Servlet init param name in web.xml <b>spnego.allow.delegation</b>. 284 * 285 * <p>Set this value to <code>true</code> if server should support 286 * credential delegation requests.</p> 287 * 288 * <p>Take a look at the {@link DelegateServletRequest} for more 289 * information about other pre-requisites.</p> 290 */ 291 public static final String ALLOW_DELEGATION = "spnego.allow.delegation"; 292 293 /** 294 * Servlet init param name in web.xml <b>spnego.allow.localhost</b>. 295 * 296 * <p>Flag to indicate if requests coming from http://localhost 297 * or http://127.0.0.1 should not be authenticated using 298 * Kerberos.</p> 299 * 300 * <p>This feature helps to obviate the requirement of 301 * creating an SPN for developer machines.</p> 302 * 303 */ 304 public static final String ALLOW_LOCALHOST = "spnego.allow.localhost"; 305 306 /** 307 * Servlet init param name in web.xml <b>spnego.allow.unsecure.basic</b>. 308 * 309 * <p>Set this value to <code>false</code> in web.xml if the filter 310 * should reject connections that do not use SSL/TLS.</p> 311 */ 312 public static final String ALLOW_UNSEC_BASIC = "spnego.allow.unsecure.basic"; 313 314 /** 315 * HTTP Response Header <b>WWW-Authenticate</b>. 316 * 317 * <p>The filter will respond with this header with a value of "Basic" 318 * and/or "Negotiate" (based on web.xml file).</p> 319 */ 320 public static final String AUTHN_HEADER = "WWW-Authenticate"; 321 322 /** 323 * HTTP Request Header <b>Authorization</b>. 324 * 325 * <p>Clients should send this header where the value is the 326 * authentication token(s).</p> 327 */ 328 public static final String AUTHZ_HEADER = "Authorization"; 329 330 /** 331 * HTTP Response Header <b>Basic</b>. 332 * 333 * <p>The filter will set this as the value for the "WWW-Authenticate" 334 * header if "Basic" auth is allowed (based on web.xml file).</p> 335 */ 336 public static final String BASIC_HEADER = "Basic"; 337 338 /** 339 * Servlet init param name in web.xml <b>spnego.login.client.module</b>. 340 * 341 * <p>The LoginModule name that exists in the login.conf file.</p> 342 */ 343 public static final String CLIENT_MODULE = "spnego.login.client.module"; 344 345 /** 346 * Servlet init param name in web.xml <b>spnego.krb5.conf</b>. 347 * 348 * <p>The location of the krb5.conf file. On Windows, this file will 349 * sometimes be named krb5.ini and reside <code>%WINDOWS_ROOT%/krb5.ini</code> 350 * here.</p> 351 * 352 * <p>By default, Java looks for the file in these locations and order: 353 * <li>System Property (java.security.krb5.conf)</li> 354 * <li>%JAVA_HOME%/lib/security/krb5.conf</li> 355 * <li>%WINDOWS_ROOT%/krb5.ini</li> 356 * </p> 357 */ 358 public static final String KRB5_CONF = "spnego.krb5.conf"; 359 360 /** 361 * Specify logging level. 362 363 * <pre> 364 * 1 = FINEST 365 * 2 = FINER 366 * 3 = FINE 367 * 4 = CONFIG 368 * 5 = INFO 369 * 6 = WARNING 370 * 7 = SEVERE 371 * </pre> 372 * 373 */ 374 static final String LOGGER_LEVEL = "spnego.logger.level"; 375 376 /** 377 * Name of Spnego Logger. 378 * 379 * <p>Example: <code>Logger.getLogger(Constants.LOGGER_NAME)</code></p> 380 */ 381 static final String LOGGER_NAME = "SpnegoHttpFilter"; 382 383 /** 384 * Servlet init param name in web.xml <b>spnego.login.conf</b>. 385 * 386 * <p>The location of the login.conf file.</p> 387 */ 388 public static final String LOGIN_CONF = "spnego.login.conf"; 389 390 /** 391 * HTTP Response Header <b>Negotiate</b>. 392 * 393 * <p>The filter will set this as the value for the "WWW-Authenticate" 394 * header. Note that the filter may also add another header with 395 * a value of "Basic" (if allowed by the web.xml file).</p> 396 */ 397 public static final String NEGOTIATE_HEADER = "Negotiate"; 398 399 /** 400 * NTLM base64-encoded token start value. 401 */ 402 static final String NTLM_PROLOG = "TlRMTVNT"; 403 404 /** 405 * Servlet init param name in web.xml <b>spnego.preauth.password</b>. 406 * 407 * <p>Network Domain password. For Windows, this is sometimes known 408 * as the Windows NT password.</p> 409 */ 410 public static final String PREAUTH_PASSWORD = "spnego.preauth.password"; 411 412 /** 413 * Servlet init param name in web.xml <b>spnego.preauth.username</b>. 414 * 415 * <p>Network Domain username. For Windows, this is sometimes known 416 * as the Windows NT username.</p> 417 */ 418 public static final String PREAUTH_USERNAME = "spnego.preauth.username"; 419 420 /** 421 * If server receives an NTLM token, the filter will return with a 401 422 * and with Basic as the only option (no Negotiate) <b>spnego.prompt.ntlm</b>. 423 */ 424 public static final String PROMPT_NTLM = "spnego.prompt.ntlm"; 425 426 /** 427 * Servlet init param name in web.xml <b>spnego.login.server.module</b>. 428 * 429 * <p>The LoginModule name that exists in the login.conf file.</p> 430 */ 431 public static final String SERVER_MODULE = "spnego.login.server.module"; 432 } 433 }