AuthenticationInfo.java revision 12745:f068a4ffddd2
1/* 2 * Copyright (c) 1995, 2013, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26package sun.net.www.protocol.http; 27 28import java.io.IOException; 29import java.io.ObjectInputStream; 30import java.net.PasswordAuthentication; 31import java.net.URL; 32import java.util.HashMap; 33 34import sun.net.www.HeaderParser; 35 36 37/** 38 * AuthenticationInfo: Encapsulate the information needed to 39 * authenticate a user to a server. 40 * 41 * @author Jon Payne 42 * @author Herb Jellinek 43 * @author Bill Foote 44 */ 45// REMIND: It would be nice if this class understood about partial matching. 46// If you're authorized for foo.com, chances are high you're also 47// authorized for baz.foo.com. 48// NB: When this gets implemented, be careful about the uncaching 49// policy in HttpURLConnection. A failure on baz.foo.com shouldn't 50// uncache foo.com! 51 52public abstract class AuthenticationInfo extends AuthCacheValue implements Cloneable { 53 54 static final long serialVersionUID = -2588378268010453259L; 55 56 // Constants saying what kind of authroization this is. This determines 57 // the namespace in the hash table lookup. 58 public static final char SERVER_AUTHENTICATION = 's'; 59 public static final char PROXY_AUTHENTICATION = 'p'; 60 61 /** 62 * If true, then simultaneous authentication requests to the same realm/proxy 63 * are serialized, in order to avoid a user having to type the same username/passwords 64 * repeatedly, via the Authenticator. Default is false, which means that this 65 * behavior is switched off. 66 */ 67 static boolean serializeAuth; 68 69 static { 70 serializeAuth = java.security.AccessController.doPrivileged( 71 new sun.security.action.GetBooleanAction( 72 "http.auth.serializeRequests")).booleanValue(); 73 } 74 75 /* AuthCacheValue: */ 76 77 protected transient PasswordAuthentication pw; 78 79 public PasswordAuthentication credentials() { 80 return pw; 81 } 82 83 public AuthCacheValue.Type getAuthType() { 84 return type == SERVER_AUTHENTICATION ? 85 AuthCacheValue.Type.Server: 86 AuthCacheValue.Type.Proxy; 87 } 88 89 AuthScheme getAuthScheme() { 90 return authScheme; 91 } 92 93 public String getHost() { 94 return host; 95 } 96 public int getPort() { 97 return port; 98 } 99 public String getRealm() { 100 return realm; 101 } 102 public String getPath() { 103 return path; 104 } 105 public String getProtocolScheme() { 106 return protocol; 107 } 108 109 /** 110 * requests is used to ensure that interaction with the 111 * Authenticator for a particular realm is single threaded. 112 * ie. if multiple threads need to get credentials from the user 113 * at the same time, then all but the first will block until 114 * the first completes its authentication. 115 */ 116 private static HashMap<String,Thread> requests = new HashMap<>(); 117 118 /* check if a request for this destination is in progress 119 * return false immediately if not. Otherwise block until 120 * request is finished and return true 121 */ 122 private static boolean requestIsInProgress (String key) { 123 if (!serializeAuth) { 124 /* behavior is disabled. Revert to concurrent requests */ 125 return false; 126 } 127 synchronized (requests) { 128 Thread t, c; 129 c = Thread.currentThread(); 130 if ((t = requests.get(key)) == null) { 131 requests.put (key, c); 132 return false; 133 } 134 if (t == c) { 135 return false; 136 } 137 while (requests.containsKey(key)) { 138 try { 139 requests.wait (); 140 } catch (InterruptedException e) {} 141 } 142 } 143 /* entry may be in cache now. */ 144 return true; 145 } 146 147 /* signal completion of an authentication (whether it succeeded or not) 148 * so that other threads can continue. 149 */ 150 private static void requestCompleted (String key) { 151 synchronized (requests) { 152 Thread thread = requests.get(key); 153 if (thread != null && thread == Thread.currentThread()) { 154 boolean waspresent = requests.remove(key) != null; 155 assert waspresent; 156 } 157 requests.notifyAll(); 158 } 159 } 160 161 //public String toString () { 162 //return ("{"+type+":"+authScheme+":"+protocol+":"+host+":"+port+":"+realm+":"+path+"}"); 163 //} 164 165 // REMIND: This cache just grows forever. We should put in a bounded 166 // cache, or maybe something using WeakRef's. 167 168 /** The type (server/proxy) of authentication this is. Used for key lookup */ 169 char type; 170 171 /** The authentication scheme (basic/digest). Also used for key lookup */ 172 AuthScheme authScheme; 173 174 /** The protocol/scheme (i.e. http or https ). Need to keep the caches 175 * logically separate for the two protocols. This field is only used 176 * when constructed with a URL (the normal case for server authentication) 177 * For proxy authentication the protocol is not relevant. 178 */ 179 String protocol; 180 181 /** The host we're authenticating against. */ 182 String host; 183 184 /** The port on the host we're authenticating against. */ 185 int port; 186 187 /** The realm we're authenticating against. */ 188 String realm; 189 190 /** The shortest path from the URL we authenticated against. */ 191 String path; 192 193 /** Use this constructor only for proxy entries */ 194 public AuthenticationInfo(char type, AuthScheme authScheme, String host, int port, String realm) { 195 this.type = type; 196 this.authScheme = authScheme; 197 this.protocol = ""; 198 this.host = host.toLowerCase(); 199 this.port = port; 200 this.realm = realm; 201 this.path = null; 202 } 203 204 public Object clone() { 205 try { 206 return super.clone (); 207 } catch (CloneNotSupportedException e) { 208 // Cannot happen because Cloneable implemented by AuthenticationInfo 209 return null; 210 } 211 } 212 213 /* 214 * Constructor used to limit the authorization to the path within 215 * the URL. Use this constructor for origin server entries. 216 */ 217 public AuthenticationInfo(char type, AuthScheme authScheme, URL url, String realm) { 218 this.type = type; 219 this.authScheme = authScheme; 220 this.protocol = url.getProtocol().toLowerCase(); 221 this.host = url.getHost().toLowerCase(); 222 this.port = url.getPort(); 223 if (this.port == -1) { 224 this.port = url.getDefaultPort(); 225 } 226 this.realm = realm; 227 228 String urlPath = url.getPath(); 229 if (urlPath.length() == 0) 230 this.path = urlPath; 231 else { 232 this.path = reducePath (urlPath); 233 } 234 235 } 236 237 /* 238 * reduce the path to the root of where we think the 239 * authorization begins. This could get shorter as 240 * the url is traversed up following a successful challenge. 241 */ 242 static String reducePath (String urlPath) { 243 int sepIndex = urlPath.lastIndexOf('/'); 244 int targetSuffixIndex = urlPath.lastIndexOf('.'); 245 if (sepIndex != -1) 246 if (sepIndex < targetSuffixIndex) 247 return urlPath.substring(0, sepIndex+1); 248 else 249 return urlPath; 250 else 251 return urlPath; 252 } 253 254 /** 255 * Returns info for the URL, for an HTTP server auth. Used when we 256 * don't yet know the realm 257 * (i.e. when we're preemptively setting the auth). 258 */ 259 static AuthenticationInfo getServerAuth(URL url) { 260 int port = url.getPort(); 261 if (port == -1) { 262 port = url.getDefaultPort(); 263 } 264 String key = SERVER_AUTHENTICATION + ":" + url.getProtocol().toLowerCase() 265 + ":" + url.getHost().toLowerCase() + ":" + port; 266 return getAuth(key, url); 267 } 268 269 /** 270 * Returns info for the URL, for an HTTP server auth. Used when we 271 * do know the realm (i.e. when we're responding to a challenge). 272 * In this case we do not use the path because the protection space 273 * is identified by the host:port:realm only 274 */ 275 static String getServerAuthKey(URL url, String realm, AuthScheme scheme) { 276 int port = url.getPort(); 277 if (port == -1) { 278 port = url.getDefaultPort(); 279 } 280 String key = SERVER_AUTHENTICATION + ":" + scheme + ":" + url.getProtocol().toLowerCase() 281 + ":" + url.getHost().toLowerCase() + ":" + port + ":" + realm; 282 return key; 283 } 284 285 static AuthenticationInfo getServerAuth(String key) { 286 AuthenticationInfo cached = getAuth(key, null); 287 if ((cached == null) && requestIsInProgress (key)) { 288 /* check the cache again, it might contain an entry */ 289 cached = getAuth(key, null); 290 } 291 return cached; 292 } 293 294 295 /** 296 * Return the AuthenticationInfo object from the cache if it's path is 297 * a substring of the supplied URLs path. 298 */ 299 static AuthenticationInfo getAuth(String key, URL url) { 300 if (url == null) { 301 return (AuthenticationInfo)cache.get (key, null); 302 } else { 303 return (AuthenticationInfo)cache.get (key, url.getPath()); 304 } 305 } 306 307 /** 308 * Returns a firewall authentication, for the given host/port. Used 309 * for preemptive header-setting. Note, the protocol field is always 310 * blank for proxies. 311 */ 312 static AuthenticationInfo getProxyAuth(String host, int port) { 313 String key = PROXY_AUTHENTICATION + "::" + host.toLowerCase() + ":" + port; 314 AuthenticationInfo result = (AuthenticationInfo) cache.get(key, null); 315 return result; 316 } 317 318 /** 319 * Returns a firewall authentication, for the given host/port and realm. 320 * Used in response to a challenge. Note, the protocol field is always 321 * blank for proxies. 322 */ 323 static String getProxyAuthKey(String host, int port, String realm, AuthScheme scheme) { 324 String key = PROXY_AUTHENTICATION + ":" + scheme + "::" + host.toLowerCase() 325 + ":" + port + ":" + realm; 326 return key; 327 } 328 329 static AuthenticationInfo getProxyAuth(String key) { 330 AuthenticationInfo cached = (AuthenticationInfo) cache.get(key, null); 331 if ((cached == null) && requestIsInProgress (key)) { 332 /* check the cache again, it might contain an entry */ 333 cached = (AuthenticationInfo) cache.get(key, null); 334 } 335 return cached; 336 } 337 338 339 /** 340 * Add this authentication to the cache 341 */ 342 void addToCache() { 343 String key = cacheKey(true); 344 cache.put(key, this); 345 if (supportsPreemptiveAuthorization()) { 346 cache.put(cacheKey(false), this); 347 } 348 endAuthRequest(key); 349 } 350 351 static void endAuthRequest (String key) { 352 if (!serializeAuth) { 353 return; 354 } 355 synchronized (requests) { 356 requestCompleted(key); 357 } 358 } 359 360 /** 361 * Remove this authentication from the cache 362 */ 363 void removeFromCache() { 364 cache.remove(cacheKey(true), this); 365 if (supportsPreemptiveAuthorization()) { 366 cache.remove(cacheKey(false), this); 367 } 368 } 369 370 /** 371 * @return true if this authentication supports preemptive authorization 372 */ 373 public abstract boolean supportsPreemptiveAuthorization(); 374 375 /** 376 * @return the name of the HTTP header this authentication wants set. 377 * This is used for preemptive authorization. 378 */ 379 public String getHeaderName() { 380 if (type == SERVER_AUTHENTICATION) { 381 return "Authorization"; 382 } else { 383 return "Proxy-authorization"; 384 } 385 } 386 387 /** 388 * Calculates and returns the authentication header value based 389 * on the stored authentication parameters. If the calculation does not depend 390 * on the URL or the request method then these parameters are ignored. 391 * @param url The URL 392 * @param method The request method 393 * @return the value of the HTTP header this authentication wants set. 394 * Used for preemptive authorization. 395 */ 396 public abstract String getHeaderValue(URL url, String method); 397 398 /** 399 * Set header(s) on the given connection. Subclasses must override 400 * This will only be called for 401 * definitive (i.e. non-preemptive) authorization. 402 * @param conn The connection to apply the header(s) to 403 * @param p A source of header values for this connection, if needed. 404 * @param raw The raw header field (if needed) 405 * @return true if all goes well, false if no headers were set. 406 */ 407 public abstract boolean setHeaders(HttpURLConnection conn, HeaderParser p, String raw); 408 409 /** 410 * Check if the header indicates that the current auth. parameters are stale. 411 * If so, then replace the relevant field with the new value 412 * and return true. Otherwise return false. 413 * returning true means the request can be retried with the same userid/password 414 * returning false means we have to go back to the user to ask for a new 415 * username password. 416 */ 417 public abstract boolean isAuthorizationStale (String header); 418 419 /** 420 * Give a key for hash table lookups. 421 * @param includeRealm if you want the realm considered. Preemptively 422 * setting an authorization is done before the realm is known. 423 */ 424 String cacheKey(boolean includeRealm) { 425 // This must be kept in sync with the getXXXAuth() methods in this 426 // class. 427 if (includeRealm) { 428 return type + ":" + authScheme + ":" + protocol + ":" 429 + host + ":" + port + ":" + realm; 430 } else { 431 return type + ":" + protocol + ":" + host + ":" + port; 432 } 433 } 434 435 String s1, s2; /* used for serialization of pw */ 436 437 private void readObject(ObjectInputStream s) 438 throws IOException, ClassNotFoundException 439 { 440 s.defaultReadObject (); 441 pw = new PasswordAuthentication (s1, s2.toCharArray()); 442 s1 = null; s2= null; 443 } 444 445 private synchronized void writeObject(java.io.ObjectOutputStream s) 446 throws IOException 447 { 448 s1 = pw.getUserName(); 449 s2 = new String (pw.getPassword()); 450 s.defaultWriteObject (); 451 } 452} 453