true if any specified
* constraint has been satisfied, or false if we have
* created a response challenge already.
*
* @param request Request we are processing
* @param response Response we are creating
*
* @exception IOException if an input/output error occurs
*/
@Override
protected boolean doAuthenticate(Request request, HttpServletResponse response)
throws IOException {
// NOTE: We don't try to reauthenticate using any existing SSO session,
// because that will only work if the original authentication was
// BASIC or FORM, which are less secure than the DIGEST auth-type
// specified for this webapp
//
// Change to true below to allow previous FORM or BASIC authentications
// to authenticate users for this webapp
// TODO make this a configurable attribute (in SingleSignOn??)
if (checkForCachedAuthentication(request, response, false)) {
return true;
}
// Validate any credentials already included with this request
Principal principal = null;
String authorization = request.getHeader("authorization");
DigestInfo digestInfo = new DigestInfo(getOpaque(), getNonceValidity(),
getKey(), nonces, isValidateUri());
if (authorization != null) {
if (digestInfo.parse(request, authorization)) {
if (digestInfo.validate(request)) {
principal = digestInfo.authenticate(context.getRealm());
}
if (principal != null && !digestInfo.isNonceStale()) {
register(request, response, principal,
HttpServletRequest.DIGEST_AUTH,
digestInfo.getUsername(), null);
return true;
}
}
}
// Send an "unauthorized" response and an appropriate challenge
// Next, generate a nonce token (that is a token which is supposed
// to be unique).
String nonce = generateNonce(request);
setAuthenticateHeader(request, response, nonce,
principal != null && digestInfo.isNonceStale());
response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
return false;
}
@Override
protected String getAuthMethod() {
return HttpServletRequest.DIGEST_AUTH;
}
// ------------------------------------------------------ Protected Methods
/**
* Removes the quotes on a string. RFC2617 states quotes are optional for
* all parameters except realm.
*
* @param quotedString The quoted string
* @param quotesRequired true if quotes were required
* @return The unquoted string
*/
protected static String removeQuotes(String quotedString,
boolean quotesRequired) {
//support both quoted and non-quoted
if (quotedString.length() > 0 && quotedString.charAt(0) != '"' &&
!quotesRequired) {
return quotedString;
} else if (quotedString.length() > 2) {
return quotedString.substring(1, quotedString.length() - 1);
} else {
return "";
}
}
/**
* Removes the quotes on a string.
*
* @param quotedString The quoted string
* @return The unquoted string
*/
protected static String removeQuotes(String quotedString) {
return removeQuotes(quotedString, false);
}
/**
* Generate a unique token. The token is generated according to the
* following pattern. NOnceToken = Base64 ( MD5 ( client-IP ":"
* time-stamp ":" private-key ) ).
*
* @param request HTTP Servlet request
* @return The generated nonce
*/
protected String generateNonce(Request request) {
long currentTime = System.currentTimeMillis();
synchronized (lastTimestampLock) {
if (currentTime > lastTimestamp) {
lastTimestamp = currentTime;
} else {
currentTime = ++lastTimestamp;
}
}
String ipTimeKey =
request.getRemoteAddr() + ":" + currentTime + ":" + getKey();
byte[] buffer = ConcurrentMessageDigest.digestMD5(
ipTimeKey.getBytes(StandardCharsets.ISO_8859_1));
String nonce = currentTime + ":" + MD5Encoder.encode(buffer);
NonceInfo info = new NonceInfo(currentTime, getNonceCountWindowSize());
synchronized (nonces) {
nonces.put(nonce, info);
}
return nonce;
}
/**
* Generates the WWW-Authenticate header.
* * The header MUST follow this template : *
* WWW-Authenticate = "WWW-Authenticate" ":" "Digest"
* digest-challenge
*
* digest-challenge = 1#( realm | [ domain ] | nonce |
* [ digest-opaque ] |[ stale ] | [ algorithm ] )
*
* realm = "realm" "=" realm-value
* realm-value = quoted-string
* domain = "domain" "=" <"> 1#URI <">
* nonce = "nonce" "=" nonce-value
* nonce-value = quoted-string
* opaque = "opaque" "=" quoted-string
* stale = "stale" "=" ( "true" | "false" )
* algorithm = "algorithm" "=" ( "MD5" | token )
*
*
* @param request HTTP Servlet request
* @param response HTTP Servlet response
* @param nonce nonce token
* @param isNonceStale true to add a stale parameter
*/
protected void setAuthenticateHeader(HttpServletRequest request,
HttpServletResponse response,
String nonce,
boolean isNonceStale) {
String realmName = getRealmName(context);
String authenticateHeader;
if (isNonceStale) {
authenticateHeader = "Digest realm=\"" + realmName + "\", " +
"qop=\"" + QOP + "\", nonce=\"" + nonce + "\", " + "opaque=\"" +
getOpaque() + "\", stale=true";
} else {
authenticateHeader = "Digest realm=\"" + realmName + "\", " +
"qop=\"" + QOP + "\", nonce=\"" + nonce + "\", " + "opaque=\"" +
getOpaque() + "\"";
}
response.setHeader(AUTH_HEADER_NAME, authenticateHeader);
}
// ------------------------------------------------------- Lifecycle Methods
@Override
protected synchronized void startInternal() throws LifecycleException {
super.startInternal();
// Generate a random secret key
if (getKey() == null) {
setKey(sessionIdGenerator.generateSessionId());
}
// Generate the opaque string the same way
if (getOpaque() == null) {
setOpaque(sessionIdGenerator.generateSessionId());
}
nonces = new LinkedHashMap