1/* 2 * Copyright (c) 2002, 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.util.Collections; 29import java.util.Iterator; 30import java.util.HashMap; 31import java.util.Set; 32 33import sun.net.www.*; 34import sun.security.action.GetPropertyAction; 35 36/** 37 * This class is used to parse the information in WWW-Authenticate: and Proxy-Authenticate: 38 * headers. It searches among multiple header lines and within each header line 39 * for the best currently supported scheme. It can also return a HeaderParser 40 * containing the challenge data for that particular scheme. 41 * 42 * Some examples: 43 * 44 * WWW-Authenticate: Basic realm="foo" Digest realm="bar" NTLM 45 * Note the realm parameter must be associated with the particular scheme. 46 * 47 * or 48 * 49 * WWW-Authenticate: Basic realm="foo" 50 * WWW-Authenticate: Digest realm="foo",qop="auth",nonce="thisisanunlikelynonce" 51 * WWW-Authenticate: NTLM 52 * 53 * or 54 * 55 * WWW-Authenticate: Basic realm="foo" 56 * WWW-Authenticate: NTLM ASKAJK9893289889QWQIOIONMNMN 57 * 58 * The last example shows how NTLM breaks the rules of rfc2617 for the structure of 59 * the authentication header. This is the reason why the raw header field is used for ntlm. 60 * 61 * At present, the class chooses schemes in following order : 62 * 1. Negotiate (if supported) 63 * 2. Kerberos (if supported) 64 * 3. Digest 65 * 4. NTLM (if supported) 66 * 5. Basic 67 * 68 * This choice can be modified by setting a system property: 69 * 70 * -Dhttp.auth.preference="scheme" 71 * 72 * which in this case, specifies that "scheme" should be used as the auth scheme when offered 73 * disregarding the default prioritisation. If scheme is not offered, or explicitly 74 * disabled, by {@code disabledSchemes}, then the default priority is used. 75 * 76 * Attention: when http.auth.preference is set as SPNEGO or Kerberos, it's actually "Negotiate 77 * with SPNEGO" or "Negotiate with Kerberos", which means the user will prefer the Negotiate 78 * scheme with GSS/SPNEGO or GSS/Kerberos mechanism. 79 * 80 * This also means that the real "Kerberos" scheme can never be set as a preference. 81 */ 82 83public class AuthenticationHeader { 84 85 MessageHeader rsp; // the response to be parsed 86 HeaderParser preferred; 87 String preferred_r; // raw Strings 88 private final HttpCallerInfo hci; // un-schemed, need check 89 90 // When set true, do not use Negotiate even if the response 91 // headers suggest so. 92 boolean dontUseNegotiate = false; 93 static String authPref=null; 94 95 public String toString() { 96 return "AuthenticationHeader: prefer " + preferred_r; 97 } 98 99 static { 100 authPref = GetPropertyAction.privilegedGetProperty("http.auth.preference"); 101 102 // http.auth.preference can be set to SPNEGO or Kerberos. 103 // In fact they means "Negotiate with SPNEGO" and "Negotiate with 104 // Kerberos" separately, so here they are all translated into 105 // Negotiate. Read NegotiateAuthentication.java to see how they 106 // were used later. 107 108 if (authPref != null) { 109 authPref = authPref.toLowerCase(); 110 if(authPref.equals("spnego") || authPref.equals("kerberos")) { 111 authPref = "negotiate"; 112 } 113 } 114 } 115 116 String hdrname; // Name of the header to look for 117 118 /** 119 * Parses a set of authentication headers and chooses the preferred scheme 120 * that is supported for a given host. 121 */ 122 public AuthenticationHeader (String hdrname, MessageHeader response, 123 HttpCallerInfo hci, boolean dontUseNegotiate) { 124 this(hdrname, response, hci, dontUseNegotiate, Collections.emptySet()); 125 } 126 127 /** 128 * Parses a set of authentication headers and chooses the preferred scheme 129 * that is supported for a given host. 130 * 131 * <p> The {@code disabledSchemes} parameter is a, possibly empty, set of 132 * authentication schemes that are disabled. 133 */ 134 public AuthenticationHeader(String hdrname, 135 MessageHeader response, 136 HttpCallerInfo hci, 137 boolean dontUseNegotiate, 138 Set<String> disabledSchemes) { 139 this.hci = hci; 140 this.dontUseNegotiate = dontUseNegotiate; 141 this.rsp = response; 142 this.hdrname = hdrname; 143 this.schemes = new HashMap<>(); 144 parse(disabledSchemes); 145 } 146 147 public HttpCallerInfo getHttpCallerInfo() { 148 return hci; 149 } 150 /* we build up a map of scheme names mapped to SchemeMapValue objects */ 151 static class SchemeMapValue { 152 SchemeMapValue (HeaderParser h, String r) {raw=r; parser=h;} 153 String raw; 154 HeaderParser parser; 155 } 156 157 HashMap<String, SchemeMapValue> schemes; 158 159 /* Iterate through each header line, and then within each line. 160 * If multiple entries exist for a particular scheme (unlikely) 161 * then the last one will be used. The 162 * preferred scheme that we support will be used. 163 */ 164 private void parse(Set<String> disabledSchemes) { 165 Iterator<String> iter = rsp.multiValueIterator(hdrname); 166 while (iter.hasNext()) { 167 String raw = iter.next(); 168 // HeaderParser lower cases everything, so can be used case-insensitively 169 HeaderParser hp = new HeaderParser(raw); 170 Iterator<String> keys = hp.keys(); 171 int i, lastSchemeIndex; 172 for (i=0, lastSchemeIndex = -1; keys.hasNext(); i++) { 173 keys.next(); 174 if (hp.findValue(i) == null) { /* found a scheme name */ 175 if (lastSchemeIndex != -1) { 176 HeaderParser hpn = hp.subsequence (lastSchemeIndex, i); 177 String scheme = hpn.findKey(0); 178 if (!disabledSchemes.contains(scheme)) 179 schemes.put(scheme, new SchemeMapValue (hpn, raw)); 180 } 181 lastSchemeIndex = i; 182 } 183 } 184 if (i > lastSchemeIndex) { 185 HeaderParser hpn = hp.subsequence (lastSchemeIndex, i); 186 String scheme = hpn.findKey(0); 187 if (!disabledSchemes.contains(scheme)) 188 schemes.put(scheme, new SchemeMapValue (hpn, raw)); 189 } 190 } 191 192 /* choose the best of them, the order is 193 * negotiate -> kerberos -> digest -> ntlm -> basic 194 */ 195 SchemeMapValue v = null; 196 if (authPref == null || (v=schemes.get (authPref)) == null) { 197 198 if(v == null && !dontUseNegotiate) { 199 SchemeMapValue tmp = schemes.get("negotiate"); 200 if(tmp != null) { 201 if(hci == null || !NegotiateAuthentication.isSupported(new HttpCallerInfo(hci, "Negotiate"))) { 202 tmp = null; 203 } 204 v = tmp; 205 } 206 } 207 208 if(v == null && !dontUseNegotiate) { 209 SchemeMapValue tmp = schemes.get("kerberos"); 210 if(tmp != null) { 211 // the Kerberos scheme is only observed in MS ISA Server. In 212 // fact i think it's a Kerberos-mechnism-only Negotiate. 213 // Since the Kerberos scheme is always accompanied with the 214 // Negotiate scheme, so it seems impossible to reach this 215 // line. Even if the user explicitly set http.auth.preference 216 // as Kerberos, it means Negotiate with Kerberos, and the code 217 // will still tried to use Negotiate at first. 218 // 219 // The only chance this line get executed is that the server 220 // only suggest the Kerberos scheme. 221 if(hci == null || !NegotiateAuthentication.isSupported(new HttpCallerInfo(hci, "Kerberos"))) { 222 tmp = null; 223 } 224 v = tmp; 225 } 226 } 227 228 if(v == null) { 229 if ((v=schemes.get ("digest")) == null) { 230 if (!NTLMAuthenticationProxy.supported 231 || ((v=schemes.get("ntlm"))==null)) { 232 v = schemes.get ("basic"); 233 } 234 } 235 } 236 } else { // authPref != null && it's found in reponses' 237 if (dontUseNegotiate && authPref.equals("negotiate")) { 238 v = null; 239 } 240 } 241 242 if (v != null) { 243 preferred = v.parser; 244 preferred_r = v.raw; 245 } 246 } 247 248 /** 249 * return a header parser containing the preferred authentication scheme (only). 250 * The preferred scheme is the strongest of the schemes proposed by the server. 251 * The returned HeaderParser will contain the relevant parameters for that scheme 252 */ 253 public HeaderParser headerParser() { 254 return preferred; 255 } 256 257 /** 258 * return the name of the preferred scheme 259 */ 260 public String scheme() { 261 if (preferred != null) { 262 return preferred.findKey(0); 263 } else { 264 return null; 265 } 266 } 267 268 /* return the raw header field for the preferred/chosen scheme */ 269 270 public String raw () { 271 return preferred_r; 272 } 273 274 /** 275 * returns true is the header exists and contains a recognised scheme 276 */ 277 public boolean isPresent () { 278 return preferred != null; 279 } 280} 281