|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
|
|
package sun.net.www.protocol.http; |
|
|
|
import java.net.URL; |
|
import java.io.IOException; |
|
import java.net.Authenticator.RequestorType; |
|
import java.util.Base64; |
|
import java.util.HashMap; |
|
import sun.net.www.HeaderParser; |
|
import sun.util.logging.PlatformLogger; |
|
import static sun.net.www.protocol.http.AuthScheme.NEGOTIATE; |
|
import static sun.net.www.protocol.http.AuthScheme.KERBEROS; |
|
import sun.security.action.GetPropertyAction; |
|
|
|
/** |
|
* NegotiateAuthentication: |
|
* |
|
* @author weijun.wang@sun.com |
|
* @since 1.6 |
|
*/ |
|
|
|
class NegotiateAuthentication extends AuthenticationInfo { |
|
|
|
private static final long serialVersionUID = 100L; |
|
private static final PlatformLogger logger = HttpURLConnection.getHttpLogger(); |
|
|
|
private final HttpCallerInfo hci; |
|
|
|
// These maps are used to manage the GSS availability for diffrent |
|
// hosts. The key for both maps is the host name. |
|
// <code>supported</code> is set when isSupported is checked, |
|
// if it's true, a cached Negotiator is put into <code>cache</code>. |
|
|
|
static HashMap <String, Boolean> supported = null; |
|
static ThreadLocal <HashMap <String, Negotiator>> cache = null; |
|
|
|
private static final boolean cacheSPNEGO; |
|
static { |
|
String spnegoCacheProp = |
|
GetPropertyAction.privilegedGetProperty("jdk.spnego.cache", "true"); |
|
cacheSPNEGO = Boolean.parseBoolean(spnegoCacheProp); |
|
} |
|
|
|
|
|
private Negotiator negotiator = null; |
|
|
|
|
|
|
|
|
|
*/ |
|
public NegotiateAuthentication(HttpCallerInfo hci) { |
|
super(RequestorType.PROXY==hci.authType ? PROXY_AUTHENTICATION : SERVER_AUTHENTICATION, |
|
hci.scheme.equalsIgnoreCase("Negotiate") ? NEGOTIATE : KERBEROS, |
|
hci.url, |
|
"", |
|
AuthenticatorKeys.getKey(hci.authenticator)); |
|
this.hci = hci; |
|
} |
|
|
|
|
|
|
|
*/ |
|
@Override |
|
public boolean supportsPreemptiveAuthorization() { |
|
return false; |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
public static boolean isSupported(HttpCallerInfo hci) { |
|
ClassLoader loader = null; |
|
try { |
|
loader = Thread.currentThread().getContextClassLoader(); |
|
} catch (SecurityException se) { |
|
if (logger.isLoggable(PlatformLogger.Level.FINER)) { |
|
logger.finer("NegotiateAuthentication: " + |
|
"Attempt to get the context class loader failed - " + se); |
|
} |
|
} |
|
|
|
if (loader != null) { |
|
// Lock on the class loader instance to avoid the deadlock engaging |
|
|
|
synchronized (loader) { |
|
return isSupportedImpl(hci); |
|
} |
|
} |
|
return isSupportedImpl(hci); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private static synchronized boolean isSupportedImpl(HttpCallerInfo hci) { |
|
if (supported == null) { |
|
supported = new HashMap<>(); |
|
} |
|
String hostname = hci.host; |
|
hostname = hostname.toLowerCase(); |
|
if (supported.containsKey(hostname)) { |
|
return supported.get(hostname); |
|
} |
|
|
|
Negotiator neg = Negotiator.getNegotiator(hci); |
|
if (neg != null) { |
|
supported.put(hostname, true); |
|
// the only place cache.put is called. here we can make sure |
|
|
|
if (cache == null) { |
|
cache = new ThreadLocal<>() { |
|
@Override |
|
protected HashMap<String, Negotiator> initialValue() { |
|
return new HashMap<>(); |
|
} |
|
}; |
|
} |
|
cache.get().put(hostname, neg); |
|
return true; |
|
} else { |
|
supported.put(hostname, false); |
|
return false; |
|
} |
|
} |
|
|
|
private static synchronized HashMap<String, Negotiator> getCache() { |
|
if (cache == null) return null; |
|
return cache.get(); |
|
} |
|
|
|
@Override |
|
protected boolean useAuthCache() { |
|
return super.useAuthCache() && cacheSPNEGO; |
|
} |
|
|
|
|
|
|
|
*/ |
|
@Override |
|
public String getHeaderValue(URL url, String method) { |
|
throw new RuntimeException ("getHeaderValue not supported"); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
@Override |
|
public boolean isAuthorizationStale (String header) { |
|
return false; /* should not be called for Negotiate */ |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
@Override |
|
public synchronized boolean setHeaders(HttpURLConnection conn, HeaderParser p, String raw) { |
|
|
|
try { |
|
String response; |
|
byte[] incoming = null; |
|
String[] parts = raw.split("\\s+"); |
|
if (parts.length > 1) { |
|
incoming = Base64.getDecoder().decode(parts[1]); |
|
} |
|
response = hci.scheme + " " + Base64.getEncoder().encodeToString( |
|
incoming==null?firstToken():nextToken(incoming)); |
|
|
|
conn.setAuthenticationProperty(getHeaderName(), response); |
|
return true; |
|
} catch (IOException e) { |
|
return false; |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private byte[] firstToken() throws IOException { |
|
negotiator = null; |
|
HashMap <String, Negotiator> cachedMap = getCache(); |
|
if (cachedMap != null) { |
|
negotiator = cachedMap.get(getHost()); |
|
if (negotiator != null) { |
|
cachedMap.remove(getHost()); |
|
} |
|
} |
|
if (negotiator == null) { |
|
negotiator = Negotiator.getNegotiator(hci); |
|
if (negotiator == null) { |
|
IOException ioe = new IOException("Cannot initialize Negotiator"); |
|
throw ioe; |
|
} |
|
} |
|
|
|
return negotiator.firstToken(); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private byte[] nextToken(byte[] token) throws IOException { |
|
return negotiator.nextToken(token); |
|
} |
|
|
|
// MS will send a final WWW-Authenticate even if the status is already |
|
// 200 OK. The token can be fed into initSecContext() again to determine |
|
// if the server can be trusted. This is not the same concept as Digest's |
|
// Authentication-Info header. |
|
// |
|
// Currently we ignore this header. |
|
|
|
} |