This commit is contained in:
2024-11-30 19:03:49 +08:00
commit 1e6763c160
3806 changed files with 737676 additions and 0 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,289 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.catalina.authenticator;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.security.Principal;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.catalina.connector.Request;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import org.apache.tomcat.util.buf.ByteChunk;
import org.apache.tomcat.util.buf.MessageBytes;
import org.apache.tomcat.util.codec.binary.Base64;
/**
* An <b>Authenticator</b> and <b>Valve</b> implementation of HTTP BASIC
* Authentication, as outlined in RFC 2617: "HTTP Authentication: Basic
* and Digest Access Authentication."
*
* @author Craig R. McClanahan
*/
public class BasicAuthenticator extends AuthenticatorBase {
private final Log log = LogFactory.getLog(BasicAuthenticator.class); // must not be static
private Charset charset = StandardCharsets.ISO_8859_1;
private String charsetString = null;
private boolean trimCredentials = true;
public String getCharset() {
return charsetString;
}
public void setCharset(String charsetString) {
// Only acceptable options are null, "" or "UTF-8" (case insensitive)
if (charsetString == null || charsetString.isEmpty()) {
charset = StandardCharsets.ISO_8859_1;
} else if ("UTF-8".equalsIgnoreCase(charsetString)) {
charset = StandardCharsets.UTF_8;
} else {
throw new IllegalArgumentException(sm.getString("basicAuthenticator.invalidCharset"));
}
this.charsetString = charsetString;
}
public boolean getTrimCredentials() {
return trimCredentials;
}
public void setTrimCredentials(boolean trimCredentials) {
this.trimCredentials = trimCredentials;
}
@Override
protected boolean doAuthenticate(Request request, HttpServletResponse response)
throws IOException {
if (checkForCachedAuthentication(request, response, true)) {
return true;
}
// Validate any credentials already included with this request
MessageBytes authorization =
request.getCoyoteRequest().getMimeHeaders()
.getValue("authorization");
if (authorization != null) {
authorization.toBytes();
ByteChunk authorizationBC = authorization.getByteChunk();
BasicCredentials credentials = null;
try {
credentials = new BasicCredentials(authorizationBC, charset, getTrimCredentials());
String username = credentials.getUsername();
String password = credentials.getPassword();
Principal principal = context.getRealm().authenticate(username, password);
if (principal != null) {
register(request, response, principal,
HttpServletRequest.BASIC_AUTH, username, password);
return true;
}
}
catch (IllegalArgumentException iae) {
if (log.isDebugEnabled()) {
log.debug("Invalid Authorization" + iae.getMessage());
}
}
}
// the request could not be authenticated, so reissue the challenge
StringBuilder value = new StringBuilder(16);
value.append("Basic realm=\"");
value.append(getRealmName(context));
value.append('\"');
if (charsetString != null && !charsetString.isEmpty()) {
value.append(", charset=");
value.append(charsetString);
}
response.setHeader(AUTH_HEADER_NAME, value.toString());
response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
return false;
}
@Override
protected String getAuthMethod() {
return HttpServletRequest.BASIC_AUTH;
}
/**
* Parser for an HTTP Authorization header for BASIC authentication
* as per RFC 2617 section 2, and the Base64 encoded credentials as
* per RFC 2045 section 6.8.
*/
public static class BasicCredentials {
// the only authentication method supported by this parser
// note: we include single white space as its delimiter
private static final String METHOD = "basic ";
private final Charset charset;
private final boolean trimCredentials;
private final ByteChunk authorization;
private final int initialOffset;
private int base64blobOffset;
private int base64blobLength;
private String username = null;
private String password = null;
/**
* Parse the HTTP Authorization header for BASIC authentication
* as per RFC 2617 section 2, and the Base64 encoded credentials
* as per RFC 2045 section 6.8.
*
* @param input The header value to parse in-place
* @param charset The character set to use to convert the bytes to a
* string
*
* @throws IllegalArgumentException If the header does not conform
* to RFC 2617
* @deprecated Unused. Will be removed in Tomcat 10. Use 3-arg constructor
*/
@Deprecated
public BasicCredentials(ByteChunk input, Charset charset) throws IllegalArgumentException {
this(input, charset, true);
}
/**
* Parse the HTTP Authorization header for BASIC authentication
* as per RFC 2617 section 2, and the Base64 encoded credentials
* as per RFC 2045 section 6.8.
*
* @param input The header value to parse in-place
* @param charset The character set to use to convert the bytes
* to a string
* @param trimCredentials Should leading and trailing whitespace be
* removed from the parsed credentials
*
* @throws IllegalArgumentException If the header does not conform
* to RFC 2617
*/
public BasicCredentials(ByteChunk input, Charset charset, boolean trimCredentials)
throws IllegalArgumentException {
authorization = input;
initialOffset = input.getOffset();
this.charset = charset;
this.trimCredentials = trimCredentials;
parseMethod();
byte[] decoded = parseBase64();
parseCredentials(decoded);
}
/**
* Trivial accessor.
*
* @return the decoded username token as a String, which is
* never be <code>null</code>, but can be empty.
*/
public String getUsername() {
return username;
}
/**
* Trivial accessor.
*
* @return the decoded password token as a String, or <code>null</code>
* if no password was found in the credentials.
*/
public String getPassword() {
return password;
}
/*
* The authorization method string is case-insensitive and must
* hae at least one space character as a delimiter.
*/
private void parseMethod() throws IllegalArgumentException {
if (authorization.startsWithIgnoreCase(METHOD, 0)) {
// step past the auth method name
base64blobOffset = initialOffset + METHOD.length();
base64blobLength = authorization.getLength() - METHOD.length();
}
else {
// is this possible, or permitted?
throw new IllegalArgumentException(
"Authorization header method is not \"Basic\"");
}
}
/*
* Decode the base64-user-pass token, which RFC 2617 states
* can be longer than the 76 characters per line limit defined
* in RFC 2045. The base64 decoder will ignore embedded line
* break characters as well as surplus surrounding white space.
*/
private byte[] parseBase64() throws IllegalArgumentException {
byte[] decoded = Base64.decodeBase64(
authorization.getBuffer(),
base64blobOffset, base64blobLength);
// restore original offset
authorization.setOffset(initialOffset);
if (decoded == null) {
throw new IllegalArgumentException(
"Basic Authorization credentials are not Base64");
}
return decoded;
}
/*
* Extract the mandatory username token and separate it from the
* optional password token. Tolerate surplus surrounding white space.
*/
private void parseCredentials(byte[] decoded)
throws IllegalArgumentException {
int colon = -1;
for (int i = 0; i < decoded.length; i++) {
if (decoded[i] == ':') {
colon = i;
break;
}
}
if (colon < 0) {
username = new String(decoded, charset);
// password will remain null!
}
else {
username = new String(decoded, 0, colon, charset);
password = new String(decoded, colon + 1, decoded.length - colon - 1, charset);
// tolerate surplus white space around credentials
if (password.length() > 1 && trimCredentials) {
password = password.trim();
}
}
// tolerate surplus white space around credentials
if (username.length() > 1 && trimCredentials) {
username = username.trim();
}
}
}
}

View File

@@ -0,0 +1,102 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.catalina.authenticator;
public class Constants {
// Authentication methods for login configuration
// Servlet spec schemes are defined in HttpServletRequest
// Vendor specific schemes
public static final String SPNEGO_METHOD = "SPNEGO";
// Form based authentication constants
public static final String FORM_ACTION = "/j_security_check";
public static final String FORM_PASSWORD = "j_password";
public static final String FORM_USERNAME = "j_username";
// SPNEGO authentication constants
public static final String KRB5_CONF_PROPERTY = "java.security.krb5.conf";
public static final String DEFAULT_KRB5_CONF = "conf/krb5.ini";
public static final String JAAS_CONF_PROPERTY = "java.security.auth.login.config";
public static final String DEFAULT_JAAS_CONF = "conf/jaas.conf";
public static final String DEFAULT_LOGIN_MODULE_NAME = "com.sun.security.jgss.krb5.accept";
/**
* @deprecated Unused. Will be removed in Tomcat 9.
*/
@Deprecated
public static final String USE_SUBJECT_CREDS_ONLY_PROPERTY = "javax.security.auth.useSubjectCredsOnly";
// Cookie name for single sign on support
public static final String SINGLE_SIGN_ON_COOKIE = System.getProperty(
"org.apache.catalina.authenticator.Constants.SSO_SESSION_COOKIE_NAME", "JSESSIONIDSSO");
// --------------------------------------------------------- Request Notes
/**
* The notes key to track the single-sign-on identity with which this
* request is associated.
*/
public static final String REQ_SSOID_NOTE = "org.apache.catalina.request.SSOID";
public static final String REQ_JASPIC_SUBJECT_NOTE = "org.apache.catalina.authenticator.jaspic.SUBJECT";
// ---------------------------------------------------------- Session Notes
/**
* The session id used as a CSRF marker when redirecting a user's request.
*/
public static final String SESSION_ID_NOTE = "org.apache.catalina.authenticator.SESSION_ID";
/**
* If the <code>cache</code> property of our authenticator is set, and
* the current request is part of a session, authentication information
* will be cached to avoid the need for repeated calls to
* <code>Realm.authenticate()</code>, under the following keys:
*/
/**
* The notes key for the password used to authenticate this user.
*/
public static final String SESS_PASSWORD_NOTE = "org.apache.catalina.session.PASSWORD";
/**
* The notes key for the username used to authenticate this user.
*/
public static final String SESS_USERNAME_NOTE = "org.apache.catalina.session.USERNAME";
/**
* The following note keys are used during form login processing to
* cache required information prior to the completion of authentication.
*/
/**
* The previously authenticated principal (if caching is disabled).
*
* @deprecated Unused. Will be removed in Tomcat 10.
*/
@Deprecated
public static final String FORM_PRINCIPAL_NOTE = "org.apache.catalina.authenticator.PRINCIPAL";
/**
* The original request information, to which the user will be
* redirected if authentication succeeds.
*/
public static final String FORM_REQUEST_NOTE = "org.apache.catalina.authenticator.REQUEST";
}

View File

@@ -0,0 +1,652 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.catalina.authenticator;
import java.io.IOException;
import java.io.StringReader;
import java.nio.charset.StandardCharsets;
import java.security.Principal;
import java.util.LinkedHashMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.Realm;
import org.apache.catalina.connector.Request;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import org.apache.tomcat.util.http.parser.Authorization;
import org.apache.tomcat.util.security.ConcurrentMessageDigest;
import org.apache.tomcat.util.security.MD5Encoder;
/**
* An <b>Authenticator</b> and <b>Valve</b> implementation of HTTP DIGEST
* Authentication (see RFC 2069).
*
* @author Craig R. McClanahan
* @author Remy Maucherat
*/
public class DigestAuthenticator extends AuthenticatorBase {
private final Log log = LogFactory.getLog(DigestAuthenticator.class); // must not be static
// -------------------------------------------------------------- Constants
/**
* Tomcat's DIGEST implementation only supports auth quality of protection.
*/
protected static final String QOP = "auth";
// ----------------------------------------------------------- Constructors
public DigestAuthenticator() {
super();
setCache(false);
}
// ----------------------------------------------------- Instance Variables
/**
* List of server nonce values currently being tracked
*/
protected Map<String,NonceInfo> nonces;
/**
* The last timestamp used to generate a nonce. Each nonce should get a
* unique timestamp.
*/
protected long lastTimestamp = 0;
protected final Object lastTimestampLock = new Object();
/**
* Maximum number of server nonces to keep in the cache. If not specified,
* the default value of 1000 is used.
*/
protected int nonceCacheSize = 1000;
/**
* The window size to use to track seen nonce count values for a given
* nonce. If not specified, the default of 100 is used.
*/
protected int nonceCountWindowSize = 100;
/**
* Private key.
*/
protected String key = null;
/**
* How long server nonces are valid for in milliseconds. Defaults to 5
* minutes.
*/
protected long nonceValidity = 5 * 60 * 1000;
/**
* Opaque string.
*/
protected String opaque;
/**
* Should the URI be validated as required by RFC2617? Can be disabled in
* reverse proxies where the proxy has modified the URI.
*/
protected boolean validateUri = true;
// ------------------------------------------------------------- Properties
public int getNonceCountWindowSize() {
return nonceCountWindowSize;
}
public void setNonceCountWindowSize(int nonceCountWindowSize) {
this.nonceCountWindowSize = nonceCountWindowSize;
}
public int getNonceCacheSize() {
return nonceCacheSize;
}
public void setNonceCacheSize(int nonceCacheSize) {
this.nonceCacheSize = nonceCacheSize;
}
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public long getNonceValidity() {
return nonceValidity;
}
public void setNonceValidity(long nonceValidity) {
this.nonceValidity = nonceValidity;
}
public String getOpaque() {
return opaque;
}
public void setOpaque(String opaque) {
this.opaque = opaque;
}
public boolean isValidateUri() {
return validateUri;
}
public void setValidateUri(boolean validateUri) {
this.validateUri = validateUri;
}
// --------------------------------------------------------- Public Methods
/**
* Authenticate the user making this request, based on the specified
* login configuration. Return <code>true</code> if any specified
* constraint has been satisfied, or <code>false</code> 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 <code>true</code> 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.
* <p>
* The header MUST follow this template :
* <pre>
* 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" "=" &lt;"&gt; 1#URI &lt;"&gt;
* nonce = "nonce" "=" nonce-value
* nonce-value = quoted-string
* opaque = "opaque" "=" quoted-string
* stale = "stale" "=" ( "true" | "false" )
* algorithm = "algorithm" "=" ( "MD5" | token )
* </pre>
*
* @param request HTTP Servlet request
* @param response HTTP Servlet response
* @param nonce nonce token
* @param isNonceStale <code>true</code> 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<String, DigestAuthenticator.NonceInfo>() {
private static final long serialVersionUID = 1L;
private static final long LOG_SUPPRESS_TIME = 5 * 60 * 1000;
private long lastLog = 0;
@Override
protected boolean removeEldestEntry(
Map.Entry<String,NonceInfo> eldest) {
// This is called from a sync so keep it simple
long currentTime = System.currentTimeMillis();
if (size() > getNonceCacheSize()) {
if (lastLog < currentTime &&
currentTime - eldest.getValue().getTimestamp() <
getNonceValidity()) {
// Replay attack is possible
log.warn(sm.getString(
"digestAuthenticator.cacheRemove"));
lastLog = currentTime + LOG_SUPPRESS_TIME;
}
return true;
}
return false;
}
};
}
public static class DigestInfo {
private final String opaque;
private final long nonceValidity;
private final String key;
private final Map<String,NonceInfo> nonces;
private boolean validateUri = true;
private String userName = null;
private String method = null;
private String uri = null;
private String response = null;
private String nonce = null;
private String nc = null;
private String cnonce = null;
private String realmName = null;
private String qop = null;
private String opaqueReceived = null;
private boolean nonceStale = false;
public DigestInfo(String opaque, long nonceValidity, String key,
Map<String,NonceInfo> nonces, boolean validateUri) {
this.opaque = opaque;
this.nonceValidity = nonceValidity;
this.key = key;
this.nonces = nonces;
this.validateUri = validateUri;
}
public String getUsername() {
return userName;
}
public boolean parse(Request request, String authorization) {
// Validate the authorization credentials format
if (authorization == null) {
return false;
}
Map<String,String> directives;
try {
directives = Authorization.parseAuthorizationDigest(
new StringReader(authorization));
} catch (IOException e) {
return false;
}
if (directives == null) {
return false;
}
method = request.getMethod();
userName = directives.get("username");
realmName = directives.get("realm");
nonce = directives.get("nonce");
nc = directives.get("nc");
cnonce = directives.get("cnonce");
qop = directives.get("qop");
uri = directives.get("uri");
response = directives.get("response");
opaqueReceived = directives.get("opaque");
return true;
}
public boolean validate(Request request) {
if ( (userName == null) || (realmName == null) || (nonce == null)
|| (uri == null) || (response == null) ) {
return false;
}
// Validate the URI - should match the request line sent by client
if (validateUri) {
String uriQuery;
String query = request.getQueryString();
if (query == null) {
uriQuery = request.getRequestURI();
} else {
uriQuery = request.getRequestURI() + "?" + query;
}
if (!uri.equals(uriQuery)) {
// Some clients (older Android) use an absolute URI for
// DIGEST but a relative URI in the request line.
// request. 2.3.5 < fixed Android version <= 4.0.3
String host = request.getHeader("host");
String scheme = request.getScheme();
if (host != null && !uriQuery.startsWith(scheme)) {
StringBuilder absolute = new StringBuilder();
absolute.append(scheme);
absolute.append("://");
absolute.append(host);
absolute.append(uriQuery);
if (!uri.equals(absolute.toString())) {
return false;
}
} else {
return false;
}
}
}
// Validate the Realm name
String lcRealm = getRealmName(request.getContext());
if (!lcRealm.equals(realmName)) {
return false;
}
// Validate the opaque string
if (!opaque.equals(opaqueReceived)) {
return false;
}
// Validate nonce
int i = nonce.indexOf(':');
if (i < 0 || (i + 1) == nonce.length()) {
return false;
}
long nonceTime;
try {
nonceTime = Long.parseLong(nonce.substring(0, i));
} catch (NumberFormatException nfe) {
return false;
}
String md5clientIpTimeKey = nonce.substring(i + 1);
long currentTime = System.currentTimeMillis();
if ((currentTime - nonceTime) > nonceValidity) {
nonceStale = true;
synchronized (nonces) {
nonces.remove(nonce);
}
}
String serverIpTimeKey =
request.getRemoteAddr() + ":" + nonceTime + ":" + key;
byte[] buffer = ConcurrentMessageDigest.digestMD5(
serverIpTimeKey.getBytes(StandardCharsets.ISO_8859_1));
String md5ServerIpTimeKey = MD5Encoder.encode(buffer);
if (!md5ServerIpTimeKey.equals(md5clientIpTimeKey)) {
return false;
}
// Validate qop
if (qop != null && !QOP.equals(qop)) {
return false;
}
// Validate cnonce and nc
// Check if presence of nc and Cnonce is consistent with presence of qop
if (qop == null) {
if (cnonce != null || nc != null) {
return false;
}
} else {
if (cnonce == null || nc == null) {
return false;
}
// RFC 2617 says nc must be 8 digits long. Older Android clients
// use 6. 2.3.5 < fixed Android version <= 4.0.3
if (nc.length() < 6 || nc.length() > 8) {
return false;
}
long count;
try {
count = Long.parseLong(nc, 16);
} catch (NumberFormatException nfe) {
return false;
}
NonceInfo info;
synchronized (nonces) {
info = nonces.get(nonce);
}
if (info == null) {
// Nonce is valid but not in cache. It must have dropped out
// of the cache - force a re-authentication
nonceStale = true;
} else {
if (!info.nonceCountValid(count)) {
return false;
}
}
}
return true;
}
public boolean isNonceStale() {
return nonceStale;
}
public Principal authenticate(Realm realm) {
// Second MD5 digest used to calculate the digest :
// MD5(Method + ":" + uri)
String a2 = method + ":" + uri;
byte[] buffer = ConcurrentMessageDigest.digestMD5(
a2.getBytes(StandardCharsets.ISO_8859_1));
String md5a2 = MD5Encoder.encode(buffer);
return realm.authenticate(userName, response, nonce, nc, cnonce,
qop, realmName, md5a2);
}
}
public static class NonceInfo {
private final long timestamp;
private final boolean seen[];
private final int offset;
private int count = 0;
public NonceInfo(long currentTime, int seenWindowSize) {
this.timestamp = currentTime;
seen = new boolean[seenWindowSize];
offset = seenWindowSize / 2;
}
public synchronized boolean nonceCountValid(long nonceCount) {
if ((count - offset) >= nonceCount ||
(nonceCount > count - offset + seen.length)) {
return false;
}
int checkIndex = (int) ((nonceCount + offset) % seen.length);
if (seen[checkIndex]) {
return false;
} else {
seen[checkIndex] = true;
seen[count % seen.length] = false;
count++;
return true;
}
}
public long getTimestamp() {
return timestamp;
}
}
}

View File

@@ -0,0 +1,725 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.catalina.authenticator;
import java.io.IOException;
import java.io.InputStream;
import java.security.Principal;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.Locale;
import javax.servlet.RequestDispatcher;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.catalina.Realm;
import org.apache.catalina.Session;
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;
import org.apache.coyote.ActionCode;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import org.apache.tomcat.util.ExceptionUtils;
import org.apache.tomcat.util.buf.ByteChunk;
import org.apache.tomcat.util.buf.MessageBytes;
import org.apache.tomcat.util.descriptor.web.LoginConfig;
import org.apache.tomcat.util.http.MimeHeaders;
/**
* An <b>Authenticator</b> and <b>Valve</b> implementation of FORM BASED
* Authentication, as described in the Servlet API Specification.
*
* @author Craig R. McClanahan
* @author Remy Maucherat
*/
public class FormAuthenticator
extends AuthenticatorBase {
private final Log log = LogFactory.getLog(FormAuthenticator.class); // must not be static
// ----------------------------------------------------- Instance Variables
/**
* Character encoding to use to read the username and password parameters
* from the request. If not set, the encoding of the request body will be
* used.
*/
protected String characterEncoding = null;
/**
* Landing page to use if a user tries to access the login page directly or
* if the session times out during login. If not set, error responses will
* be sent instead.
*/
protected String landingPage = null;
// ------------------------------------------------------------- Properties
/**
* Return the character encoding to use to read the user name and password.
*
* @return The name of the character encoding
*/
public String getCharacterEncoding() {
return characterEncoding;
}
/**
* Set the character encoding to be used to read the user name and password.
*
* @param encoding The name of the encoding to use
*/
public void setCharacterEncoding(String encoding) {
characterEncoding = encoding;
}
/**
* Return the landing page to use when FORM auth is mis-used.
*
* @return The path to the landing page relative to the web application root
*/
public String getLandingPage() {
return landingPage;
}
/**
* Set the landing page to use when the FORM auth is mis-used.
*
* @param landingPage The path to the landing page relative to the web
* application root
*/
public void setLandingPage(String landingPage) {
this.landingPage = landingPage;
}
// ------------------------------------------------------ Protected Methods
/**
* Authenticate the user making this request, based on the specified
* login configuration. Return <code>true</code> if any specified
* constraint has been satisfied, or <code>false</code> 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 {
// References to objects we will need later
Session session = null;
Principal principal = null;
// Have we authenticated this user before but have caching disabled?
if (!cache) {
session = request.getSessionInternal(true);
if (log.isDebugEnabled()) {
log.debug("Checking for reauthenticate in session " + session);
}
String username = (String) session.getNote(Constants.SESS_USERNAME_NOTE);
String password = (String) session.getNote(Constants.SESS_PASSWORD_NOTE);
if (username != null && password != null) {
if (log.isDebugEnabled()) {
log.debug("Reauthenticating username '" + username + "'");
}
principal = context.getRealm().authenticate(username, password);
if (principal != null) {
register(request, response, principal, HttpServletRequest.FORM_AUTH, username, password);
if (!matchRequest(request)) {
return true;
}
}
if (log.isDebugEnabled()) {
log.debug("Reauthentication failed, proceed normally");
}
}
}
// Is this the re-submit of the original request URI after successful
// authentication? If so, forward the *original* request instead.
if (matchRequest(request)) {
session = request.getSessionInternal(true);
if (log.isDebugEnabled()) {
log.debug("Restore request from session '" + session.getIdInternal() + "'");
}
if (restoreRequest(request, session)) {
if (log.isDebugEnabled()) {
log.debug("Proceed to restored request");
}
return true;
} else {
if (log.isDebugEnabled()) {
log.debug("Restore of original request failed");
}
response.sendError(HttpServletResponse.SC_BAD_REQUEST);
return false;
}
}
// This check has to be after the previous check for a matching request
// because that matching request may also include a cached Principal.
if (checkForCachedAuthentication(request, response, true)) {
return true;
}
// Acquire references to objects we will need to evaluate
String contextPath = request.getContextPath();
String requestURI = request.getDecodedRequestURI();
// Is this the action request from the login page?
boolean loginAction = requestURI.startsWith(contextPath) && requestURI.endsWith(Constants.FORM_ACTION);
LoginConfig config = context.getLoginConfig();
// No -- Save this request and redirect to the form login page
if (!loginAction) {
// If this request was to the root of the context without a trailing
// '/', need to redirect to add it else the submit of the login form
// may not go to the correct web application
if (request.getServletPath().length() == 0 && request.getPathInfo() == null) {
StringBuilder location = new StringBuilder(requestURI);
location.append('/');
if (request.getQueryString() != null) {
location.append('?');
location.append(request.getQueryString());
}
response.sendRedirect(response.encodeRedirectURL(location.toString()));
return false;
}
session = request.getSessionInternal(true);
if (log.isDebugEnabled()) {
log.debug("Save request in session '" + session.getIdInternal() + "'");
}
try {
saveRequest(request, session);
} catch (IOException ioe) {
log.debug("Request body too big to save during authentication");
response.sendError(HttpServletResponse.SC_FORBIDDEN, sm.getString("authenticator.requestBodyTooBig"));
return false;
}
forwardToLoginPage(request, response, config);
return false;
}
// Yes -- Acknowledge the request, validate the specified credentials
// and redirect to the error page if they are not correct
request.getResponse().sendAcknowledgement();
Realm realm = context.getRealm();
if (characterEncoding != null) {
request.setCharacterEncoding(characterEncoding);
}
String username = request.getParameter(Constants.FORM_USERNAME);
String password = request.getParameter(Constants.FORM_PASSWORD);
if (log.isDebugEnabled()) {
log.debug("Authenticating username '" + username + "'");
}
principal = realm.authenticate(username, password);
if (principal == null) {
forwardToErrorPage(request, response, config);
return false;
}
if (log.isDebugEnabled()) {
log.debug("Authentication of '" + username + "' was successful");
}
if (session == null) {
session = request.getSessionInternal(false);
}
if (session != null && getChangeSessionIdOnAuthentication()) {
// Does session id match?
String expectedSessionId = (String) session.getNote(Constants.SESSION_ID_NOTE);
if (expectedSessionId == null || !expectedSessionId.equals(request.getRequestedSessionId())) {
session.expire();
session = null;
}
}
if (session == null) {
if (containerLog.isDebugEnabled()) {
containerLog.debug("User took so long to log on the session expired");
}
if (landingPage == null) {
response.sendError(
HttpServletResponse.SC_REQUEST_TIMEOUT, sm.getString("authenticator.sessionExpired"));
} else {
// Make the authenticator think the user originally requested
// the landing page
String uri = request.getContextPath() + landingPage;
SavedRequest saved = new SavedRequest();
saved.setMethod("GET");
saved.setRequestURI(uri);
saved.setDecodedRequestURI(uri);
request.getSessionInternal(true).setNote(Constants.FORM_REQUEST_NOTE, saved);
response.sendRedirect(response.encodeRedirectURL(uri));
}
return false;
}
register(request, response, principal, HttpServletRequest.FORM_AUTH, username, password);
// Redirect the user to the original request URI (which will cause
// the original request to be restored)
requestURI = savedRequestURL(session);
if (log.isDebugEnabled()) {
log.debug("Redirecting to original '" + requestURI + "'");
}
if (requestURI == null) {
if (landingPage == null) {
response.sendError(HttpServletResponse.SC_BAD_REQUEST, sm.getString("authenticator.formlogin"));
} else {
// Make the authenticator think the user originally requested
// the landing page
String uri = request.getContextPath() + landingPage;
SavedRequest saved = new SavedRequest();
saved.setMethod("GET");
saved.setRequestURI(uri);
saved.setDecodedRequestURI(uri);
session.setNote(Constants.FORM_REQUEST_NOTE, saved);
response.sendRedirect(response.encodeRedirectURL(uri));
}
} else {
// Until the Servlet API allows specifying the type of redirect to
// use.
Response internalResponse = request.getResponse();
String location = response.encodeRedirectURL(requestURI);
if ("HTTP/1.1".equals(request.getProtocol())) {
internalResponse.sendRedirect(location, HttpServletResponse.SC_SEE_OTHER);
} else {
internalResponse.sendRedirect(location, HttpServletResponse.SC_FOUND);
}
}
return false;
}
@Override
protected boolean isContinuationRequired(Request request) {
// Special handling for form-based logins to deal with the case
// where the login form (and therefore the "j_security_check" URI
// to which it submits) might be outside the secured area
String contextPath = this.context.getPath();
String decodedRequestURI = request.getDecodedRequestURI();
if (decodedRequestURI.startsWith(contextPath) &&
decodedRequestURI.endsWith(Constants.FORM_ACTION)) {
return true;
}
// Special handling for form-based logins to deal with the case where
// a resource is protected for some HTTP methods but not protected for
// GET which is used after authentication when redirecting to the
// protected resource.
// TODO: This is similar to the FormAuthenticator.matchRequest() logic
// Is there a way to remove the duplication?
Session session = request.getSessionInternal(false);
if (session != null) {
SavedRequest savedRequest = (SavedRequest) session.getNote(Constants.FORM_REQUEST_NOTE);
if (savedRequest != null &&
decodedRequestURI.equals(savedRequest.getDecodedRequestURI())) {
return true;
}
}
return false;
}
@Override
protected String getAuthMethod() {
return HttpServletRequest.FORM_AUTH;
}
@Override
protected void register(Request request, HttpServletResponse response,
Principal principal, String authType, String username,
String password, boolean alwaysUseSession, boolean cache) {
super.register(request, response, principal, authType, username, password, alwaysUseSession, cache);
// If caching an authenticated Principal is turned off,
// store username and password as session notes to use them for re-authentication.
if (!cache) {
Session session = request.getSessionInternal(false);
if (session != null) {
if (username != null) {
session.setNote(Constants.SESS_USERNAME_NOTE, username);
} else {
session.removeNote(Constants.SESS_USERNAME_NOTE);
}
if (password != null) {
session.setNote(Constants.SESS_PASSWORD_NOTE, password);
} else {
session.removeNote(Constants.SESS_PASSWORD_NOTE);
}
}
}
}
/**
* Called to forward to the login page
*
* @param request Request we are processing
* @param response Response we are populating
* @param config Login configuration describing how authentication
* should be performed
* @throws IOException If the forward to the login page fails and the call
* to {@link HttpServletResponse#sendError(int, String)}
* throws an {@link IOException}
*/
protected void forwardToLoginPage(Request request,
HttpServletResponse response, LoginConfig config)
throws IOException {
if (log.isDebugEnabled()) {
log.debug(sm.getString("formAuthenticator.forwardLogin",
request.getRequestURI(), request.getMethod(),
config.getLoginPage(), context.getName()));
}
String loginPage = config.getLoginPage();
if (loginPage == null || loginPage.length() == 0) {
String msg = sm.getString("formAuthenticator.noLoginPage",
context.getName());
log.warn(msg);
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
msg);
return;
}
if (getChangeSessionIdOnAuthentication()) {
Session session = request.getSessionInternal(false);
if (session != null) {
String newSessionId = changeSessionID(request, session);
session.setNote(Constants.SESSION_ID_NOTE, newSessionId);
}
}
// Always use GET for the login page, regardless of the method used
String oldMethod = request.getMethod();
request.getCoyoteRequest().method().setString("GET");
RequestDispatcher disp =
context.getServletContext().getRequestDispatcher(loginPage);
try {
if (context.fireRequestInitEvent(request.getRequest())) {
disp.forward(request.getRequest(), response);
context.fireRequestDestroyEvent(request.getRequest());
}
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
String msg = sm.getString("formAuthenticator.forwardLoginFail");
log.warn(msg, t);
request.setAttribute(RequestDispatcher.ERROR_EXCEPTION, t);
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
msg);
} finally {
// Restore original method so that it is written into access log
request.getCoyoteRequest().method().setString(oldMethod);
}
}
/**
* Called to forward to the error page
*
* @param request Request we are processing
* @param response Response we are populating
* @param config Login configuration describing how authentication
* should be performed
* @throws IOException If the forward to the error page fails and the call
* to {@link HttpServletResponse#sendError(int, String)}
* throws an {@link IOException}
*/
protected void forwardToErrorPage(Request request,
HttpServletResponse response, LoginConfig config)
throws IOException {
String errorPage = config.getErrorPage();
if (errorPage == null || errorPage.length() == 0) {
String msg = sm.getString("formAuthenticator.noErrorPage",
context.getName());
log.warn(msg);
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
msg);
return;
}
RequestDispatcher disp =
context.getServletContext().getRequestDispatcher(config.getErrorPage());
try {
if (context.fireRequestInitEvent(request.getRequest())) {
disp.forward(request.getRequest(), response);
context.fireRequestDestroyEvent(request.getRequest());
}
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
String msg = sm.getString("formAuthenticator.forwardErrorFail");
log.warn(msg, t);
request.setAttribute(RequestDispatcher.ERROR_EXCEPTION, t);
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
msg);
}
}
/**
* Does this request match the saved one (so that it must be the redirect
* we signaled after successful authentication?
*
* @param request The request to be verified
* @return <code>true</code> if the requests matched the saved one
*/
protected boolean matchRequest(Request request) {
// Has a session been created?
Session session = request.getSessionInternal(false);
if (session == null) {
return false;
}
// Is there a saved request?
SavedRequest sreq = (SavedRequest) session.getNote(Constants.FORM_REQUEST_NOTE);
if (sreq == null) {
return false;
}
// Is there a saved principal?
if (cache && session.getPrincipal() == null || !cache && request.getPrincipal() == null) {
return false;
}
// Does session id match?
if (getChangeSessionIdOnAuthentication()) {
String expectedSessionId = (String) session.getNote(Constants.SESSION_ID_NOTE);
if (expectedSessionId == null || !expectedSessionId.equals(request.getRequestedSessionId())) {
return false;
}
}
// Does the request URI match?
String decodedRequestURI = request.getDecodedRequestURI();
if (decodedRequestURI == null) {
return false;
}
return decodedRequestURI.equals(sreq.getDecodedRequestURI());
}
/**
* Restore the original request from information stored in our session.
* If the original request is no longer present (because the session
* timed out), return <code>false</code>; otherwise, return
* <code>true</code>.
*
* @param request The request to be restored
* @param session The session containing the saved information
* @return <code>true</code> if the request was successfully restored
* @throws IOException if an IO error occurred during the process
*/
protected boolean restoreRequest(Request request, Session session)
throws IOException {
// Retrieve and remove the SavedRequest object from our session
SavedRequest saved = (SavedRequest) session.getNote(Constants.FORM_REQUEST_NOTE);
session.removeNote(Constants.FORM_REQUEST_NOTE);
session.removeNote(Constants.SESSION_ID_NOTE);
if (saved == null) {
return false;
}
// Swallow any request body since we will be replacing it
// Need to do this before headers are restored as AJP connector uses
// content length header to determine how much data needs to be read for
// request body
byte[] buffer = new byte[4096];
InputStream is = request.createInputStream();
while (is.read(buffer) >= 0) {
// Ignore request body
}
// Modify our current request to reflect the original one
request.clearCookies();
Iterator<Cookie> cookies = saved.getCookies();
while (cookies.hasNext()) {
request.addCookie(cookies.next());
}
String method = saved.getMethod();
MimeHeaders rmh = request.getCoyoteRequest().getMimeHeaders();
rmh.recycle();
boolean cacheable = "GET".equalsIgnoreCase(method) ||
"HEAD".equalsIgnoreCase(method);
Iterator<String> names = saved.getHeaderNames();
while (names.hasNext()) {
String name = names.next();
// The browser isn't expecting this conditional response now.
// Assuming that it can quietly recover from an unexpected 412.
// BZ 43687
if(!("If-Modified-Since".equalsIgnoreCase(name) ||
(cacheable && "If-None-Match".equalsIgnoreCase(name)))) {
Iterator<String> values = saved.getHeaderValues(name);
while (values.hasNext()) {
rmh.addValue(name).setString(values.next());
}
}
}
request.clearLocales();
Iterator<Locale> locales = saved.getLocales();
while (locales.hasNext()) {
request.addLocale(locales.next());
}
request.getCoyoteRequest().getParameters().recycle();
ByteChunk body = saved.getBody();
if (body != null) {
request.getCoyoteRequest().action
(ActionCode.REQ_SET_BODY_REPLAY, body);
// Set content type
MessageBytes contentType = MessageBytes.newInstance();
// If no content type specified, use default for POST
String savedContentType = saved.getContentType();
if (savedContentType == null && "POST".equalsIgnoreCase(method)) {
savedContentType = "application/x-www-form-urlencoded";
}
contentType.setString(savedContentType);
request.getCoyoteRequest().setContentType(contentType);
}
request.getCoyoteRequest().method().setString(method);
// The method, URI, queryString and protocol are normally stored as
// bytes in the HttpInputBuffer and converted lazily to String. At this
// point, the method has already been set as String in the line above
// but the URI, queryString and protocol are still in byte form in the
// HttpInputBuffer. Processing the saved request body will overwrite
// these bytes. Configuring the HttpInputBuffer to retain these bytes as
// it would in a normal request would require some invasive API changes.
// Therefore force the conversion to String now so the correct values
// are presented if the application requests them.
request.getRequestURI();
request.getQueryString();
request.getProtocol();
return true;
}
/**
* Save the original request information into our session.
*
* @param request The request to be saved
* @param session The session to contain the saved information
* @throws IOException if an IO error occurred during the process
*/
protected void saveRequest(Request request, Session session)
throws IOException {
// Create and populate a SavedRequest object for this request
SavedRequest saved = new SavedRequest();
Cookie cookies[] = request.getCookies();
if (cookies != null) {
for (int i = 0; i < cookies.length; i++) {
saved.addCookie(cookies[i]);
}
}
Enumeration<String> names = request.getHeaderNames();
while (names.hasMoreElements()) {
String name = names.nextElement();
Enumeration<String> values = request.getHeaders(name);
while (values.hasMoreElements()) {
String value = values.nextElement();
saved.addHeader(name, value);
}
}
Enumeration<Locale> locales = request.getLocales();
while (locales.hasMoreElements()) {
Locale locale = locales.nextElement();
saved.addLocale(locale);
}
// May need to acknowledge a 100-continue expectation
request.getResponse().sendAcknowledgement();
int maxSavePostSize = request.getConnector().getMaxSavePostSize();
if (maxSavePostSize != 0) {
ByteChunk body = new ByteChunk();
body.setLimit(maxSavePostSize);
byte[] buffer = new byte[4096];
int bytesRead;
InputStream is = request.getInputStream();
while ( (bytesRead = is.read(buffer) ) >= 0) {
body.append(buffer, 0, bytesRead);
}
// Only save the request body if there is something to save
if (body.getLength() > 0) {
saved.setContentType(request.getContentType());
saved.setBody(body);
}
}
saved.setMethod(request.getMethod());
saved.setQueryString(request.getQueryString());
saved.setRequestURI(request.getRequestURI());
saved.setDecodedRequestURI(request.getDecodedRequestURI());
// Stash the SavedRequest in our session for later use
session.setNote(Constants.FORM_REQUEST_NOTE, saved);
}
/**
* Return the request URI (with the corresponding query string, if any)
* from the saved request so that we can redirect to it.
*
* @param session Our current session
* @return the original request URL
*/
protected String savedRequestURL(Session session) {
SavedRequest saved =
(SavedRequest) session.getNote(Constants.FORM_REQUEST_NOTE);
if (saved == null) {
return null;
}
StringBuilder sb = new StringBuilder(saved.getRequestURI());
if (saved.getQueryString() != null) {
sb.append('?');
sb.append(saved.getQueryString());
}
return sb.toString();
}
}

View File

@@ -0,0 +1,72 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
authenticator.certificates=No client certificate chain in this request
authenticator.changeSessionId=Session ID changed on authentication from [{0}] to [{1}]
authenticator.check.authorize=User name [{0}] obtained from the Connector and trusted to be valid. Obtaining roles for this user from the Tomcat Realm.
authenticator.check.authorizeFail=Realm did not recognise user [{0}]. Creating a Principal with that name and no roles.
authenticator.check.found=Already authenticated [{0}]
authenticator.check.sso=Not authenticated but SSO session ID [{0}] found. Attempting re-authentication.
authenticator.formlogin=Invalid direct reference to form login page
authenticator.jaspicCleanSubjectFail=Failed to clean JASPIC subject
authenticator.jaspicSecureResponseFail=Failed to secure response during JASPIC processing
authenticator.jaspicServerAuthContextFail=Failed to obtain a JASPIC ServerAuthContext instance
authenticator.loginFail=Login failed
authenticator.manager=Exception initializing trust managers
authenticator.noAuthHeader=No authorization header sent by client
authenticator.notContext=Configuration error: Must be attached to a Context
authenticator.requestBodyTooBig=The request body was too large to be cached during the authentication process
authenticator.sessionExpired=The time allowed for the login process has been exceeded. If you wish to continue you must either click back twice and re-click the link you requested or close and re-open your browser
authenticator.tomcatPrincipalLogoutFail=Logout with TomcatPrincipal instance has failed
authenticator.unauthorized=Cannot authenticate with the provided credentials
basicAuthenticator.invalidCharset=The only permitted values are null, the empty string or UTF-8
digestAuthenticator.cacheRemove=A valid entry has been removed from client nonce cache to make room for new entries. A replay attack is now possible. To prevent the possibility of replay attacks, reduce nonceValidity or increase nonceCacheSize. Further warnings of this type will be suppressed for 5 minutes.
formAuthenticator.forwardErrorFail=Unexpected error forwarding to error page
formAuthenticator.forwardLogin=Forwarding request for [{0}] made with method [{1}] to login page [{2}] of context [{3}] using request method GET
formAuthenticator.forwardLoginFail=Unexpected error forwarding to login page
formAuthenticator.noErrorPage=No error page was defined for FORM authentication in context [{0}]
formAuthenticator.noLoginPage=No login page was defined for FORM authentication in context [{0}]
singleSignOn.debug.associate=SSO associating application session [{1}] with SSO session [{0}]
singleSignOn.debug.associateFail=SSO failed to associate application session [{0}] since SSO session [{1}] does not exist
singleSignOn.debug.cookieCheck=SSO checking for SSO cookie
singleSignOn.debug.cookieNotFound=SSO did not find an SSO cookie
singleSignOn.debug.deregister=SSO expiring application session [{0}] associated with SSO session [{1}]
singleSignOn.debug.deregisterFail=SSO failed to deregister the SSO session [{0}] because it was not in the cache
singleSignOn.debug.deregisterNone=SSO deregistered the SSO session [{0}] but found no associated application sessions
singleSignOn.debug.hasPrincipal=SSO found previously authenticated Principal [{0}]
singleSignOn.debug.invoke=SSO processing request for [{0}]
singleSignOn.debug.principalCheck=SSO looking for a cached Principal for SSO session [{0}]
singleSignOn.debug.principalFound=SSO found cached Principal [{0}] with authentication type [{1}]
singleSignOn.debug.principalNotFound=SSO did not find a cached Principal. Erasing SSO cookie for session [{0}]
singleSignOn.debug.register=SSO registering SSO session [{0}] for user [{1}] with authentication type [{2}]
singleSignOn.debug.removeSession=SSO removing application session [{0}] from SSO session [{1}]
singleSignOn.debug.sessionLogout=SSO processing a log out for SSO session [{0}] and application session [{1}]
singleSignOn.debug.sessionTimeout=SSO processing a time out for SSO session [{0}] and application session [{1}]
singleSignOn.debug.update=SSO updating SSO session [{0}] to authentication type [{1}]
singleSignOn.sessionExpire.contextNotFound=SSO unable to expire session [{0}] because the Context could not be found
singleSignOn.sessionExpire.engineNull=SSO unable to expire session [{0}] because the Engine was null
singleSignOn.sessionExpire.hostNotFound=SSO unable to expire session [{0}] because the Host could not be found
singleSignOn.sessionExpire.managerError=SSO unable to expire session [{0}] because the Manager threw an Exception when searching for the session
singleSignOn.sessionExpire.managerNotFound=SSO unable to expire session [{0}] because the Manager could not be found
singleSignOn.sessionExpire.sessionNotFound=SSO unable to expire session [{0}] because the Session could not be found
spnegoAuthenticator.authHeaderNoToken=The Negotiate authorization header sent by the client did not include a token
spnegoAuthenticator.authHeaderNotNego=The authorization header sent by the client did not start with Negotiate
spnegoAuthenticator.serviceLoginFail=Unable to login as the service principal
spnegoAuthenticator.ticketValidateFail=Failed to validate client supplied ticket

View File

@@ -0,0 +1,23 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
authenticator.certificates=Keine Client Zertifikatskette im Request
authenticator.check.found=Bereits authentifiziert [{0}]
authenticator.jaspicCleanSubjectFail=Konnte JASPIC Subject nicht leeren.
authenticator.jaspicServerAuthContextFail=Kontte keine JASPIC ServerAuthContext Instanz erhalten
singleSignOn.debug.cookieCheck=SSO prüfe nach SSO Cookie
singleSignOn.debug.principalFound=SSO fand Principal [{0}] mut Authentication Typ [{1}] im Cache
singleSignOn.sessionExpire.hostNotFound=SSO kann Session [{0}] nicht ablaufen lassen, da der Host nicht gefunden werden konnte

View File

@@ -0,0 +1,44 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
authenticator.certificates=No hay cadena de certificados del cliente en esta petición
authenticator.formlogin=Referencia directa al formulario de conexión (página de formulario de login) inválida
authenticator.jaspicCleanSubjectFail=Fallo al limpiar el elemento JASPIC \n
authenticator.jaspicServerAuthContextFail=Fallo al intentar obtener una instancia JASPIC ServerAuthContext
authenticator.loginFail=No pude ingresar
authenticator.manager=Excepción inicializando administradores de confianza
authenticator.noAuthHeader=El cliente no ha enviado autorización de cabecera
authenticator.notContext=Error de Configuración: Debe de estar unido a un Contexto
authenticator.requestBodyTooBig=El cuerpo del requerimiento era demasiado grande para realizar caché durante el proceso de autenticación
authenticator.sessionExpired=El tiempo permitido para realizar login ha sido excedido. Si deseas continuar, debes hacer clik dos veces y volver a hacer clik otra vez o cerrar y reabrir tu navegador
authenticator.unauthorized=Imposible autenticar mediante las credenciales suministradas
digestAuthenticator.cacheRemove=Se ha quitado una entrada válida de la caché "nonce" del cliente para hacer espacio a nuevas entradas.. Ahora es posible un ataque de reinyección. Para prevenirlos, reduce "nonceValidity" o incrementa "nonceCacheSize". El resto de mensajes de este tipo serán suspendidos durante 5 minutos.
formAuthenticator.forwardErrorFail=Error inesperado de reenvío a página de error
formAuthenticator.forwardLoginFail=Error inesperado de reenvío a pagina de ingreso
formAuthenticator.noErrorPage=No se ha definido página de error para la autenticación FORM en el contexto [{0}]
formAuthenticator.noLoginPage=No se ha definido página de ingreso para la autenticación FORM en el contexto [{0}]
singleSignOn.debug.principalCheck=SSO esta buscando un Principal cacheado para las sesión SSO [{0}]\n
singleSignOn.debug.principalFound=SSO encontró el Principal cacheado [{0}] con autenticación tipo [{1}]\n
singleSignOn.debug.removeSession=SSO removiendo la sesión de la aplicación [{0}] SSO con sesión [{1}]\n
singleSignOn.sessionExpire.hostNotFound=SSO es incapaz de expirar la session [{0}] porque el Host no puede ser encontrado
singleSignOn.sessionExpire.managerError=SSO incapaz de expirar sesión [{0}] porque el Gerenciador lanzó una excepción mientras buscaba la sesión
spnegoAuthenticator.authHeaderNoToken=La cabecera de Negociación de autorización enviada por el cliente no incluía una ficha
spnegoAuthenticator.authHeaderNotNego=La cabecera de autorización enviada por el cliente no comenzaba con Negotiate
spnegoAuthenticator.serviceLoginFail=No puedo ingresar como director del servicio
spnegoAuthenticator.ticketValidateFail=No pude validar el billete suministrado por el cliente

View File

@@ -0,0 +1,72 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
authenticator.certificates=Aucune chaîne de certificat client (client certificate chain) dans cette requête
authenticator.changeSessionId=L''id de session a changé suite à l''authntification de [{0}] en [{1}]
authenticator.check.authorize=Le nom d''utilisateur [{0}] obtenu à partir du connecteur est considéré comme de valide et de confiance, les rôles sont obtenus à partir du royaume
authenticator.check.authorizeFail=Le royaume ne reconnait pas l''utilisateur [{0}], un principal a été crée avec ce nom mais sans rôles
authenticator.check.found=Déjà authentifié [{0}]
authenticator.check.sso=Pas d''authentification mais un session ID SSO [{0}] a été trouvé, nouvelle tentative d''authentification
authenticator.formlogin=Référence directe au formulaire de connexion (form login page) invalide
authenticator.jaspicCleanSubjectFail=Échec du nettoyage du sujet de JASPIC
authenticator.jaspicSecureResponseFail=Echec de la sécurisation de la réponse lors du traitement de JASPIC
authenticator.jaspicServerAuthContextFail=Échec d'obtention d'une instance JASPIC ServerAuthContext
authenticator.loginFail=Échec de connexion ("Login failed")
authenticator.manager=Exception lors de l'initialisation des gestionnaires d'authentification (trust managers)
authenticator.noAuthHeader=Aucun en-tête d'autorisation envoyé par le client
authenticator.notContext=Erreur de configuration: Doit être attaché à un contexte
authenticator.requestBodyTooBig=Le corps de la requête était trop grand pour être mis en cache pendant le processus d'authentification
authenticator.sessionExpired=Le temps alloué au processus de login est échu. Si vous désirez continuer, veuillez soit retourner en arrière 2 fois et recliquer le lien demandé, soit fermer et ré-ouvrir votre navigateur
authenticator.tomcatPrincipalLogoutFail=La déconnection avec l'instance de TomcatPrincipal a échoué
authenticator.unauthorized=Impossible d'authentifier avec les crédits fournis (provided credentials)
basicAuthenticator.invalidCharset=Les seules valeurs permises sont null, la chaîne vide, ou des caractères UTF-8
digestAuthenticator.cacheRemove=Une entrée valide du cache de nonce des clients a été enlevée pour faire de la place pour de nouvelles entrées, ce qui rend possible une attaque par répétition; pour éviter cela, il est possible de reduire nonceValidity ou d'augmenter nonceCacheSize; les avertissements de ce type ne se reproduiront pas avant 5 minutes
formAuthenticator.forwardErrorFail=Erreur inattendue lors de la transmission à la page d'erreur
formAuthenticator.forwardLogin=Transmission de la requête pour [{0}] faite avec la méthode [{1}] à la page de connection [{2}] du contexte [{3}] en utilisant la méthode GET
formAuthenticator.forwardLoginFail=Erreur inattendue lors de la transmission à la page de connection
formAuthenticator.noErrorPage=Aucune page d''erreur n''a été définie pour la méthode d''authentification FORM dans le contexte [{0}]
formAuthenticator.noLoginPage=Aucune page de connection n''a été définie pour la méthode d''authentification FORM dans le contexte [{0}]
singleSignOn.debug.associate=Association de la session [{1}] de l''application avec la session SSO [{0}]
singleSignOn.debug.associateFail=Le SSO n''a pu associer la session [{0}] de l''application car la session SSO [{1}] n''existe pas
singleSignOn.debug.cookieCheck=Le SSO recherche un cookie SSO.
singleSignOn.debug.cookieNotFound=Le SSO n'a pas trouvé de cookie SSO
singleSignOn.debug.deregister=Le SSO expire la session [{0}] de l''application associée à la session SSO [{1}]
singleSignOn.debug.deregisterFail=Le SSO n''a pu déenregistrer la session SSO [{0}] parce qu''elle n''est pas dans le cache
singleSignOn.debug.deregisterNone=Le SSO a désenregistré la session SSO [{0}] mais n''a trouvé aucune session d''application associée
singleSignOn.debug.hasPrincipal=Le SSO a trouvé un principal [{0}] précédemment authentifié
singleSignOn.debug.invoke=Le SSO traite la requête pour [{0}]
singleSignOn.debug.principalCheck=Le SSO recherche le Principal en cache pour la session SSO [{0}]
singleSignOn.debug.principalFound=Le SSO a trouvé en cache le Principal [{0}] avec le type d''authentification [{1}]
singleSignOn.debug.principalNotFound=Le SSO n''a pas trouvé de principal en cache, le cookie SSO de la session [{0}] est effacé
singleSignOn.debug.register=Enregistrement de la session SSO [{0}] pour l''utilisateur [{1}] avec le type d''authentification [{2}]
singleSignOn.debug.removeSession=Le SSO retire la session applicative [{0}] de la session SSO [{1}]
singleSignOn.debug.sessionLogout=Le SSO effectue une déconnection pour la session SSO [{0}] et la session [{1}] de l''application
singleSignOn.debug.sessionTimeout=Le SSO traite un timeout pour la session SSO [{0}] et la session [{1}] de l''application
singleSignOn.debug.update=Le SSO met à jour la session SSO [{0}] avec le type d''authentification [{1}]
singleSignOn.sessionExpire.contextNotFound=Le SSO n''a pu faire expirer la session [{0}] parce que le contexte n''a pas été trouvé
singleSignOn.sessionExpire.engineNull=Le SSO n''a pu faire expirer la session [{0}] parce que le moteur est null
singleSignOn.sessionExpire.hostNotFound=SSO ne peut pas expirer le session [{0}] parce l''hôte ("Host") n''a a pas été trouvé
singleSignOn.sessionExpire.managerError=Impossible d''expirer la session [{0}] parce que le Manager a lancé une exception lors de la recherche de la session
singleSignOn.sessionExpire.managerNotFound=Le SSO n''a pu faire expirer la session [{0}] parce que le gestionnaire de sessions n''a pas été trouvé
singleSignOn.sessionExpire.sessionNotFound=Impossible d''expirer la session [{0}] parce que la session n''a pas été trouvée
spnegoAuthenticator.authHeaderNoToken=L'en-tête de négociation dautorisation ("Negotiate authorization header") envoyé par le client n'incluait pas de jeton ("token")
spnegoAuthenticator.authHeaderNotNego=L'en-tête d'autorisation envoyé par le client ne commence pas par Negotiate
spnegoAuthenticator.serviceLoginFail=Impossible de se connecteur en tant que principal de service
spnegoAuthenticator.ticketValidateFail=Impossible de valider le ticket fourni par le client

View File

@@ -0,0 +1,72 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
authenticator.certificates=このリクエストにはクライアント認証チェーンがありません
authenticator.changeSessionId=認証時に[{0}]から[{1}]にセッションIDが変更されました。
authenticator.check.authorize=Connector から取得したユーザー名 [{0}] を正当なものとして信頼します。ユーザーのロールは Tomcat Realmから取得します。
authenticator.check.authorizeFail=Realm がユーザー[{0}]を認識しませんでした。 その名前とロールのないプリンシパルを作成します。
authenticator.check.found=既に認証された[{0}]
authenticator.check.sso=認証されていませんが、SSOセッションID [{0}]が見つかりました。 再認証を試みます。
authenticator.formlogin=フォームログインページへの無効な直接参照です
authenticator.jaspicCleanSubjectFail=JASPIC の cleanSubject が失敗しました。
authenticator.jaspicSecureResponseFail=JASPIC処理中のsecure レスポンスに失敗しました
authenticator.jaspicServerAuthContextFail=JASPIC ServerAuthContext インスタンスの取得に失敗しました。
authenticator.loginFail=ログイン失敗
authenticator.manager=トラストマネージャを初期化中の例外です
authenticator.noAuthHeader=クライアントは認証ヘッダーを送信しませんでした。
authenticator.notContext=設定エラー: コンテキストに指定しなければいけません
authenticator.requestBodyTooBig=認証処理中にリクエストボディが大きすぎてキャッシュされませんでした。
authenticator.sessionExpired=ログインプロセスに認められていた時間が過ぎました。継続したいならばバックボタンを2度押してから再度リンクを押すかブラウザを立ち上げ直してください
authenticator.tomcatPrincipalLogoutFail=TomcatPrincipal インスタンスによるログアウトが失敗しました。
authenticator.unauthorized=提供された証明書で認証できません
basicAuthenticator.invalidCharset=指定できる値は、null、空の文字列またはUTF-8です。
digestAuthenticator.cacheRemove=有効なエントリがクライアントのnonceキャッシュから削除され、新しいエントリのためのスペースが確保されました。 リプレイ攻撃が可能になりました。 リプレイ攻撃の可能性を防ぐには、nonceValidityを減らすか、nonceCacheSizeを増やしてください。 このタイプの警告は5分間表示されなくなります。
formAuthenticator.forwardErrorFail=予期せぬ異常によりエラーページへ転送します。
formAuthenticator.forwardLogin=リクエストメソッドGETを使用してコンテキスト[{3}]のページ[{2}]にメソッド[{1}]で行われた[{0}]の要求をフォワードします。
formAuthenticator.forwardLoginFail=ログインページへの転送での予期しないエラー
formAuthenticator.noErrorPage=コンテキスト[{0}]のFORM認証にエラーページが定義されていません
formAuthenticator.noLoginPage=コンテキスト[{0}]のFORM認証にログインページが定義されていません。
singleSignOn.debug.associate=SSOはアプリケーションセッション[{1}]をSSOセッション[{0}]に関連付けます
singleSignOn.debug.associateFail=SSOセッション[{1}]が存在しないため、SSOはアプリケーションセッション[{0}]を関連付けられませんでした。
singleSignOn.debug.cookieCheck=SSOがSSOクッキーをチェックします
singleSignOn.debug.cookieNotFound=SSOはSSO Cookieを検出しませんでした。
singleSignOn.debug.deregister=SSOセッション[{1}]に関連付けられたアプリケーションセッション[{0}]を期限切れにします。
singleSignOn.debug.deregisterFail=キャッシュにないため、SSOセッション[{0}]の登録を解除できませんでした。
singleSignOn.debug.deregisterNone=SSOセッション[{0}]の登録を解除しましたが、関連するアプリケーションセッションが見つかりませんでした。
singleSignOn.debug.hasPrincipal=SSOが以前に認証されたプリンシパル[{0}]を検出しました
singleSignOn.debug.invoke=[{0}]に対するSSO処理リクエスト
singleSignOn.debug.principalCheck=SSOセッションのキャッシュされたプリンシパルを探すSSO [{0}]
singleSignOn.debug.principalFound=SSO のキャッシュされたプリンシパル [{0}] を取得しました。認証タイプは [{1}] です。
singleSignOn.debug.principalNotFound=SSOはキャッシュされたプリンシパルを検出しませんでした。 セッション[{0}]のSSO Cookieを消去しています。
singleSignOn.debug.register=認証タイプ[{2}]のユーザー[{1}]のSSOセッション[{0}]を登録しているSSO
singleSignOn.debug.removeSession=SSOセッション[{1}]からのアプリケーションセッション[{0}]の削除
singleSignOn.debug.sessionLogout=SSOセッション[{0}]とアプリケーションセッション[{1}]のログアウトを処理するSSO。
singleSignOn.debug.sessionTimeout=SSOはSSOセッション[{0}]とアプリケーションセッション[{1}]のタイムアウト処理中
singleSignOn.debug.update=SSOはSSOセッション[{0}]を認証タイプ[{1}]に更新します。
singleSignOn.sessionExpire.contextNotFound=Contextが見つからないため、SSOはセッション[{0}]を期限切れにできません
singleSignOn.sessionExpire.engineNull=Engine がNullだったため、SSOはセッション[{0}]を期限切れにできません。
singleSignOn.sessionExpire.hostNotFound=ホストが見つからないため SSO セッション [{0}] を失効できません。
singleSignOn.sessionExpire.managerError=セッションを検索するときにManagerが例外をスローしたため、SSOはセッション[{0}]を期限切れにできません
singleSignOn.sessionExpire.managerNotFound=Managerが見つからなかったので、SSOはセッション[{0}]を期限切れにできません。
singleSignOn.sessionExpire.sessionNotFound=セッションが見つかりませんでしたので、SSOはセッション[{0}]を期限切れにできません。
spnegoAuthenticator.authHeaderNoToken=クライアントから受信した Negoiate 認証ヘッダにはトークンがありません。
spnegoAuthenticator.authHeaderNotNego=クライアントから受信した認証ヘッダーは Negotiate から始まっていません。
spnegoAuthenticator.serviceLoginFail=サービスプリンシパルとしてログインできません
spnegoAuthenticator.ticketValidateFail=クライアント提供のチケットの検証に失敗しました。

View File

@@ -0,0 +1,72 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
authenticator.certificates=이 요청에 클라인트 인증서 체인이 없습니다.
authenticator.changeSessionId=인증 처리 시, 세션 ID를 [{0}]에서 [{1}](으)로 변경했습니다.
authenticator.check.authorize=사용자 이름 [{0}]을(를) Connector로부터 얻었으며, 이는 유효한 것으로 신뢰되었습니다. Tomcat Realm으로부터, 이 사용자를 위한 역할들을 구합니다.
authenticator.check.authorizeFail=Realm이 사용자 [{0}]을(를) 인식하지 못했습니다. 해당 사용자명에 대해 아무런 역할 없이 Principal을 생성합니다.
authenticator.check.found=[{0}]은(는) 이미 인증되었습니다.
authenticator.check.sso=인증되지 않았는데, SSO 세션 ID [{0}]이(가) 발견되었습니다. 다시 인증을 시도합니다.
authenticator.formlogin=폼 로그인 페이지에 대한 유효하지 않은 직접 참조
authenticator.jaspicCleanSubjectFail=JASPIC subject를 제거하지 못했습니다.
authenticator.jaspicSecureResponseFail=JASPIC 처리 중 응답을 보안처리 하지 못했습니다.
authenticator.jaspicServerAuthContextFail=JASPIC ServerAuthContext 인스턴스를 획득하지 못했습니다.
authenticator.loginFail=로그인 실패
authenticator.manager=Trust 매니저들을 초기화하는 중 예외 발생
authenticator.noAuthHeader=클라이언트가 authorization 헤더를 보내지 않았습니다.
authenticator.notContext=설정 오류: 컨텍스트에 설정되어야만 합니다.
authenticator.requestBodyTooBig=요청의 body가 너무 커서, 인증 처리 과정에서 캐시에 저장될 수 없습니다.
authenticator.sessionExpired=로그인 처리 허용 시간이 초과되었습니다. 계속하시려면 뒤로 가기를 두번 클릭한 후, 요청했던 링크를 다시 클릭하거나, 브라우저를 닫았다가 다시 시작해야 합니다.
authenticator.tomcatPrincipalLogoutFail=TomcatPrincipal 인스턴스를 사용한 로그아웃 시도가 실패했습니다.
authenticator.unauthorized=제공된 credentials를 사용하여 인증할 수 없습니다.
basicAuthenticator.invalidCharset=허용된 값들은 오직 널, 빈 문자열, 또는 UTF-8 문자열입니다.
digestAuthenticator.cacheRemove=새로운 엔트리들을 위한 공간을 만들기 위해, client nonce cache로부터 유효한 엔트리를 제거했습니다. 리플레이 공격이 가능해진 상태입니다. 가능성 있는 리플레이 공격들을 방지하려면, nonceValidity를 감소 시키거나, nonceCacheSize를 증가 시키십시오. 더 이상 이러한 종류의 경고 메시지들은 향후 5분 동안 나오지 않을 것입니다.
formAuthenticator.forwardErrorFail=오류 페이지로 forward하는 중 예기치 않은 오류 발생
formAuthenticator.forwardLogin=메소드 [{1}]을(를) 사용한 [{0}]에 대한 요청을, 컨텍스트 [{3}]의 로그인 페이지 [{2}](으)로, GET 요청 메소드를 사용하여 forward 합니다.
formAuthenticator.forwardLoginFail=로그인 페이지로 forward하는 중 예기치 않은 오류 발생
formAuthenticator.noErrorPage=컨텍스트 [{0}]에서 폼 기반 인증을 위한 오류 페이지가 정의되지 않았습니다.
formAuthenticator.noLoginPage=컨텍스트 [{0}]에서, 폼 기반 인증을 위한 로그인 페이지가 정의되지 않았습니다.
singleSignOn.debug.associate=SSO가, 애플리케이션 세션 [{1}]을(를) SSO 세션 [{0}]와(과) 연관시킵니다.
singleSignOn.debug.associateFail=SSO 세션 [{1}]이(가) 존재하지 않기 때문에, SSO가 애플리케이션 세션 [{0}]을(를) 연관시키지 못했습니다.
singleSignOn.debug.cookieCheck=SSO가, SSO 쿠키가 존재하는지 점검합니다.
singleSignOn.debug.cookieNotFound=SSO가, SSO 쿠키를 찾지 못했습니다.
singleSignOn.debug.deregister=SSO가, SSO 세션 [{1}]와(과) 연관된 애플리케이션 세션 [{0}]을(를) 만료시킵니다.
singleSignOn.debug.deregisterFail=SSO 세션 [{0}]이(가) 캐시에 존재하지 않기 때문에, SSO가 해당 SSO 세션에 대한 등록을 제거하지 못했습니다.
singleSignOn.debug.deregisterNone=SSO가 SSO 세션 [{0}]의 등록을 제거했으나, 연관된 애플리케이션 세션들을 찾지 못했습니다.
singleSignOn.debug.hasPrincipal=SSO가 이전에 인증된 Principal [{0}]을(를) 발견했습니다.
singleSignOn.debug.invoke=SSO가 [{0}]을(를) 위해 요청을 처리합니다.
singleSignOn.debug.principalCheck=SSO 세션 [{0}]을(를) 위하여, SSO가 캐시된 Principal을 찾습니다.
singleSignOn.debug.principalFound=인증 타입이 [{1}]인, 캐시된 Principal [{0}]을(를), SSO가 발견했습니다.
singleSignOn.debug.principalNotFound=SSO가 캐시된 Principal을 찾지 못했습니다. 세션 [{0}]을(를) 위한 SSO 쿠키를 지웁니다.
singleSignOn.debug.register=사용자 [{1}]을(를) 위해, 인증 타입 [{2}]을 사용하여, SSO가 SSO 세션 [{0}]을(를) 등록합니다.
singleSignOn.debug.removeSession=SSO가 SSO 세션 [{1}](으)로부터 세션 [{0}]을(를) 제거합니다.
singleSignOn.debug.sessionLogout=SSO 세션 [{0}]와(과) 애플리케이션 세션 [{1}]을(를) 위해, SSO가 로그아웃을 처리 중
singleSignOn.debug.sessionTimeout=SSO 세션 [{0}]와(과) 애플리케이션 세션 [{1}]을(를) 위한, SSO 처리가 제한 시간 초과되었습니다.
singleSignOn.debug.update=SSO가, SSO 세션 [{0}]의 인증 타입을 [{1}](으)로 변경합니다.
singleSignOn.sessionExpire.contextNotFound=컨텍스트를 찾을 수 없기 때문에, SSO가 세션 [{0}]을(를) 만료시킬 수 없습니다.
singleSignOn.sessionExpire.engineNull=엔진이 널이기 때문에 SSO가 세션 [{0}]을(를) 만료시킬 수 없습니다.
singleSignOn.sessionExpire.hostNotFound=호스트를 찾을 수 없어서, SSO가 세션 [{0}]을(를) 만료시킬 수 없습니다.
singleSignOn.sessionExpire.managerError=세션을 찾는 동안 매니저가 예외를 발생시켜, SSO가 세션 [{0}]을(를) 만료시킬 수 없습니다.
singleSignOn.sessionExpire.managerNotFound=매니저를 찾을 수 없기 때문에, SSO가 세션 [{0}]을(를) 만료시킬 수 없습니다.
singleSignOn.sessionExpire.sessionNotFound=세션을 찾을 수 없기 때문에, SSO가 세션 [{0}]을(를) 만료시킬 수 없습니다.
spnegoAuthenticator.authHeaderNoToken=클라이언트에 의해 전송된 Negotiate authorization 헤더가 토큰을 포함하지 않았습니다.
spnegoAuthenticator.authHeaderNotNego=클라이언트가 보낸 Authorization 헤더가 Negotiate로 시작하지 않았습니다.
spnegoAuthenticator.serviceLoginFail=서비스 Principal로서 로그인 할 수 없습니다.
spnegoAuthenticator.ticketValidateFail=클라이언트에 의해 제공된 티켓이 유효한지를 확인하지 못했습니다.

View File

@@ -0,0 +1,53 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
authenticator.certificates=此请求中没有客户端证书链
authenticator.changeSessionId=在身份验证时, 会话 ID 从 [{0} 更改为 [{1}]
authenticator.check.found=已通过身份验证 [{0}]
authenticator.check.sso=未经过身份验证但找到了SSO会话ID [{0}]。尝试重新验证。
authenticator.formlogin=对表单登录页的直接引用无效
authenticator.jaspicCleanSubjectFail=清除 JASPIC 主题失败
authenticator.jaspicSecureResponseFail=在JASPIC处理期间无法保证响应
authenticator.jaspicServerAuthContextFail=失败的获取一个JASPIC ServerAuthContext 实例
authenticator.loginFail=登录失败
authenticator.manager=初始化信任管理器异常
authenticator.noAuthHeader=客户端未发送授权请求头
authenticator.notContext=配置错误:必须被附属于一个上下文
authenticator.requestBodyTooBig=请求正文太大,无法在身份验证过程中进行缓存
authenticator.sessionExpired=已超出登录过程所允许的时间。 如果您希望继续,则必须单击两次后退并重新单击您请求的链接或先关闭然后重新打开浏览器
authenticator.tomcatPrincipalLogoutFail=使用TomcatPrincipal实例注销失败
authenticator.unauthorized=无法使用提供的凭据进行身份验证
basicAuthenticator.invalidCharset=只允许值为null、空字符串或UTF-8
digestAuthenticator.cacheRemove=已从客户端 nonce 缓存中删除有效条目以便为新条目腾出空间。重播攻击现在是可能的。为防止重播攻击的可能性请降低nonceValidity或增加nonceCacheSize。此类型的进一步警告将被抑制5分钟。
formAuthenticator.noErrorPage=没有为上下文[{0}]中的表单身份验证定义错误页
singleSignOn.debug.associate=SSO将应用程序会话[{1}]与SSO会话[{0}]关联
singleSignOn.debug.cookieCheck=SSO检查SSO cookie
singleSignOn.debug.cookieNotFound=SSO没有找到SSO cookie
singleSignOn.debug.deregisterFail=SSO撤销登记SSO会话[{0}]失败因为缓存中不包含这个SSO会话
singleSignOn.debug.hasPrincipal=找到以前经过身份验证的主体[{0}]
singleSignOn.debug.invoke=SSO为[{0}]处理请求
singleSignOn.debug.principalCheck=SSO为SSO会话[{0}]寻找缓存的Principal
singleSignOn.debug.principalFound=SSO 找到了带着认证类型的缓存代理
singleSignOn.debug.removeSession=SSO 从 SSO session [{1}] 中删除应用程序会话 [{0}]
singleSignOn.debug.update=SSO 更新SSO 会话[{0}] 对认证 类型[{1}]
singleSignOn.sessionExpire.hostNotFound=因为 Host 丢失SSO 无法使 session [{0}] 失效
singleSignOn.sessionExpire.managerError=由于会话管理器在检索会话时抛出异常,导致单点登录无法使会话[{0}]失效
singleSignOn.sessionExpire.managerNotFound=SSO无法使会话[{0}]过期,因为找不到管理器
spnegoAuthenticator.authHeaderNoToken=客户端发送的协商授权 header 未包含 token

View File

@@ -0,0 +1,110 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.catalina.authenticator;
import java.io.IOException;
import javax.servlet.http.HttpServletResponse;
import org.apache.catalina.connector.Request;
/**
* An <b>Authenticator</b> and <b>Valve</b> implementation that checks
* only security constraints not involving user authentication.
*
* @author Craig R. McClanahan
*/
public final class NonLoginAuthenticator extends AuthenticatorBase {
// --------------------------------------------------------- Public Methods
/**
* <p>Authenticate the user making this request, based on the fact that no
* <code>login-config</code> has been defined for the container.</p>
*
* <p>This implementation means "login the user even though there is no
* self-contained way to establish a security Principal for that user".</p>
*
* <p>This method is called by the AuthenticatorBase super class to
* establish a Principal for the user BEFORE the container security
* constraints are examined, i.e. it is not yet known whether the user
* will eventually be permitted to access the requested resource.
* Therefore, it is necessary to always return <code>true</code> to
* indicate the user has not failed authentication.</p>
*
* <p>There are two cases:</p>
* <ul>
* <li>without SingleSignon: a Session instance does not yet exist
* and there is no <code>auth-method</code> to authenticate the
* user, so leave Request's Principal as null.
* Note: AuthenticatorBase will later examine the security constraints
* to determine whether the resource is accessible by a user
* without a security Principal and Role (i.e. unauthenticated).
* </li>
* <li>with SingleSignon: if the user has already authenticated via
* another container (using its own login configuration), then
* associate this Session with the SSOEntry so it inherits the
* already-established security Principal and associated Roles.
* Note: This particular session will become a full member of the
* SingleSignOnEntry Session collection and so will potentially
* keep the SSOE "alive", even if all the other properly
* authenticated Sessions expire first... until it expires too.
* </li>
* </ul>
*
* @param request Request we are processing
* @param response Response we are creating
* @return boolean to indicate whether the user is authenticated
* @exception IOException if an input/output error occurs
*/
@Override
protected boolean doAuthenticate(Request request, HttpServletResponse response)
throws IOException {
// Don't try and use SSO to authenticate since there is no auth
// configured for this web application
if (checkForCachedAuthentication(request, response, true)) {
// Save the inherited Principal in this session so it can remain
// authenticated until it expires.
if (cache) {
request.getSessionInternal(true).setPrincipal(request.getPrincipal());
}
return true;
}
// No Principal means the user is not already authenticated
// and so will not be assigned any roles. It is safe to
// to say the user is now authenticated because access to
// protected resources will only be allowed with a matching role.
// i.e. SC_FORBIDDEN (403 status) will be generated later.
if (containerLog.isDebugEnabled())
containerLog.debug("User authenticated without any roles");
return true;
}
/**
* Return the authentication method, which is vendor-specific and
* not defined by HttpServletRequest.
*/
@Override
protected String getAuthMethod() {
return "NONE";
}
}

View File

@@ -0,0 +1,103 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.catalina.authenticator;
import java.io.IOException;
import java.security.Principal;
import java.security.cert.X509Certificate;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.catalina.connector.Request;
/**
* An <b>Authenticator</b> and <b>Valve</b> implementation of authentication
* that utilizes SSL certificates to identify client users.
*
* @author Craig R. McClanahan
*/
public class SSLAuthenticator extends AuthenticatorBase {
// --------------------------------------------------------- Public Methods
/**
* Authenticate the user by checking for the existence of a certificate
* chain, validating it against the trust manager for the connector and then
* validating the user's identity against the configured Realm.
*
* @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 CLIENT-CERT 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;
}
// Retrieve the certificate chain for this client
if (containerLog.isDebugEnabled()) {
containerLog.debug(" Looking up certificates");
}
X509Certificate certs[] = getRequestCertificates(request);
if ((certs == null) || (certs.length < 1)) {
if (containerLog.isDebugEnabled()) {
containerLog.debug(" No certificates included with this request");
}
response.sendError(HttpServletResponse.SC_UNAUTHORIZED,
sm.getString("authenticator.certificates"));
return false;
}
// Authenticate the specified certificate chain
Principal principal = context.getRealm().authenticate(certs);
if (principal == null) {
if (containerLog.isDebugEnabled()) {
containerLog.debug(" Realm.authenticate() returned false");
}
response.sendError(HttpServletResponse.SC_UNAUTHORIZED,
sm.getString("authenticator.unauthorized"));
return false;
}
// Cache the principal (if requested) and record this authentication
register(request, response, principal,
HttpServletRequest.CLIENT_CERT_AUTH, null, null);
return true;
}
@Override
protected String getAuthMethod() {
return HttpServletRequest.CLIENT_CERT_AUTH;
}
}

View File

@@ -0,0 +1,188 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.catalina.authenticator;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import javax.servlet.http.Cookie;
import org.apache.tomcat.util.buf.ByteChunk;
/**
* Object that saves the critical information from a request so that
* form-based authentication can reproduce it once the user has been
* authenticated.
* <p>
* <b>IMPLEMENTATION NOTE</b> - It is assumed that this object is accessed
* only from the context of a single thread, so no synchronization around
* internal collection classes is performed.
*
* @author Craig R. McClanahan
*/
public final class SavedRequest {
/**
* The set of Cookies associated with this Request.
*/
private final List<Cookie> cookies = new ArrayList<>();
public void addCookie(Cookie cookie) {
cookies.add(cookie);
}
public Iterator<Cookie> getCookies() {
return cookies.iterator();
}
/**
* The set of Headers associated with this Request. Each key is a header
* name, while the value is a List containing one or more actual
* values for this header. The values are returned as an Iterator when
* you ask for them.
*/
private final Map<String, List<String>> headers = new HashMap<>();
public void addHeader(String name, String value) {
List<String> values = headers.get(name);
if (values == null) {
values = new ArrayList<>();
headers.put(name, values);
}
values.add(value);
}
public Iterator<String> getHeaderNames() {
return headers.keySet().iterator();
}
public Iterator<String> getHeaderValues(String name) {
List<String> values = headers.get(name);
if (values == null)
return Collections.emptyIterator();
else
return values.iterator();
}
/**
* The set of Locales associated with this Request.
*/
private final List<Locale> locales = new ArrayList<>();
public void addLocale(Locale locale) {
locales.add(locale);
}
public Iterator<Locale> getLocales() {
return locales.iterator();
}
/**
* The request method used on this Request.
*/
private String method = null;
public String getMethod() {
return this.method;
}
public void setMethod(String method) {
this.method = method;
}
/**
* The query string associated with this Request.
*/
private String queryString = null;
public String getQueryString() {
return this.queryString;
}
public void setQueryString(String queryString) {
this.queryString = queryString;
}
/**
* The request URI associated with this Request.
*/
private String requestURI = null;
public String getRequestURI() {
return this.requestURI;
}
public void setRequestURI(String requestURI) {
this.requestURI = requestURI;
}
/**
* The decode request URI associated with this Request. Path parameters are
* also excluded
*/
private String decodedRequestURI = null;
public String getDecodedRequestURI() {
return this.decodedRequestURI;
}
public void setDecodedRequestURI(String decodedRequestURI) {
this.decodedRequestURI = decodedRequestURI;
}
/**
* The body of this request.
*/
private ByteChunk body = null;
public ByteChunk getBody() {
return this.body;
}
public void setBody(ByteChunk body) {
this.body = body;
}
/**
* The content type of the request, used if this is a POST.
*/
private String contentType = null;
public String getContentType() {
return this.contentType;
}
public void setContentType(String contentType) {
this.contentType = contentType;
}
}

View File

@@ -0,0 +1,619 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.catalina.authenticator;
import java.io.IOException;
import java.security.Principal;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import org.apache.catalina.Container;
import org.apache.catalina.Context;
import org.apache.catalina.Engine;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.Manager;
import org.apache.catalina.Realm;
import org.apache.catalina.Session;
import org.apache.catalina.SessionListener;
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;
import org.apache.catalina.valves.ValveBase;
import org.apache.tomcat.util.res.StringManager;
/**
* A <strong>Valve</strong> that supports a "single sign on" user experience,
* where the security identity of a user who successfully authenticates to one
* web application is propagated to other web applications in the same
* security domain. For successful use, the following requirements must
* be met:
* <ul>
* <li>This Valve must be configured on the Container that represents a
* virtual host (typically an implementation of <code>Host</code>).</li>
* <li>The <code>Realm</code> that contains the shared user and role
* information must be configured on the same Container (or a higher
* one), and not overridden at the web application level.</li>
* <li>The web applications themselves must use one of the standard
* Authenticators found in the
* <code>org.apache.catalina.authenticator</code> package.</li>
* </ul>
*
* @author Craig R. McClanahan
*/
public class SingleSignOn extends ValveBase {
private static final StringManager sm = StringManager.getManager(SingleSignOn.class);
/* The engine at the top of the container hierarchy in which this SSO Valve
* has been placed. It is used to get back to a session object from a
* SingleSignOnSessionKey and is updated when the Valve starts and stops.
*/
private Engine engine;
//------------------------------------------------------ Constructor
public SingleSignOn() {
super(true);
}
// ----------------------------------------------------- Instance Variables
/**
* The cache of SingleSignOnEntry instances for authenticated Principals,
* keyed by the cookie value that is used to select them.
*/
protected Map<String,SingleSignOnEntry> cache = new ConcurrentHashMap<>();
/**
* Indicates whether this valve should require a downstream Authenticator to
* reauthenticate each request, or if it itself can bind a UserPrincipal
* and AuthType object to the request.
*/
private boolean requireReauthentication = false;
/**
* Optional SSO cookie domain.
*/
private String cookieDomain;
// ------------------------------------------------------------- Properties
/**
* Returns the optional cookie domain.
* May return null.
*
* @return The cookie domain
*/
public String getCookieDomain() {
return cookieDomain;
}
/**
* Sets the domain to be used for sso cookies.
*
* @param cookieDomain cookie domain name
*/
public void setCookieDomain(String cookieDomain) {
if (cookieDomain != null && cookieDomain.trim().length() == 0) {
this.cookieDomain = null;
} else {
this.cookieDomain = cookieDomain;
}
}
/**
* Gets whether each request needs to be reauthenticated (by an
* Authenticator downstream in the pipeline) to the security
* <code>Realm</code>, or if this Valve can itself bind security info
* to the request based on the presence of a valid SSO entry without
* rechecking with the <code>Realm</code>.
*
* @return <code>true</code> if it is required that a downstream
* Authenticator reauthenticate each request before calls to
* <code>HttpServletRequest.setUserPrincipal()</code>
* and <code>HttpServletRequest.setAuthType()</code> are made;
* <code>false</code> if the <code>Valve</code> can itself make
* those calls relying on the presence of a valid SingleSignOn
* entry associated with the request.
*
* @see #setRequireReauthentication
*/
public boolean getRequireReauthentication() {
return requireReauthentication;
}
/**
* Sets whether each request needs to be reauthenticated (by an
* Authenticator downstream in the pipeline) to the security
* <code>Realm</code>, or if this Valve can itself bind security info
* to the request, based on the presence of a valid SSO entry, without
* rechecking with the <code>Realm</code>.
* <p>
* If this property is <code>false</code> (the default), this
* <code>Valve</code> will bind a UserPrincipal and AuthType to the request
* if a valid SSO entry is associated with the request. It will not notify
* the security <code>Realm</code> of the incoming request.
* <p>
* This property should be set to <code>true</code> if the overall server
* configuration requires that the <code>Realm</code> reauthenticate each
* request thread. An example of such a configuration would be one where
* the <code>Realm</code> implementation provides security for both a
* web tier and an associated EJB tier, and needs to set security
* credentials on each request thread in order to support EJB access.
* <p>
* If this property is set to <code>true</code>, this Valve will set flags
* on the request notifying the downstream Authenticator that the request
* is associated with an SSO session. The Authenticator will then call its
* {@link AuthenticatorBase#reauthenticateFromSSO reauthenticateFromSSO}
* method to attempt to reauthenticate the request to the
* <code>Realm</code>, using any credentials that were cached with this
* Valve.
* <p>
* The default value of this property is <code>false</code>, in order
* to maintain backward compatibility with previous versions of Tomcat.
*
* @param required <code>true</code> if it is required that a downstream
* Authenticator reauthenticate each request before calls
* to <code>HttpServletRequest.setUserPrincipal()</code>
* and <code>HttpServletRequest.setAuthType()</code> are
* made; <code>false</code> if the <code>Valve</code> can
* itself make those calls relying on the presence of a
* valid SingleSignOn entry associated with the request.
*
* @see AuthenticatorBase#reauthenticateFromSSO
*/
public void setRequireReauthentication(boolean required) {
this.requireReauthentication = required;
}
// ---------------------------------------------------------- Valve Methods
/**
* Perform single-sign-on support processing for this request.
*
* @param request The servlet request we are processing
* @param response The servlet response we are creating
*
* @exception IOException if an input/output error occurs
* @exception ServletException if a servlet error occurs
*/
@Override
public void invoke(Request request, Response response)
throws IOException, ServletException {
request.removeNote(Constants.REQ_SSOID_NOTE);
// Has a valid user already been authenticated?
if (containerLog.isDebugEnabled()) {
containerLog.debug(sm.getString("singleSignOn.debug.invoke", request.getRequestURI()));
}
if (request.getUserPrincipal() != null) {
if (containerLog.isDebugEnabled()) {
containerLog.debug(sm.getString("singleSignOn.debug.hasPrincipal",
request.getUserPrincipal().getName()));
}
getNext().invoke(request, response);
return;
}
// Check for the single sign on cookie
if (containerLog.isDebugEnabled()) {
containerLog.debug(sm.getString("singleSignOn.debug.cookieCheck"));
}
Cookie cookie = null;
Cookie cookies[] = request.getCookies();
if (cookies != null) {
for (int i = 0; i < cookies.length; i++) {
if (Constants.SINGLE_SIGN_ON_COOKIE.equals(cookies[i].getName())) {
cookie = cookies[i];
break;
}
}
}
if (cookie == null) {
if (containerLog.isDebugEnabled()) {
containerLog.debug(sm.getString("singleSignOn.debug.cookieNotFound"));
}
getNext().invoke(request, response);
return;
}
// Look up the cached Principal associated with this cookie value
if (containerLog.isDebugEnabled()) {
containerLog.debug(sm.getString("singleSignOn.debug.principalCheck",
cookie.getValue()));
}
SingleSignOnEntry entry = cache.get(cookie.getValue());
if (entry != null) {
if (containerLog.isDebugEnabled()) {
containerLog.debug(sm.getString("singleSignOn.debug.principalFound",
entry.getPrincipal() != null ? entry.getPrincipal().getName() : "",
entry.getAuthType()));
}
request.setNote(Constants.REQ_SSOID_NOTE, cookie.getValue());
// Only set security elements if reauthentication is not required
if (!getRequireReauthentication()) {
request.setAuthType(entry.getAuthType());
request.setUserPrincipal(entry.getPrincipal());
}
} else {
if (containerLog.isDebugEnabled()) {
containerLog.debug(sm.getString("singleSignOn.debug.principalNotFound",
cookie.getValue()));
}
// No need to return a valid SSO session ID
cookie.setValue("REMOVE");
// Age of zero will trigger removal
cookie.setMaxAge(0);
// Domain and path have to match the original cookie to 'replace'
// the original cookie
cookie.setPath("/");
String domain = getCookieDomain();
if (domain != null) {
cookie.setDomain(domain);
}
// This is going to trigger a Set-Cookie header. While the value is
// not security sensitive, ensure that expectations for secure and
// httpOnly are met
cookie.setSecure(request.isSecure());
if (request.getServletContext().getSessionCookieConfig().isHttpOnly() ||
request.getContext().getUseHttpOnly()) {
cookie.setHttpOnly(true);
}
response.addCookie(cookie);
}
// Invoke the next Valve in our pipeline
getNext().invoke(request, response);
}
// ------------------------------------------------------ Protected Methods
/**
* Process a session destroyed event by removing references to that session
* from the caches and - if the session destruction is the result of a
* logout - destroy the associated SSO session.
*
* @param ssoId The ID of the SSO session which which the destroyed
* session was associated
* @param session The session that has been destroyed
*/
public void sessionDestroyed(String ssoId, Session session) {
if (!getState().isAvailable()) {
return;
}
// Was the session destroyed as the result of a timeout or context stop?
// If so, we'll just remove the expired session from the SSO. If the
// session was logged out, we'll log out of all session associated with
// the SSO.
if (((session.getMaxInactiveInterval() > 0)
&& (session.getIdleTimeInternal() >= session.getMaxInactiveInterval() * 1000))
|| (!session.getManager().getContext().getState().isAvailable())) {
if (containerLog.isDebugEnabled()) {
containerLog.debug(sm.getString("singleSignOn.debug.sessionTimeout",
ssoId, session));
}
removeSession(ssoId, session);
} else {
// The session was logged out.
// Deregister this single session id, invalidating
// associated sessions
if (containerLog.isDebugEnabled()) {
containerLog.debug(sm.getString("singleSignOn.debug.sessionLogout",
ssoId, session));
}
// First remove the session that we know has expired / been logged
// out since it has already been removed from its Manager and, if
// we don't remove it first, deregister() will log a warning that it
// can't be found
removeSession(ssoId, session);
// If the SSO session was only associated with one web app the call
// above will have removed the SSO session from the cache
if (cache.containsKey(ssoId)) {
deregister(ssoId);
}
}
}
/**
* Associate the specified single sign on identifier with the
* specified Session.
*
* @param ssoId Single sign on identifier
* @param session Session to be associated
*
* @return <code>true</code> if the session was associated to the given SSO
* session, otherwise <code>false</code>
*/
protected boolean associate(String ssoId, Session session) {
SingleSignOnEntry sso = cache.get(ssoId);
if (sso == null) {
if (containerLog.isDebugEnabled()) {
containerLog.debug(sm.getString("singleSignOn.debug.associateFail",
ssoId, session));
}
return false;
} else {
if (containerLog.isDebugEnabled()) {
containerLog.debug(sm.getString("singleSignOn.debug.associate",
ssoId, session));
}
sso.addSession(this, ssoId, session);
return true;
}
}
/**
* Deregister the specified single sign on identifier, and invalidate
* any associated sessions.
*
* @param ssoId Single sign on identifier to deregister
*/
protected void deregister(String ssoId) {
// Look up and remove the corresponding SingleSignOnEntry
SingleSignOnEntry sso = cache.remove(ssoId);
if (sso == null) {
if (containerLog.isDebugEnabled()) {
containerLog.debug(sm.getString("singleSignOn.debug.deregisterFail", ssoId));
}
return;
}
// Expire any associated sessions
Set<SingleSignOnSessionKey> ssoKeys = sso.findSessions();
if (ssoKeys.size() == 0) {
if (containerLog.isDebugEnabled()) {
containerLog.debug(sm.getString("singleSignOn.debug.deregisterNone", ssoId));
}
}
for (SingleSignOnSessionKey ssoKey : ssoKeys) {
if (containerLog.isDebugEnabled()) {
containerLog.debug(sm.getString("singleSignOn.debug.deregister", ssoKey, ssoId));
}
// Invalidate this session
expire(ssoKey);
}
// NOTE: Clients may still possess the old single sign on cookie,
// but it will be removed on the next request since it is no longer
// in the cache
}
private void expire(SingleSignOnSessionKey key) {
if (engine == null) {
containerLog.warn(sm.getString("singleSignOn.sessionExpire.engineNull", key));
return;
}
Container host = engine.findChild(key.getHostName());
if (host == null) {
containerLog.warn(sm.getString("singleSignOn.sessionExpire.hostNotFound", key));
return;
}
Context context = (Context) host.findChild(key.getContextName());
if (context == null) {
containerLog.warn(sm.getString("singleSignOn.sessionExpire.contextNotFound", key));
return;
}
Manager manager = context.getManager();
if (manager == null) {
containerLog.warn(sm.getString("singleSignOn.sessionExpire.managerNotFound", key));
return;
}
Session session = null;
try {
session = manager.findSession(key.getSessionId());
} catch (IOException e) {
containerLog.warn(sm.getString("singleSignOn.sessionExpire.managerError", key), e);
return;
}
if (session == null) {
containerLog.warn(sm.getString("singleSignOn.sessionExpire.sessionNotFound", key));
return;
}
session.expire();
}
/**
* Attempts reauthentication to the given <code>Realm</code> using
* the credentials associated with the single sign-on session
* identified by argument <code>ssoId</code>.
* <p>
* If reauthentication is successful, the <code>Principal</code> and
* authorization type associated with the SSO session will be bound
* to the given <code>Request</code> object via calls to
* {@link Request#setAuthType Request.setAuthType()} and
* {@link Request#setUserPrincipal Request.setUserPrincipal()}
* </p>
*
* @param ssoId identifier of SingleSignOn session with which the
* caller is associated
* @param realm Realm implementation against which the caller is to
* be authenticated
* @param request the request that needs to be authenticated
*
* @return <code>true</code> if reauthentication was successful,
* <code>false</code> otherwise.
*/
protected boolean reauthenticate(String ssoId, Realm realm,
Request request) {
if (ssoId == null || realm == null) {
return false;
}
boolean reauthenticated = false;
SingleSignOnEntry entry = cache.get(ssoId);
if (entry != null && entry.getCanReauthenticate()) {
String username = entry.getUsername();
if (username != null) {
Principal reauthPrincipal =
realm.authenticate(username, entry.getPassword());
if (reauthPrincipal != null) {
reauthenticated = true;
// Bind the authorization credentials to the request
request.setAuthType(entry.getAuthType());
request.setUserPrincipal(reauthPrincipal);
}
}
}
return reauthenticated;
}
/**
* Register the specified Principal as being associated with the specified
* value for the single sign on identifier.
*
* @param ssoId Single sign on identifier to register
* @param principal Associated user principal that is identified
* @param authType Authentication type used to authenticate this
* user principal
* @param username Username used to authenticate this user
* @param password Password used to authenticate this user
*/
protected void register(String ssoId, Principal principal, String authType,
String username, String password) {
if (containerLog.isDebugEnabled()) {
containerLog.debug(sm.getString("singleSignOn.debug.register", ssoId,
principal != null ? principal.getName() : "", authType));
}
cache.put(ssoId, new SingleSignOnEntry(principal, authType, username, password));
}
/**
* Updates any <code>SingleSignOnEntry</code> found under key
* <code>ssoId</code> with the given authentication data.
* <p>
* The purpose of this method is to allow an SSO entry that was
* established without a username/password combination (i.e. established
* following DIGEST or CLIENT_CERT authentication) to be updated with
* a username and password if one becomes available through a subsequent
* BASIC or FORM authentication. The SSO entry will then be usable for
* reauthentication.
* <p>
* <b>NOTE:</b> Only updates the SSO entry if a call to
* <code>SingleSignOnEntry.getCanReauthenticate()</code> returns
* <code>false</code>; otherwise, it is assumed that the SSO entry already
* has sufficient information to allow reauthentication and that no update
* is needed.
*
* @param ssoId identifier of Single sign to be updated
* @param principal the <code>Principal</code> returned by the latest
* call to <code>Realm.authenticate</code>.
* @param authType the type of authenticator used (BASIC, CLIENT_CERT,
* DIGEST or FORM)
* @param username the username (if any) used for the authentication
* @param password the password (if any) used for the authentication
*
* @return <code>true</code> if the credentials were updated, otherwise
* <code>false</code>
*/
protected boolean update(String ssoId, Principal principal, String authType,
String username, String password) {
SingleSignOnEntry sso = cache.get(ssoId);
if (sso != null && !sso.getCanReauthenticate()) {
if (containerLog.isDebugEnabled()) {
containerLog.debug(sm.getString("singleSignOn.debug.update", ssoId, authType));
}
sso.updateCredentials(principal, authType, username, password);
return true;
}
return false;
}
/**
* Remove a single Session from a SingleSignOn. Called when
* a session is timed out and no longer active.
*
* @param ssoId Single sign on identifier from which to remove the session.
* @param session the session to be removed.
*/
protected void removeSession(String ssoId, Session session) {
if (containerLog.isDebugEnabled()) {
containerLog.debug(sm.getString("singleSignOn.debug.removeSession", session, ssoId));
}
// Get a reference to the SingleSignOn
SingleSignOnEntry entry = cache.get(ssoId);
if (entry == null) {
return;
}
// Remove the inactive session from SingleSignOnEntry
entry.removeSession(session);
// If there are not sessions left in the SingleSignOnEntry,
// deregister the entry.
if (entry.findSessions().size() == 0) {
deregister(ssoId);
}
}
protected SessionListener getSessionListener(String ssoId) {
return new SingleSignOnListener(ssoId);
}
@Override
protected synchronized void startInternal() throws LifecycleException {
Container c = getContainer();
while (c != null && !(c instanceof Engine)) {
c = c.getParent();
}
if (c != null) {
engine = (Engine) c;
}
super.startInternal();
}
@Override
protected synchronized void stopInternal() throws LifecycleException {
super.stopInternal();
engine = null;
}
}

View File

@@ -0,0 +1,216 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.catalina.authenticator;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.security.Principal;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import javax.servlet.http.HttpServletRequest;
import org.apache.catalina.Session;
/**
* A class that represents entries in the cache of authenticated users.
* This is necessary to make it available to
* <code>AuthenticatorBase</code> subclasses that need it in order to perform
* reauthentications when SingleSignOn is in use.
*
* @author B Stansberry, based on work by Craig R. McClanahan
*
* @see SingleSignOn
* @see AuthenticatorBase#reauthenticateFromSSO
*/
public class SingleSignOnEntry implements Serializable {
private static final long serialVersionUID = 1L;
// ------------------------------------------------------ Instance Fields
private String authType = null;
private String password = null;
// Marked as transient so special handling can be applied to serialization
private transient Principal principal = null;
private final ConcurrentMap<SingleSignOnSessionKey,SingleSignOnSessionKey> sessionKeys =
new ConcurrentHashMap<>();
private String username = null;
private boolean canReauthenticate = false;
// --------------------------------------------------------- Constructors
/**
* Creates a new SingleSignOnEntry
*
* @param principal the <code>Principal</code> returned by the latest
* call to <code>Realm.authenticate</code>.
* @param authType the type of authenticator used (BASIC, CLIENT_CERT,
* DIGEST or FORM)
* @param username the username (if any) used for the authentication
* @param password the password (if any) used for the authentication
*/
public SingleSignOnEntry(Principal principal, String authType,
String username, String password) {
updateCredentials(principal, authType, username, password);
}
// ------------------------------------------------------- Package Methods
/**
* Adds a <code>Session</code> to the list of those associated with
* this SSO.
*
* @param sso The <code>SingleSignOn</code> valve that is managing
* the SSO session.
* @param ssoId The ID of the SSO session.
* @param session The <code>Session</code> being associated with the SSO.
*/
public void addSession(SingleSignOn sso, String ssoId, Session session) {
SingleSignOnSessionKey key = new SingleSignOnSessionKey(session);
SingleSignOnSessionKey currentKey = sessionKeys.putIfAbsent(key, key);
if (currentKey == null) {
// Session not previously added
session.addSessionListener(sso.getSessionListener(ssoId));
}
}
/**
* Removes the given <code>Session</code> from the list of those
* associated with this SSO.
*
* @param session the <code>Session</code> to remove.
*/
public void removeSession(Session session) {
SingleSignOnSessionKey key = new SingleSignOnSessionKey(session);
sessionKeys.remove(key);
}
/**
* Returns the HTTP Session identifiers associated with this SSO.
*
* @return The identifiers for the HTTP sessions that are current associated
* with this SSo entry
*/
public Set<SingleSignOnSessionKey> findSessions() {
return sessionKeys.keySet();
}
/**
* Gets the name of the authentication type originally used to authenticate
* the user associated with the SSO.
*
* @return "BASIC", "CLIENT_CERT", "DIGEST", "FORM" or "NONE"
*/
public String getAuthType() {
return this.authType;
}
/**
* Gets whether the authentication type associated with the original
* authentication supports reauthentication.
*
* @return <code>true</code> if <code>getAuthType</code> returns
* "BASIC" or "FORM", <code>false</code> otherwise.
*/
public boolean getCanReauthenticate() {
return this.canReauthenticate;
}
/**
* Gets the password credential (if any) associated with the SSO.
*
* @return the password credential associated with the SSO, or
* <code>null</code> if the original authentication type
* does not involve a password.
*/
public String getPassword() {
return this.password;
}
/**
* Gets the <code>Principal</code> that has been authenticated by the SSO.
*
* @return The Principal that was created by the authentication that
* triggered the creation of the SSO entry
*/
public Principal getPrincipal() {
return this.principal;
}
/**
* Gets the user name provided by the user as part of the authentication
* process.
*
* @return The user name that was authenticated as part of the
* authentication that triggered the creation of the SSO entry
*/
public String getUsername() {
return this.username;
}
/**
* Updates the SingleSignOnEntry to reflect the latest security
* information associated with the caller.
*
* @param principal the <code>Principal</code> returned by the latest
* call to <code>Realm.authenticate</code>.
* @param authType the type of authenticator used (BASIC, CLIENT_CERT,
* DIGEST or FORM)
* @param username the username (if any) used for the authentication
* @param password the password (if any) used for the authentication
*/
public synchronized void updateCredentials(Principal principal, String authType,
String username, String password) {
this.principal = principal;
this.authType = authType;
this.username = username;
this.password = password;
this.canReauthenticate = (HttpServletRequest.BASIC_AUTH.equals(authType) ||
HttpServletRequest.FORM_AUTH.equals(authType));
}
private void writeObject(ObjectOutputStream out) throws IOException {
out.defaultWriteObject();
if (principal instanceof Serializable) {
out.writeBoolean(true);
out.writeObject(principal);
} else {
out.writeBoolean(false);
}
}
private void readObject(ObjectInputStream in) throws IOException,
ClassNotFoundException {
in.defaultReadObject();
boolean hasPrincipal = in.readBoolean();
if (hasPrincipal) {
principal = (Principal) in.readObject();
}
}
}

View File

@@ -0,0 +1,61 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.catalina.authenticator;
import java.io.Serializable;
import org.apache.catalina.Authenticator;
import org.apache.catalina.Context;
import org.apache.catalina.Manager;
import org.apache.catalina.Session;
import org.apache.catalina.SessionEvent;
import org.apache.catalina.SessionListener;
public class SingleSignOnListener implements SessionListener, Serializable {
private static final long serialVersionUID = 1L;
private final String ssoId;
public SingleSignOnListener(String ssoId) {
this.ssoId = ssoId;
}
@Override
public void sessionEvent(SessionEvent event) {
if (!Session.SESSION_DESTROYED_EVENT.equals(event.getType())) {
return;
}
Session session = event.getSession();
Manager manager = session.getManager();
if (manager == null) {
return;
}
Context context = manager.getContext();
Authenticator authenticator = context.getAuthenticator();
if (!(authenticator instanceof AuthenticatorBase)) {
return;
}
SingleSignOn sso = ((AuthenticatorBase) authenticator).sso;
if (sso == null) {
return;
}
sso.sessionDestroyed(ssoId, session);
}
}

View File

@@ -0,0 +1,122 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.catalina.authenticator;
import java.io.Serializable;
import org.apache.catalina.Context;
import org.apache.catalina.Session;
/**
* Key used by SSO to identify a session. This key is used rather than the
* actual session to facilitate the replication of the SSO information
* across a cluster where replicating the entire session would generate
* significant, unnecessary overhead.
*
*/
public class SingleSignOnSessionKey implements Serializable {
private static final long serialVersionUID = 1L;
private final String sessionId;
private final String contextName;
private final String hostName;
public SingleSignOnSessionKey(Session session) {
this.sessionId = session.getId();
Context context = session.getManager().getContext();
this.contextName = context.getName();
this.hostName = context.getParent().getName();
}
public String getSessionId() {
return sessionId;
}
public String getContextName() {
return contextName;
}
public String getHostName() {
return hostName;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result +
((sessionId == null) ? 0 : sessionId.hashCode());
result = prime * result +
((contextName == null) ? 0 : contextName.hashCode());
result = prime * result +
((hostName == null) ? 0 : hostName.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
SingleSignOnSessionKey other = (SingleSignOnSessionKey) obj;
if (sessionId == null) {
if (other.sessionId != null) {
return false;
}
} else if (!sessionId.equals(other.sessionId)) {
return false;
}
if (contextName == null) {
if (other.contextName != null) {
return false;
}
} else if (!contextName.equals(other.contextName)) {
return false;
}
if (hostName == null) {
if (other.hostName != null) {
return false;
}
} else if (!hostName.equals(other.hostName)) {
return false;
}
return true;
}
@Override
public String toString() {
// Session ID is 32. Standard text is 36. Host could easily be 20+.
// Context could be anything from 0 upwards. 128 seems like a reasonable
// size to accommodate most cases without being too big.
StringBuilder sb = new StringBuilder(128);
sb.append("Host: [");
sb.append(hostName);
sb.append("], Context: [");
sb.append(contextName);
sb.append("], SessionID: [");
sb.append(sessionId);
sb.append("]");
return sb.toString();
}
}

View File

@@ -0,0 +1,496 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.catalina.authenticator;
import java.io.File;
import java.io.IOException;
import java.security.Principal;
import java.security.PrivilegedAction;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.LinkedHashMap;
import java.util.regex.Pattern;
import javax.security.auth.Subject;
import javax.security.auth.login.LoginContext;
import javax.security.auth.login.LoginException;
import javax.servlet.http.HttpServletResponse;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.Realm;
import org.apache.catalina.connector.Request;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import org.apache.tomcat.util.buf.ByteChunk;
import org.apache.tomcat.util.buf.MessageBytes;
import org.apache.tomcat.util.codec.binary.Base64;
import org.apache.tomcat.util.compat.JreVendor;
import org.ietf.jgss.GSSContext;
import org.ietf.jgss.GSSCredential;
import org.ietf.jgss.GSSException;
import org.ietf.jgss.GSSManager;
import org.ietf.jgss.Oid;
/**
* A SPNEGO authenticator that uses the SPNEGO/Kerberos support built in to Java
* 6. Successful Kerberos authentication depends on the correct configuration of
* multiple components. If the configuration is invalid, the error messages are
* often cryptic although a Google search will usually point you in the right
* direction.
*/
public class SpnegoAuthenticator extends AuthenticatorBase {
private final Log log = LogFactory.getLog(SpnegoAuthenticator.class); // must not be static
private static final String AUTH_HEADER_VALUE_NEGOTIATE = "Negotiate";
private String loginConfigName = Constants.DEFAULT_LOGIN_MODULE_NAME;
public String getLoginConfigName() {
return loginConfigName;
}
public void setLoginConfigName(String loginConfigName) {
this.loginConfigName = loginConfigName;
}
private boolean storeDelegatedCredential = true;
public boolean isStoreDelegatedCredential() {
return storeDelegatedCredential;
}
public void setStoreDelegatedCredential(
boolean storeDelegatedCredential) {
this.storeDelegatedCredential = storeDelegatedCredential;
}
private Pattern noKeepAliveUserAgents = null;
public String getNoKeepAliveUserAgents() {
Pattern p = noKeepAliveUserAgents;
if (p == null) {
return null;
} else {
return p.pattern();
}
}
public void setNoKeepAliveUserAgents(String noKeepAliveUserAgents) {
if (noKeepAliveUserAgents == null ||
noKeepAliveUserAgents.length() == 0) {
this.noKeepAliveUserAgents = null;
} else {
this.noKeepAliveUserAgents = Pattern.compile(noKeepAliveUserAgents);
}
}
private boolean applyJava8u40Fix = true;
public boolean getApplyJava8u40Fix() {
return applyJava8u40Fix;
}
public void setApplyJava8u40Fix(boolean applyJava8u40Fix) {
this.applyJava8u40Fix = applyJava8u40Fix;
}
@Override
protected String getAuthMethod() {
return Constants.SPNEGO_METHOD;
}
@Override
protected void initInternal() throws LifecycleException {
super.initInternal();
// Kerberos configuration file location
String krb5Conf = System.getProperty(Constants.KRB5_CONF_PROPERTY);
if (krb5Conf == null) {
// System property not set, use the Tomcat default
File krb5ConfFile = new File(container.getCatalinaBase(),
Constants.DEFAULT_KRB5_CONF);
System.setProperty(Constants.KRB5_CONF_PROPERTY,
krb5ConfFile.getAbsolutePath());
}
// JAAS configuration file location
String jaasConf = System.getProperty(Constants.JAAS_CONF_PROPERTY);
if (jaasConf == null) {
// System property not set, use the Tomcat default
File jaasConfFile = new File(container.getCatalinaBase(),
Constants.DEFAULT_JAAS_CONF);
System.setProperty(Constants.JAAS_CONF_PROPERTY,
jaasConfFile.getAbsolutePath());
}
}
@Override
protected boolean doAuthenticate(Request request, HttpServletResponse response)
throws IOException {
if (checkForCachedAuthentication(request, response, true)) {
return true;
}
MessageBytes authorization =
request.getCoyoteRequest().getMimeHeaders()
.getValue("authorization");
if (authorization == null) {
if (log.isDebugEnabled()) {
log.debug(sm.getString("authenticator.noAuthHeader"));
}
response.setHeader(AUTH_HEADER_NAME, AUTH_HEADER_VALUE_NEGOTIATE);
response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
return false;
}
authorization.toBytes();
ByteChunk authorizationBC = authorization.getByteChunk();
if (!authorizationBC.startsWithIgnoreCase("negotiate ", 0)) {
if (log.isDebugEnabled()) {
log.debug(sm.getString(
"spnegoAuthenticator.authHeaderNotNego"));
}
response.setHeader(AUTH_HEADER_NAME, AUTH_HEADER_VALUE_NEGOTIATE);
response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
return false;
}
authorizationBC.setOffset(authorizationBC.getOffset() + 10);
byte[] decoded = Base64.decodeBase64(authorizationBC.getBuffer(),
authorizationBC.getOffset(),
authorizationBC.getLength());
if (getApplyJava8u40Fix()) {
SpnegoTokenFixer.fix(decoded);
}
if (decoded.length == 0) {
if (log.isDebugEnabled()) {
log.debug(sm.getString(
"spnegoAuthenticator.authHeaderNoToken"));
}
response.setHeader(AUTH_HEADER_NAME, AUTH_HEADER_VALUE_NEGOTIATE);
response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
return false;
}
LoginContext lc = null;
GSSContext gssContext = null;
byte[] outToken = null;
Principal principal = null;
try {
try {
lc = new LoginContext(getLoginConfigName());
lc.login();
} catch (LoginException e) {
log.error(sm.getString("spnegoAuthenticator.serviceLoginFail"),
e);
response.sendError(
HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
return false;
}
Subject subject = lc.getSubject();
// Assume the GSSContext is stateless
// TODO: Confirm this assumption
final GSSManager manager = GSSManager.getInstance();
// IBM JDK only understands indefinite lifetime
final int credentialLifetime;
if (JreVendor.IS_IBM_JVM) {
credentialLifetime = GSSCredential.INDEFINITE_LIFETIME;
} else {
credentialLifetime = GSSCredential.DEFAULT_LIFETIME;
}
final PrivilegedExceptionAction<GSSCredential> action =
new PrivilegedExceptionAction<GSSCredential>() {
@Override
public GSSCredential run() throws GSSException {
return manager.createCredential(null,
credentialLifetime,
new Oid("1.3.6.1.5.5.2"),
GSSCredential.ACCEPT_ONLY);
}
};
gssContext = manager.createContext(Subject.doAs(subject, action));
outToken = Subject.doAs(lc.getSubject(), new AcceptAction(gssContext, decoded));
if (outToken == null) {
if (log.isDebugEnabled()) {
log.debug(sm.getString(
"spnegoAuthenticator.ticketValidateFail"));
}
// Start again
response.setHeader(AUTH_HEADER_NAME, AUTH_HEADER_VALUE_NEGOTIATE);
response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
return false;
}
principal = Subject.doAs(subject, new AuthenticateAction(
context.getRealm(), gssContext, storeDelegatedCredential));
} catch (GSSException e) {
if (log.isDebugEnabled()) {
log.debug(sm.getString("spnegoAuthenticator.ticketValidateFail"), e);
}
response.setHeader(AUTH_HEADER_NAME, AUTH_HEADER_VALUE_NEGOTIATE);
response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
return false;
} catch (PrivilegedActionException e) {
Throwable cause = e.getCause();
if (cause instanceof GSSException) {
if (log.isDebugEnabled()) {
log.debug(sm.getString("spnegoAuthenticator.serviceLoginFail"), e);
}
} else {
log.error(sm.getString("spnegoAuthenticator.serviceLoginFail"), e);
}
response.setHeader(AUTH_HEADER_NAME, AUTH_HEADER_VALUE_NEGOTIATE);
response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
return false;
} finally {
if (gssContext != null) {
try {
gssContext.dispose();
} catch (GSSException e) {
// Ignore
}
}
if (lc != null) {
try {
lc.logout();
} catch (LoginException e) {
// Ignore
}
}
}
// Send response token on success and failure
response.setHeader(AUTH_HEADER_NAME, AUTH_HEADER_VALUE_NEGOTIATE + " "
+ Base64.encodeBase64String(outToken));
if (principal != null) {
register(request, response, principal, Constants.SPNEGO_METHOD,
principal.getName(), null);
Pattern p = noKeepAliveUserAgents;
if (p != null) {
MessageBytes ua =
request.getCoyoteRequest().getMimeHeaders().getValue(
"user-agent");
if (ua != null && p.matcher(ua.toString()).matches()) {
response.setHeader("Connection", "close");
}
}
return true;
}
response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
return false;
}
/**
* This class gets a gss credential via a privileged action.
*/
public static class AcceptAction implements PrivilegedExceptionAction<byte[]> {
GSSContext gssContext;
byte[] decoded;
public AcceptAction(GSSContext context, byte[] decodedToken) {
this.gssContext = context;
this.decoded = decodedToken;
}
@Override
public byte[] run() throws GSSException {
return gssContext.acceptSecContext(decoded,
0, decoded.length);
}
}
public static class AuthenticateAction implements PrivilegedAction<Principal> {
private final Realm realm;
private final GSSContext gssContext;
private final boolean storeDelegatedCredential;
public AuthenticateAction(Realm realm, GSSContext gssContext,
boolean storeDelegatedCredential) {
this.realm = realm;
this.gssContext = gssContext;
this.storeDelegatedCredential = storeDelegatedCredential;
}
@Override
public Principal run() {
return realm.authenticate(gssContext, storeDelegatedCredential);
}
}
/**
* This class implements a hack around an incompatibility between the
* SPNEGO implementation in Windows and the SPNEGO implementation in Java 8
* update 40 onwards. It was introduced by the change to fix this bug:
* https://bugs.openjdk.java.net/browse/JDK-8048194
* (note: the change applied is not the one suggested in the bug report)
* <p>
* It is not clear to me if Windows, Java or Tomcat is at fault here. I
* think it is Java but I could be wrong.
* <p>
* This hack works by re-ordering the list of mechTypes in the NegTokenInit
* token.
*/
public static class SpnegoTokenFixer {
public static void fix(byte[] token) {
SpnegoTokenFixer fixer = new SpnegoTokenFixer(token);
fixer.fix();
}
private final byte[] token;
private int pos = 0;
private SpnegoTokenFixer(byte[] token) {
this.token = token;
}
// Fixes the token in-place
private void fix() {
/*
* Useful references:
* http://tools.ietf.org/html/rfc4121#page-5
* http://tools.ietf.org/html/rfc2743#page-81
* https://msdn.microsoft.com/en-us/library/ms995330.aspx
*/
// Scan until we find the mech types list. If we find anything
// unexpected, abort the fix process.
if (!tag(0x60)) return;
if (!length()) return;
if (!oid("1.3.6.1.5.5.2")) return;
if (!tag(0xa0)) return;
if (!length()) return;
if (!tag(0x30)) return;
if (!length()) return;
if (!tag(0xa0)) return;
lengthAsInt();
if (!tag(0x30)) return;
// Now at the start of the mechType list.
// Read the mechTypes into an ordered set
int mechTypesLen = lengthAsInt();
int mechTypesStart = pos;
LinkedHashMap<String, int[]> mechTypeEntries = new LinkedHashMap<>();
while (pos < mechTypesStart + mechTypesLen) {
int[] value = new int[2];
value[0] = pos;
String key = oidAsString();
value[1] = pos - value[0];
mechTypeEntries.put(key, value);
}
// Now construct the re-ordered mechType list
byte[] replacement = new byte[mechTypesLen];
int replacementPos = 0;
int[] first = mechTypeEntries.remove("1.2.840.113554.1.2.2");
if (first != null) {
System.arraycopy(token, first[0], replacement, replacementPos, first[1]);
replacementPos += first[1];
}
for (int[] markers : mechTypeEntries.values()) {
System.arraycopy(token, markers[0], replacement, replacementPos, markers[1]);
replacementPos += markers[1];
}
// Finally, replace the original mechType list with the re-ordered
// one.
System.arraycopy(replacement, 0, token, mechTypesStart, mechTypesLen);
}
private boolean tag(int expected) {
return (token[pos++] & 0xFF) == expected;
}
private boolean length() {
// No need to retain the length - just need to consume it and make
// sure it is valid.
int len = lengthAsInt();
return pos + len == token.length;
}
private int lengthAsInt() {
int len = token[pos++] & 0xFF;
if (len > 127) {
int bytes = len - 128;
len = 0;
for (int i = 0; i < bytes; i++) {
len = len << 8;
len = len + (token[pos++] & 0xff);
}
}
return len;
}
private boolean oid(String expected) {
return expected.equals(oidAsString());
}
private String oidAsString() {
if (!tag(0x06)) return null;
StringBuilder result = new StringBuilder();
int len = lengthAsInt();
// First byte is special case
int v = token[pos++] & 0xFF;
int c2 = v % 40;
int c1 = (v - c2) / 40;
result.append(c1);
result.append('.');
result.append(c2);
int c = 0;
boolean write = false;
for (int i = 1; i < len; i++) {
int b = token[pos++] & 0xFF;
if (b > 127) {
b -= 128;
} else {
write = true;
}
c = c << 7;
c += b;
if (write) {
result.append('.');
result.append(c);
c = 0;
write = false;
}
}
return result.toString();
}
}
}

View File

@@ -0,0 +1,514 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.catalina.authenticator.jaspic;
import java.io.File;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import javax.security.auth.message.config.AuthConfigFactory;
import javax.security.auth.message.config.AuthConfigProvider;
import javax.security.auth.message.config.RegistrationListener;
import org.apache.catalina.Globals;
import org.apache.catalina.authenticator.jaspic.PersistentProviderRegistrations.Provider;
import org.apache.catalina.authenticator.jaspic.PersistentProviderRegistrations.Providers;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import org.apache.tomcat.util.res.StringManager;
public class AuthConfigFactoryImpl extends AuthConfigFactory {
private final Log log = LogFactory.getLog(AuthConfigFactoryImpl.class); // must not be static
private static final StringManager sm = StringManager.getManager(AuthConfigFactoryImpl.class);
private static final String CONFIG_PATH = "conf/jaspic-providers.xml";
private static final File CONFIG_FILE =
new File(System.getProperty(Globals.CATALINA_BASE_PROP), CONFIG_PATH);
private static final Object CONFIG_FILE_LOCK = new Object();
private static final String[] EMPTY_STRING_ARRAY = new String[0];
private static String DEFAULT_REGISTRATION_ID = getRegistrationID(null, null);
private final Map<String,RegistrationContextImpl> layerAppContextRegistrations =
new ConcurrentHashMap<>();
private final Map<String,RegistrationContextImpl> appContextRegistrations =
new ConcurrentHashMap<>();
private final Map<String,RegistrationContextImpl> layerRegistrations =
new ConcurrentHashMap<>();
// Note: Although there will only ever be a maximum of one entry in this
// Map, use a ConcurrentHashMap for consistency
private final Map<String,RegistrationContextImpl> defaultRegistration =
new ConcurrentHashMap<>(1);
public AuthConfigFactoryImpl() {
loadPersistentRegistrations();
}
@Override
public AuthConfigProvider getConfigProvider(String layer, String appContext,
RegistrationListener listener) {
RegistrationContextImpl registrationContext =
findRegistrationContextImpl(layer, appContext);
if (registrationContext != null) {
if (listener != null) {
RegistrationListenerWrapper wrapper = new RegistrationListenerWrapper(
layer, appContext, listener);
registrationContext.addListener(wrapper);
}
return registrationContext.getProvider();
}
return null;
}
@Override
public String registerConfigProvider(String className,
@SuppressWarnings("rawtypes") Map properties, String layer, String appContext,
String description) {
String registrationID =
doRegisterConfigProvider(className, properties, layer, appContext, description);
savePersistentRegistrations();
return registrationID;
}
@SuppressWarnings("unchecked")
private String doRegisterConfigProvider(String className,
@SuppressWarnings("rawtypes") Map properties, String layer, String appContext,
String description) {
if (log.isDebugEnabled()) {
log.debug(sm.getString("authConfigFactoryImpl.registerClass",
className, layer, appContext));
}
AuthConfigProvider provider = null;
if (className != null) {
provider = createAuthConfigProvider(className, properties);
}
String registrationID = getRegistrationID(layer, appContext);
RegistrationContextImpl registrationContextImpl = new RegistrationContextImpl(
layer, appContext, description, true, provider, properties);
addRegistrationContextImpl(layer, appContext, registrationID, registrationContextImpl);
return registrationID;
}
private AuthConfigProvider createAuthConfigProvider(String className,
@SuppressWarnings("rawtypes") Map properties) throws SecurityException {
Class<?> clazz = null;
AuthConfigProvider provider = null;
try {
clazz = Class.forName(className, true, Thread.currentThread().getContextClassLoader());
} catch (ClassNotFoundException e) {
// Ignore so the re-try below can proceed
}
try {
if (clazz == null) {
clazz = Class.forName(className);
}
Constructor<?> constructor = clazz.getConstructor(Map.class, AuthConfigFactory.class);
provider = (AuthConfigProvider) constructor.newInstance(properties, null);
} catch (ReflectiveOperationException | IllegalArgumentException e) {
throw new SecurityException(e);
}
return provider;
}
@Override
public String registerConfigProvider(AuthConfigProvider provider, String layer,
String appContext, String description) {
if (log.isDebugEnabled()) {
log.debug(sm.getString("authConfigFactoryImpl.registerInstance",
provider.getClass().getName(), layer, appContext));
}
String registrationID = getRegistrationID(layer, appContext);
RegistrationContextImpl registrationContextImpl = new RegistrationContextImpl(
layer, appContext, description, false, provider, null);
addRegistrationContextImpl(layer, appContext, registrationID, registrationContextImpl);
return registrationID;
}
private void addRegistrationContextImpl(String layer, String appContext,
String registrationID, RegistrationContextImpl registrationContextImpl) {
RegistrationContextImpl previous = null;
// Add the registration, noting any registration it replaces
if (layer != null && appContext != null) {
previous = layerAppContextRegistrations.put(registrationID, registrationContextImpl);
} else if (layer == null && appContext != null) {
previous = appContextRegistrations.put(registrationID, registrationContextImpl);
} else if (layer != null && appContext == null) {
previous = layerRegistrations.put(registrationID, registrationContextImpl);
} else {
previous = defaultRegistration.put(registrationID, registrationContextImpl);
}
if (previous == null) {
// No match with previous registration so need to check listeners
// for all less specific registrations to see if they need to be
// notified of this new registration. That there is no exact match
// with a previous registration allows a few short-cuts to be taken
if (layer != null && appContext != null) {
// Need to check existing appContext registrations
// (and layer and default)
// appContext must match
RegistrationContextImpl registration =
appContextRegistrations.get(getRegistrationID(null, appContext));
if (registration != null) {
for (RegistrationListenerWrapper wrapper : registration.listeners) {
if (layer.equals(wrapper.getMessageLayer()) &&
appContext.equals(wrapper.getAppContext())) {
registration.listeners.remove(wrapper);
wrapper.listener.notify(wrapper.messageLayer, wrapper.appContext);
}
}
}
}
if (appContext != null) {
// Need to check existing layer registrations
// (and default)
// Need to check registrations for all layers
for (RegistrationContextImpl registration : layerRegistrations.values()) {
for (RegistrationListenerWrapper wrapper : registration.listeners) {
if (appContext.equals(wrapper.getAppContext())) {
registration.listeners.remove(wrapper);
wrapper.listener.notify(wrapper.messageLayer, wrapper.appContext);
}
}
}
}
if (layer != null || appContext != null) {
// Need to check default
for (RegistrationContextImpl registration : defaultRegistration.values()) {
for (RegistrationListenerWrapper wrapper : registration.listeners) {
if (appContext != null && appContext.equals(wrapper.getAppContext()) ||
layer != null && layer.equals(wrapper.getMessageLayer())) {
registration.listeners.remove(wrapper);
wrapper.listener.notify(wrapper.messageLayer, wrapper.appContext);
}
}
}
}
} else {
// Replaced an existing registration so need to notify those listeners
for (RegistrationListenerWrapper wrapper : previous.listeners) {
previous.listeners.remove(wrapper);
wrapper.listener.notify(wrapper.messageLayer, wrapper.appContext);
}
}
}
@Override
public boolean removeRegistration(String registrationID) {
RegistrationContextImpl registration = null;
if (DEFAULT_REGISTRATION_ID.equals(registrationID)) {
registration = defaultRegistration.remove(registrationID);
}
if (registration == null) {
registration = layerAppContextRegistrations.remove(registrationID);
}
if (registration == null) {
registration = appContextRegistrations.remove(registrationID);
}
if (registration == null) {
registration = layerRegistrations.remove(registrationID);
}
if (registration == null) {
return false;
} else {
for (RegistrationListenerWrapper wrapper : registration.listeners) {
wrapper.getListener().notify(wrapper.getMessageLayer(), wrapper.getAppContext());
}
if (registration.isPersistent()) {
savePersistentRegistrations();
}
return true;
}
}
@Override
public String[] detachListener(RegistrationListener listener, String layer, String appContext) {
String registrationID = getRegistrationID(layer, appContext);
RegistrationContextImpl registrationContext = findRegistrationContextImpl(layer, appContext);
if (registrationContext != null && registrationContext.removeListener(listener)) {
return new String[] { registrationID };
}
return EMPTY_STRING_ARRAY;
}
@Override
public String[] getRegistrationIDs(AuthConfigProvider provider) {
List<String> result = new ArrayList<>();
if (provider == null) {
result.addAll(layerAppContextRegistrations.keySet());
result.addAll(appContextRegistrations.keySet());
result.addAll(layerRegistrations.keySet());
if (!defaultRegistration.isEmpty()) {
result.add(DEFAULT_REGISTRATION_ID);
}
} else {
findProvider(provider, layerAppContextRegistrations, result);
findProvider(provider, appContextRegistrations, result);
findProvider(provider, layerRegistrations, result);
findProvider(provider, defaultRegistration, result);
}
return result.toArray(EMPTY_STRING_ARRAY);
}
private void findProvider(AuthConfigProvider provider,
Map<String,RegistrationContextImpl> registrations, List<String> result) {
for (Entry<String,RegistrationContextImpl> entry : registrations.entrySet()) {
if (provider.equals(entry.getValue().getProvider())) {
result.add(entry.getKey());
}
}
}
@Override
public RegistrationContext getRegistrationContext(String registrationID) {
RegistrationContext result = defaultRegistration.get(registrationID);
if (result == null) {
result = layerAppContextRegistrations.get(registrationID);
}
if (result == null) {
result = appContextRegistrations.get(registrationID);
}
if (result == null) {
result = layerRegistrations.get(registrationID);
}
return result;
}
@Override
public void refresh() {
loadPersistentRegistrations();
}
private static String getRegistrationID(String layer, String appContext) {
if (layer != null && layer.length() == 0) {
throw new IllegalArgumentException(
sm.getString("authConfigFactoryImpl.zeroLengthMessageLayer"));
}
if (appContext != null && appContext.length() == 0) {
throw new IllegalArgumentException(
sm.getString("authConfigFactoryImpl.zeroLengthAppContext"));
}
return (layer == null ? "" : layer) + ":" + (appContext == null ? "" : appContext);
}
private void loadPersistentRegistrations() {
synchronized (CONFIG_FILE_LOCK) {
if (log.isDebugEnabled()) {
log.debug(sm.getString("authConfigFactoryImpl.load",
CONFIG_FILE.getAbsolutePath()));
}
if (!CONFIG_FILE.isFile()) {
return;
}
Providers providers = PersistentProviderRegistrations.loadProviders(CONFIG_FILE);
for (Provider provider : providers.getProviders()) {
doRegisterConfigProvider(provider.getClassName(), provider.getProperties(),
provider.getLayer(), provider.getAppContext(), provider.getDescription());
}
}
}
private void savePersistentRegistrations() {
synchronized (CONFIG_FILE_LOCK) {
Providers providers = new Providers();
savePersistentProviders(providers, layerAppContextRegistrations);
savePersistentProviders(providers, appContextRegistrations);
savePersistentProviders(providers, layerRegistrations);
savePersistentProviders(providers, defaultRegistration);
PersistentProviderRegistrations.writeProviders(providers, CONFIG_FILE);
}
}
private void savePersistentProviders(Providers providers,
Map<String,RegistrationContextImpl> registrations) {
for (Entry<String,RegistrationContextImpl> entry : registrations.entrySet()) {
savePersistentProvider(providers, entry.getValue());
}
}
private void savePersistentProvider(Providers providers,
RegistrationContextImpl registrationContextImpl) {
if (registrationContextImpl != null && registrationContextImpl.isPersistent()) {
Provider provider = new Provider();
provider.setAppContext(registrationContextImpl.getAppContext());
if (registrationContextImpl.getProvider() != null) {
provider.setClassName(registrationContextImpl.getProvider().getClass().getName());
}
provider.setDescription(registrationContextImpl.getDescription());
provider.setLayer(registrationContextImpl.getMessageLayer());
for (Entry<String,String> property : registrationContextImpl.getProperties().entrySet()) {
provider.addProperty(property.getKey(), property.getValue());
}
providers.addProvider(provider);
}
}
private RegistrationContextImpl findRegistrationContextImpl(String layer, String appContext) {
RegistrationContextImpl result;
result = layerAppContextRegistrations.get(getRegistrationID(layer, appContext));
if (result == null) {
result = appContextRegistrations.get(getRegistrationID(null, appContext));
}
if (result == null) {
result = layerRegistrations.get(getRegistrationID(layer, null));
}
if (result == null) {
result = defaultRegistration.get(DEFAULT_REGISTRATION_ID);
}
return result;
}
private static class RegistrationContextImpl implements RegistrationContext {
private RegistrationContextImpl(String messageLayer, String appContext, String description,
boolean persistent, AuthConfigProvider provider, Map<String,String> properties) {
this.messageLayer = messageLayer;
this.appContext = appContext;
this.description = description;
this.persistent = persistent;
this.provider = provider;
Map<String,String> propertiesCopy = new HashMap<>();
if (properties != null) {
propertiesCopy.putAll(properties);
}
this.properties = Collections.unmodifiableMap(propertiesCopy);
}
private final String messageLayer;
private final String appContext;
private final String description;
private final boolean persistent;
private final AuthConfigProvider provider;
private final Map<String,String> properties;
private final List<RegistrationListenerWrapper> listeners = new CopyOnWriteArrayList<>();
@Override
public String getMessageLayer() {
return messageLayer;
}
@Override
public String getAppContext() {
return appContext;
}
@Override
public String getDescription() {
return description;
}
@Override
public boolean isPersistent() {
return persistent;
}
private AuthConfigProvider getProvider() {
return provider;
}
private void addListener(RegistrationListenerWrapper listener) {
if (listener != null) {
listeners.add(listener);
}
}
private Map<String,String> getProperties() {
return properties;
}
private boolean removeListener(RegistrationListener listener) {
boolean result = false;
for (RegistrationListenerWrapper wrapper : listeners) {
if (wrapper.getListener().equals(listener)) {
listeners.remove(wrapper);
result = true;
}
}
return result;
}
}
private static class RegistrationListenerWrapper {
private final String messageLayer;
private final String appContext;
private final RegistrationListener listener;
public RegistrationListenerWrapper(String messageLayer, String appContext,
RegistrationListener listener) {
this.messageLayer = messageLayer;
this.appContext = appContext;
this.listener = listener;
}
public String getMessageLayer() {
return messageLayer;
}
public String getAppContext() {
return appContext;
}
public RegistrationListener getListener() {
return listener;
}
}
}

View File

@@ -0,0 +1,121 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.catalina.authenticator.jaspic;
import java.io.IOException;
import java.security.Principal;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import javax.security.auth.Subject;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.UnsupportedCallbackException;
import javax.security.auth.message.callback.CallerPrincipalCallback;
import javax.security.auth.message.callback.GroupPrincipalCallback;
import org.apache.catalina.realm.GenericPrincipal;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import org.apache.tomcat.util.res.StringManager;
/**
* Implemented as a singleton since the class is stateless.
*/
public class CallbackHandlerImpl implements CallbackHandler {
private static final StringManager sm = StringManager.getManager(CallbackHandlerImpl.class);
private static CallbackHandler instance;
static {
instance = new CallbackHandlerImpl();
}
public static CallbackHandler getInstance() {
return instance;
}
private CallbackHandlerImpl() {
// Hide default constructor
}
@Override
public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
String name = null;
Principal principal = null;
Subject subject = null;
String[] groups = null;
if (callbacks != null) {
// Need to combine data from multiple callbacks so use this to hold
// the data
// Process the callbacks
for (Callback callback : callbacks) {
if (callback instanceof CallerPrincipalCallback) {
CallerPrincipalCallback cpc = (CallerPrincipalCallback) callback;
name = cpc.getName();
principal = cpc.getPrincipal();
subject = cpc.getSubject();
} else if (callback instanceof GroupPrincipalCallback) {
GroupPrincipalCallback gpc = (GroupPrincipalCallback) callback;
groups = gpc.getGroups();
} else {
// This is a singleton so need to get correct Logger for
// current TCCL
Log log = LogFactory.getLog(CallbackHandlerImpl.class);
log.error(sm.getString("callbackHandlerImpl.jaspicCallbackMissing",
callback.getClass().getName()));
}
}
// Create the GenericPrincipal
Principal gp = getPrincipal(principal, name, groups);
if (subject != null && gp != null) {
subject.getPrivateCredentials().add(gp);
}
}
}
private Principal getPrincipal(Principal principal, String name, String[] groups) {
// If the Principal is cached in the session JASPIC may simply return it
if (principal instanceof GenericPrincipal) {
return principal;
}
if (name == null && principal != null) {
name = principal.getName();
}
if (name == null) {
return null;
}
List<String> roles;
if (groups == null || groups.length == 0) {
roles = Collections.emptyList();
} else {
roles = Arrays.asList(groups);
}
return new GenericPrincipal(name, null, roles, principal);
}
}

View File

@@ -0,0 +1,30 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
authConfigFactoryImpl.load=Loading persistent provider registrations from [{0}]
authConfigFactoryImpl.registerClass=Registering class [{0}] for layer [{1}] and application context [{2}]
authConfigFactoryImpl.registerInstance=Registering instance of type[{0}] for layer [{1}] and application context [{2}]
authConfigFactoryImpl.zeroLengthAppContext=A zero length application context name is not valid
authConfigFactoryImpl.zeroLengthMessageLayer=A zero length message layer name is not valid
callbackHandlerImpl.jaspicCallbackMissing=Unsupported JASPIC callback of type [{0}] received which was ignored
jaspicAuthenticator.authenticate=Authenticating request for [{0}] via JASPIC
persistentProviderRegistrations.deleteFail=The temporary file [{0}] cannot be deleted
persistentProviderRegistrations.existsDeleteFail=The temporary file [{0}] already exists and cannot be deleted
persistentProviderRegistrations.moveFail=Failed to move [{0}] to [{1}]
simpleServerAuthConfig.noModules="No ServerAuthModules configured"

View File

@@ -0,0 +1,18 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
authConfigFactoryImpl.zeroLengthAppContext=Ein leerer Name für einen Applikationskontext ist nicht erlaubt.
persistentProviderRegistrations.existsDeleteFail=Die temporäre Datei [{0}] existiert bereits und kann nicht gelöscht werden

View File

@@ -0,0 +1,18 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
authConfigFactoryImpl.zeroLengthAppContext=Un nombre de aplicación con nombre de contexto de longitud cero no es válido
persistentProviderRegistrations.existsDeleteFail=El archivo temporal [{0}] ya existe y no puede ser borrado

View File

@@ -0,0 +1,30 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
authConfigFactoryImpl.load=Chargement des enregistrements pour le fournisseurs persistants à partir de [{0}]
authConfigFactoryImpl.registerClass=Enregistrement de la classe [{0}] pour la couche [{1}] et le contexte d''application [{2}]
authConfigFactoryImpl.registerInstance=Enregistrement de l''instance de type [{0}] pour la couche [{1}] et le contexte d''application [{2}]
authConfigFactoryImpl.zeroLengthAppContext=Un nom de contexte vide n'est pas valide
authConfigFactoryImpl.zeroLengthMessageLayer=Un message vide de nom de couche est invalide
callbackHandlerImpl.jaspicCallbackMissing=Le rappel (callback) JASPIC de type [{0}] reçu n''est pas supporté et a été ignoré
jaspicAuthenticator.authenticate=Authentification de la requête pour [{0}] avec JASPIC
persistentProviderRegistrations.deleteFail=Le fichier temporaire [{0}] n''a pas pu être effacé
persistentProviderRegistrations.existsDeleteFail=Le fichier temporaire [{0}] existe déjà et ne peut être effacé
persistentProviderRegistrations.moveFail=Echec de déplacement de [{0}] vers [{1}]
simpleServerAuthConfig.noModules=Aucun ServerAuthModules n'est configuré

View File

@@ -0,0 +1,30 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
authConfigFactoryImpl.load=[{0}]から永続的なプロバイダ登録を読み込みます。
authConfigFactoryImpl.registerClass=アプリケーションコンテキスト [{2}] のレイヤー [{1}] にクラス [{0}] を登録します。
authConfigFactoryImpl.registerInstance=レイヤ[{1}]とアプリケーションコンテキスト[{2}]のタイプ[{0}]のインスタンスの登録
authConfigFactoryImpl.zeroLengthAppContext=文字列長が 0 のアプリケーションコンテキスト名は不正です。
authConfigFactoryImpl.zeroLengthMessageLayer=長さゼロのメッセージ層名は無効です
callbackHandlerImpl.jaspicCallbackMissing=受信したタイプ[{0}]のサポートされていないJASPICコールバックが無視されました。
jaspicAuthenticator.authenticate=JASPIC経由で[{0}]へのリクエストを認証しています
persistentProviderRegistrations.deleteFail=一時ファイル [{0}] を削除できません。
persistentProviderRegistrations.existsDeleteFail=同名の一時ファイル [{0}] が存在し、削除もできませんでした。
persistentProviderRegistrations.moveFail=[{0}]を[{1}]に移動できませんでした。
simpleServerAuthConfig.noModules="ServerAuthModulesが設定されていません"

View File

@@ -0,0 +1,30 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
authConfigFactoryImpl.load=[{0}](으)로부터 persistent provider 등록 사항들을 로드합니다.
authConfigFactoryImpl.registerClass=레이어 [{1}]와(과) 애플리케이션 컨텍스트 [{2}]을(를) 위한 클래스 [{0}]을(를) 등록합니다.
authConfigFactoryImpl.registerInstance=레이어 [{1}]와(과) 애플리케이션 컨텍스트 [{2}]을(를) 위한 타입 [{0}]의 인스턴스를 등록합니다.
authConfigFactoryImpl.zeroLengthAppContext=애플리케이션 컨텍스트 이름의 길이가 0으로, 이는 유효하지 않습니다.
authConfigFactoryImpl.zeroLengthMessageLayer=길이가 0인 메시지 레이어 이름은 유효하지 않습니다.
callbackHandlerImpl.jaspicCallbackMissing=타입이 [{0}]인 지원되지 않는 JASPIC 콜백을 받았는데, 이는 무시됩니다.
jaspicAuthenticator.authenticate=[{0}]을(를) 위한 요청을 JASPIC를 통하여 인증합니다.
persistentProviderRegistrations.deleteFail=임시 파일 [{0}]을(를) 삭제할 수 없습니다.
persistentProviderRegistrations.existsDeleteFail=임시 파일 [{0}]이(가) 이미 존재하며 삭제될 수 없습니다.
persistentProviderRegistrations.moveFail=[{0}]을(를) [{1}](으)로 이동시키지 못했습니다.
simpleServerAuthConfig.noModules="ServerAuthModule이 설정되지 않음"

View File

@@ -0,0 +1,16 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
persistentProviderRegistrations.existsDeleteFail=Временный файл [{0}] уже существует и не может быть удалён

View File

@@ -0,0 +1,26 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
authConfigFactoryImpl.load=从[{0}]加载持久化提供者注册信息
authConfigFactoryImpl.zeroLengthAppContext=:)应用上下文名称的长度为0是无效的
authConfigFactoryImpl.zeroLengthMessageLayer=零长度的消息层名称是无效的
jaspicAuthenticator.authenticate=通过JASPIC验证[{0}]的请求
persistentProviderRegistrations.deleteFail=无法删除临时文件[{0}]
persistentProviderRegistrations.existsDeleteFail=临时文件[{0}]已存在且无法删除
persistentProviderRegistrations.moveFail=无法将[{0}]移至[{1}]
simpleServerAuthConfig.noModules=“没有配置ServerAuthModules”

View File

@@ -0,0 +1,80 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.catalina.authenticator.jaspic;
import java.util.HashMap;
import java.util.Map;
import javax.security.auth.message.MessageInfo;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.tomcat.util.res.StringManager;
public class MessageInfoImpl implements MessageInfo {
protected static final StringManager sm = StringManager.getManager(MessageInfoImpl.class);
public static final String IS_MANDATORY = "javax.security.auth.message.MessagePolicy.isMandatory";
private final Map<String, Object> map = new HashMap<>();
private HttpServletRequest request;
private HttpServletResponse response;
public MessageInfoImpl() {
}
public MessageInfoImpl(HttpServletRequest request, HttpServletResponse response, boolean authMandatory) {
this.request = request;
this.response = response;
map.put(IS_MANDATORY, Boolean.toString(authMandatory));
}
@Override
@SuppressWarnings("rawtypes")
// JASPIC uses raw types
public Map getMap() {
return map;
}
@Override
public Object getRequestMessage() {
return request;
}
@Override
public Object getResponseMessage() {
return response;
}
@Override
public void setRequestMessage(Object request) {
if (!(request instanceof HttpServletRequest)) {
throw new IllegalArgumentException(sm.getString("authenticator.jaspic.badRequestType",
request.getClass().getName()));
}
this.request = (HttpServletRequest) request;
}
@Override
public void setResponseMessage(Object response) {
if (!(response instanceof HttpServletResponse)) {
throw new IllegalArgumentException(sm.getString("authenticator.jaspic.badResponseType",
response.getClass().getName()));
}
this.response = (HttpServletResponse) response;
}
}

View File

@@ -0,0 +1,265 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.catalina.authenticator.jaspic;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import org.apache.tomcat.util.digester.Digester;
import org.apache.tomcat.util.res.StringManager;
import org.xml.sax.SAXException;
/**
* Utility class for the loading and saving of JASPIC persistent provider
* registrations.
*/
final class PersistentProviderRegistrations {
private static final StringManager sm =
StringManager.getManager(PersistentProviderRegistrations.class);
private PersistentProviderRegistrations() {
// Utility class. Hide default constructor
}
static Providers loadProviders(File configFile) {
try (InputStream is = new FileInputStream(configFile)) {
// Construct a digester to read the XML input file
Digester digester = new Digester();
try {
digester.setFeature("http://apache.org/xml/features/allow-java-encodings", true);
digester.setValidating(true);
digester.setNamespaceAware(true);
} catch (Exception e) {
throw new SecurityException(e);
}
// Create an object to hold the parse results and put it on the top
// of the digester's stack
Providers result = new Providers();
digester.push(result);
// Configure the digester
digester.addObjectCreate("jaspic-providers/provider", Provider.class.getName());
digester.addSetProperties("jaspic-providers/provider");
digester.addSetNext("jaspic-providers/provider", "addProvider", Provider.class.getName());
digester.addObjectCreate("jaspic-providers/provider/property", Property.class.getName());
digester.addSetProperties("jaspic-providers/provider/property");
digester.addSetNext("jaspic-providers/provider/property", "addProperty", Property.class.getName());
// Parse the input
digester.parse(is);
return result;
} catch (IOException | SAXException e) {
throw new SecurityException(e);
}
}
static void writeProviders(Providers providers, File configFile) {
File configFileOld = new File(configFile.getAbsolutePath() + ".old");
File configFileNew = new File(configFile.getAbsolutePath() + ".new");
// Remove left over temporary files if present
if (configFileOld.exists()) {
if (configFileOld.delete()) {
throw new SecurityException(sm.getString(
"persistentProviderRegistrations.existsDeleteFail",
configFileOld.getAbsolutePath()));
}
}
if (configFileNew.exists()) {
if (configFileNew.delete()) {
throw new SecurityException(sm.getString(
"persistentProviderRegistrations.existsDeleteFail",
configFileNew.getAbsolutePath()));
}
}
// Write out the providers to the temporary new file
try (OutputStream fos = new FileOutputStream(configFileNew);
Writer writer = new OutputStreamWriter(fos, StandardCharsets.UTF_8)) {
writer.write(
"<?xml version='1.0' encoding='utf-8'?>\n" +
"<jaspic-providers\n" +
" xmlns=\"http://tomcat.apache.org/xml\"\n" +
" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n" +
" xsi:schemaLocation=\"http://tomcat.apache.org/xml jaspic-providers.xsd\"\n" +
" version=\"1.0\">\n");
for (Provider provider : providers.providers) {
writer.write(" <provider");
writeOptional("className", provider.getClassName(), writer);
writeOptional("layer", provider.getLayer(), writer);
writeOptional("appContext", provider.getAppContext(), writer);
writeOptional("description", provider.getDescription(), writer);
writer.write(">\n");
for (Entry<String,String> entry : provider.getProperties().entrySet()) {
writer.write(" <property name=\"");
writer.write(entry.getKey());
writer.write("\" value=\"");
writer.write(entry.getValue());
writer.write("\"/>\n");
}
writer.write(" </provider>\n");
}
writer.write("</jaspic-providers>\n");
} catch (IOException e) {
if (!configFileNew.delete()) {
Log log = LogFactory.getLog(PersistentProviderRegistrations.class);
log.warn(sm.getString("persistentProviderRegistrations.deleteFail",
configFileNew.getAbsolutePath()));
}
throw new SecurityException(e);
}
// Move the current file out of the way
if (configFile.isFile()) {
if (!configFile.renameTo(configFileOld)) {
throw new SecurityException(sm.getString("persistentProviderRegistrations.moveFail",
configFile.getAbsolutePath(), configFileOld.getAbsolutePath()));
}
}
// Move the new file into place
if (!configFileNew.renameTo(configFile)) {
throw new SecurityException(sm.getString("persistentProviderRegistrations.moveFail",
configFileNew.getAbsolutePath(), configFile.getAbsolutePath()));
}
// Remove the old file
if (configFileOld.exists() && !configFileOld.delete()) {
Log log = LogFactory.getLog(PersistentProviderRegistrations.class);
log.warn(sm.getString("persistentProviderRegistrations.deleteFail",
configFileOld.getAbsolutePath()));
}
}
private static void writeOptional(String name, String value, Writer writer) throws IOException {
if (value != null) {
writer.write(" " + name + "=\"");
writer.write(value);
writer.write("\"");
}
}
public static class Providers {
private final List<Provider> providers = new ArrayList<>();
public void addProvider(Provider provider) {
providers.add(provider);
}
public List<Provider> getProviders() {
return providers;
}
}
public static class Provider {
private String className;
private String layer;
private String appContext;
private String description;
private final Map<String,String> properties = new HashMap<>();
public String getClassName() {
return className;
}
public void setClassName(String className) {
this.className = className;
}
public String getLayer() {
return layer;
}
public void setLayer(String layer) {
this.layer = layer;
}
public String getAppContext() {
return appContext;
}
public void setAppContext(String appContext) {
this.appContext = appContext;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public void addProperty(Property property) {
properties.put(property.getName(), property.getValue());
}
void addProperty(String name, String value) {
properties.put(name, value);
}
public Map<String,String> getProperties() {
return properties;
}
}
public static class Property {
private String name;
private String value;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
}

View File

@@ -0,0 +1,89 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.catalina.authenticator.jaspic;
import java.util.Map;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.message.AuthException;
import javax.security.auth.message.config.AuthConfigFactory;
import javax.security.auth.message.config.AuthConfigProvider;
import javax.security.auth.message.config.ClientAuthConfig;
import javax.security.auth.message.config.ServerAuthConfig;
/**
* Basic implementation primarily intended for use when using third-party
* {@link javax.security.auth.message.module.ServerAuthModule} implementations
* that only provide the module.
*/
public class SimpleAuthConfigProvider implements AuthConfigProvider {
private final Map<String,String> properties;
private volatile ServerAuthConfig serverAuthConfig;
public SimpleAuthConfigProvider(Map<String,String> properties, AuthConfigFactory factory) {
this.properties = properties;
if (factory != null) {
factory.registerConfigProvider(this, null, null, "Automatic registration");
}
}
/**
* {@inheritDoc}
* <p>
* This implementation does not support client-side authentication and
* therefore always returns {@code null}.
*/
@Override
public ClientAuthConfig getClientAuthConfig(String layer, String appContext,
CallbackHandler handler) throws AuthException {
return null;
}
@Override
public ServerAuthConfig getServerAuthConfig(String layer, String appContext,
CallbackHandler handler) throws AuthException {
ServerAuthConfig serverAuthConfig = this.serverAuthConfig;
if (serverAuthConfig == null) {
synchronized (this) {
if (this.serverAuthConfig == null) {
this.serverAuthConfig = createServerAuthConfig(layer, appContext, handler, properties);
}
serverAuthConfig = this.serverAuthConfig;
}
}
return serverAuthConfig;
}
protected ServerAuthConfig createServerAuthConfig(String layer, String appContext,
CallbackHandler handler, Map<String,String> properties) {
return new SimpleServerAuthConfig(layer, appContext, handler, properties);
}
@Override
public void refresh() {
ServerAuthConfig serverAuthConfig = this.serverAuthConfig;
if (serverAuthConfig != null) {
serverAuthConfig.refresh();
}
}
}

View File

@@ -0,0 +1,150 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.catalina.authenticator.jaspic;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.security.auth.Subject;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.message.AuthException;
import javax.security.auth.message.MessageInfo;
import javax.security.auth.message.config.ServerAuthConfig;
import javax.security.auth.message.config.ServerAuthContext;
import javax.security.auth.message.module.ServerAuthModule;
import org.apache.tomcat.util.res.StringManager;
/**
* Basic implementation primarily intended for use when using third-party
* {@link ServerAuthModule} implementations that only provide the module. This
* implementation supports configuring the {@link ServerAuthContext} with
* multiple modules.
*/
public class SimpleServerAuthConfig implements ServerAuthConfig {
private static StringManager sm = StringManager.getManager(SimpleServerAuthConfig.class);
private static final String SERVER_AUTH_MODULE_KEY_PREFIX =
"org.apache.catalina.authenticator.jaspic.ServerAuthModule.";
private final String layer;
private final String appContext;
private final CallbackHandler handler;
private final Map<String,String> properties;
private volatile ServerAuthContext serverAuthContext;
public SimpleServerAuthConfig(String layer, String appContext, CallbackHandler handler,
Map<String,String> properties) {
this.layer = layer;
this.appContext = appContext;
this.handler = handler;
this.properties = properties;
}
@Override
public String getMessageLayer() {
return layer;
}
@Override
public String getAppContext() {
return appContext;
}
@Override
public String getAuthContextID(MessageInfo messageInfo) {
return messageInfo.toString();
}
@Override
public void refresh() {
serverAuthContext = null;
}
@Override
public boolean isProtected() {
return false;
}
@SuppressWarnings({"rawtypes", "unchecked"}) // JASPIC API uses raw types
@Override
public ServerAuthContext getAuthContext(String authContextID, Subject serviceSubject,
Map properties) throws AuthException {
ServerAuthContext serverAuthContext = this.serverAuthContext;
if (serverAuthContext == null) {
synchronized (this) {
if (this.serverAuthContext == null) {
Map<String,String> mergedProperties = new HashMap<>();
if (this.properties != null) {
mergedProperties.putAll(this.properties);
}
if (properties != null) {
mergedProperties.putAll(properties);
}
List<ServerAuthModule> modules = new ArrayList<>();
int moduleIndex = 1;
String key = SERVER_AUTH_MODULE_KEY_PREFIX + moduleIndex;
String moduleClassName = mergedProperties.get(key);
while (moduleClassName != null) {
try {
Class<?> clazz = Class.forName(moduleClassName);
ServerAuthModule module =
(ServerAuthModule) clazz.getConstructor().newInstance();
module.initialize(null, null, handler, mergedProperties);
modules.add(module);
} catch (ReflectiveOperationException | IllegalArgumentException |
SecurityException e) {
AuthException ae = new AuthException();
ae.initCause(e);
throw ae;
}
// Look for the next module
moduleIndex++;
key = SERVER_AUTH_MODULE_KEY_PREFIX + moduleIndex;
moduleClassName = mergedProperties.get(key);
}
if (modules.size() == 0) {
throw new AuthException(sm.getString("simpleServerAuthConfig.noModules"));
}
this.serverAuthContext = createServerAuthContext(modules);
}
serverAuthContext = this.serverAuthContext;
}
}
return serverAuthContext;
}
protected ServerAuthContext createServerAuthContext(List<ServerAuthModule> modules) {
return new SimpleServerAuthContext(modules);
}
}

View File

@@ -0,0 +1,74 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.catalina.authenticator.jaspic;
import java.util.List;
import javax.security.auth.Subject;
import javax.security.auth.message.AuthException;
import javax.security.auth.message.AuthStatus;
import javax.security.auth.message.MessageInfo;
import javax.security.auth.message.config.ServerAuthContext;
import javax.security.auth.message.module.ServerAuthModule;
/**
* Basic implementation primarily intended for use when using third-party
* {@link ServerAuthModule} implementations that only provide the module. This
* implementation supports multiple modules and will treat the user as
* authenticated if any one module is able to authenticate the user.
*/
public class SimpleServerAuthContext implements ServerAuthContext {
private final List<ServerAuthModule> modules;
public SimpleServerAuthContext(List<ServerAuthModule> modules) {
this.modules = modules;
}
@SuppressWarnings("unchecked") // JASPIC API uses raw types
@Override
public AuthStatus validateRequest(MessageInfo messageInfo, Subject clientSubject,
Subject serviceSubject) throws AuthException {
for (int moduleIndex = 0; moduleIndex < modules.size(); moduleIndex++) {
ServerAuthModule module = modules.get(moduleIndex);
AuthStatus result = module.validateRequest(messageInfo, clientSubject, serviceSubject);
if (result != AuthStatus.SEND_FAILURE) {
messageInfo.getMap().put("moduleIndex", Integer.valueOf(moduleIndex));
return result;
}
}
return AuthStatus.SEND_FAILURE;
}
@Override
public AuthStatus secureResponse(MessageInfo messageInfo, Subject serviceSubject)
throws AuthException {
ServerAuthModule module = modules.get(((Integer) messageInfo.getMap().get("moduleIndex")).intValue());
return module.secureResponse(messageInfo, serviceSubject);
}
@Override
public void cleanSubject(MessageInfo messageInfo, Subject subject) throws AuthException {
for (ServerAuthModule module : modules) {
module.cleanSubject(messageInfo, subject);
}
}
}

View File

@@ -0,0 +1,297 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<mbeans-descriptors>
<mbean name="BasicAuthenticator"
description="An Authenticator and Valve implementation of HTTP BASIC Authentication"
domain="Catalina"
group="Valve"
type="org.apache.catalina.authenticator.BasicAuthenticator">
<attribute name="alwaysUseSession"
description="Should a session always be used once a user is authenticated?"
type="boolean"/>
<attribute name="cache"
description="Should we cache authenticated Principals if the request is part of an HTTP session?"
type="boolean"/>
<attribute name="changeSessionIdOnAuthentication"
description="Controls if the session ID is changed if a session exists at the point where users are authenticated"
type="boolean"/>
<attribute name="className"
description="Fully qualified class name of the managed object"
type="java.lang.String"
writeable="false"/>
<attribute name="disableProxyCaching"
description="Controls the caching of pages that are protected by security constraints"
type="boolean"/>
<attribute name="securePagesWithPragma"
description="Controls the caching of pages that are protected by security constraints"
type="boolean"/>
<attribute name="secureRandomAlgorithm"
description="The name of the algorithm to use for SSO session ID generation"
type="java.lang.String"/>
<attribute name="secureRandomClass"
description="The name of the class to use for SSO session ID generation"
type="java.lang.String"/>
<attribute name="secureRandomProvider"
description="The name of the provider to use for SSO session ID generation"
type="java.lang.String"/>
<attribute name="stateName"
description="The name of the LifecycleState that this component is currently in"
type="java.lang.String"
writeable="false"/>
<attribute name="trimCredentials"
description="Controls whether leading and/or trailing whitespace is removed from the parsed credentials"
type="boolean"/>
</mbean>
<mbean name="DigestAuthenticator"
description="An Authenticator and Valve implementation of HTTP DIGEST Authentication"
domain="Catalina"
group="Valve"
type="org.apache.catalina.authenticator.DigestAuthenticator">
<attribute name="alwaysUseSession"
description="Should a session always be used once a user is authenticated?"
type="boolean"/>
<attribute name="cache"
description="Should we cache authenticated Principals if the request is part of an HTTP session?"
type="boolean"/>
<attribute name="changeSessionIdOnAuthentication"
description="Controls if the session ID is changed if a session exists at the point where users are authenticated"
type="boolean"/>
<attribute name="className"
description="Fully qualified class name of the managed object"
type="java.lang.String"
writeable="false"/>
<attribute name="cnonceCacheSize"
description="The size of the cnonce cache used to prevent replay attacks"
type="int"/>
<attribute name="disableProxyCaching"
description="Controls the caching of pages that are protected by security constraints"
type="boolean"/>
<attribute name="key"
description="The secret key used by digest authentication"
type="java.lang.String"/>
<attribute name="nonceValidity"
description="The time, in milliseconds, for which a server issued nonce will be valid"
type="long"/>
<attribute name="opaque"
description="The opaque server string used by digest authentication"
type="java.lang.String"/>
<attribute name="securePagesWithPragma"
description="Controls the caching of pages that are protected by security constraints"
type="boolean"/>
<attribute name="secureRandomAlgorithm"
description="The name of the algorithm to use for SSO session ID generation"
type="java.lang.String"/>
<attribute name="secureRandomClass"
description="The name of the class to use for SSO session ID generation"
type="java.lang.String"/>
<attribute name="secureRandomProvider"
description="The name of the provider to use for SSO session ID generation"
type="java.lang.String"/>
<attribute name="stateName"
description="The name of the LifecycleState that this component is currently in"
type="java.lang.String"
writeable="false"/>
<attribute name="validateUri"
description="Should the uri be validated as required by RFC2617?"
type="boolean"/>
</mbean>
<mbean name="FormAuthenticator"
description="An Authenticator and Valve implementation of FORM BASED Authentication"
domain="Catalina"
group="Valve"
type="org.apache.catalina.authenticator.FormAuthenticator">
<attribute name="changeSessionIdOnAuthentication"
description="Controls if the session ID is changed if a session exists at the point where users are authenticated"
type="boolean"/>
<attribute name="characterEncoding"
description="Character encoding to use to read the username and password parameters from the request"
type="java.lang.String"/>
<attribute name="className"
description="Fully qualified class name of the managed object"
type="java.lang.String"
writeable="false"/>
<attribute name="disableProxyCaching"
description="Controls the caching of pages that are protected by security constraints"
type="boolean"/>
<attribute name="landingPage"
description="Controls the behavior of the FORM authentication process if the process is misused, for example by directly requesting the login page or delaying logging in for so long that the session expires"
type="java.lang.String"/>
<attribute name="securePagesWithPragma"
description="Controls the caching of pages that are protected by security constraints"
type="boolean"/>
<attribute name="secureRandomAlgorithm"
description="The name of the algorithm to use for SSO session ID generation"
type="java.lang.String"/>
<attribute name="secureRandomClass"
description="The name of the class to use for SSO session ID generation"
type="java.lang.String"/>
<attribute name="secureRandomProvider"
description="The name of the provider to use for SSO session ID generation"
type="java.lang.String"/>
<attribute name="stateName"
description="The name of the LifecycleState that this component is currently in"
type="java.lang.String"
writeable="false"/>
</mbean>
<mbean name="NonLoginAuthenticator"
description="An Authenticator and Valve implementation that checks only security constraints not involving user authentication"
domain="Catalina"
group="Valve"
type="org.apache.catalina.authenticator.NonLoginAuthenticator">
<attribute name="cache"
description="Should we cache authenticated Principals if the request is part of an HTTP session?"
type="boolean"/>
<attribute name="changeSessionIdOnAuthentication"
description="Controls if the session ID is changed if a session exists at the point where users are authenticated"
type="boolean"/>
<attribute name="className"
description="Fully qualified class name of the managed object"
type="java.lang.String"
writeable="false"/>
<attribute name="disableProxyCaching"
description="Controls the caching of pages that are protected by security constraints"
type="boolean"/>
<attribute name="securePagesWithPragma"
description="Controls the caching of pages that are protected by security constraints"
type="boolean"/>
<attribute name="stateName"
description="The name of the LifecycleState that this component is currently in"
type="java.lang.String"
writeable="false"/>
</mbean>
<mbean name="SingleSignOn"
description="A Valve that supports a 'single signon' user experience"
domain="Catalina"
group="Valve"
type="org.apache.catalina.authenticator.SingleSignOn">
<attribute name="className"
description="Fully qualified class name of the managed object"
type="java.lang.String"
writeable="false"/>
<attribute name="requireReauthentication"
description="Should we attempt to reauthenticate each request against the security Realm?"
type="boolean"/>
<attribute name="cookieDomain"
description="(Optional) Domain to be used by sso cookies"
type="java.lang.String" />
<attribute name="stateName"
description="The name of the LifecycleState that this component is currently in"
type="java.lang.String"
writeable="false"/>
</mbean>
<mbean name="SSLAuthenticator"
description="An Authenticator and Valve implementation of authentication that utilizes SSL certificates to identify client users"
domain="Catalina"
group="Valve"
type="org.apache.catalina.authenticator.SSLAuthenticator">
<attribute name="cache"
description="Should we cache authenticated Principals if the request is part of an HTTP session?"
type="boolean"/>
<attribute name="changeSessionIdOnAuthentication"
description="Controls if the session ID is changed if a session exists at the point where users are authenticated"
type="boolean"/>
<attribute name="className"
description="Fully qualified class name of the managed object"
type="java.lang.String"
writeable="false"/>
<attribute name="disableProxyCaching"
description="Controls the caching of pages that are protected by security constraints"
type="boolean"/>
<attribute name="securePagesWithPragma"
description="Controls the caching of pages that are protected by security constraints"
type="boolean"/>
<attribute name="secureRandomAlgorithm"
description="The name of the algorithm to use for SSO session ID generation"
type="java.lang.String"/>
<attribute name="secureRandomClass"
description="The name of the class to use for SSO session ID generation"
type="java.lang.String"/>
<attribute name="secureRandomProvider"
description="The name of the provider to use for SSO session ID generation"
type="java.lang.String"/>
<attribute name="stateName"
description="The name of the LifecycleState that this component is currently in"
type="java.lang.String"
writeable="false"/>
</mbean>
</mbeans-descriptors>

View File

@@ -0,0 +1,54 @@
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<body>
<p>This package contains <code>Authenticator</code> implementations for the
various supported authentication methods (BASIC, DIGEST, and FORM). In
addition, there is a convenience base class,
<code>AuthenticatorBase</code>, for customized <code>Authenticator</code>
implementations.</p>
<p>If you are using the standard context configuration class
(<code>org.apache.catalina.startup.ContextConfig</code>) to configure the
Authenticator associated with a particular context, you can register the Java
class to be used for each possible authentication method by modifying the
following Properties file:</p>
<pre>
src/share/org/apache/catalina/startup/Authenticators.properties
</pre>
<p>Each of the standard implementations extends a common base class
(<code>AuthenticatorBase</code>), which is configured by setting the
following JavaBeans properties (with default values in square brackets):</p>
<ul>
<li><b>cache</b> - Should we cache authenticated Principals (thus avoiding
per-request lookups in our underlying <code>Realm</code>) if this request
is part of an HTTP session? [true]</li>
<li><b>debug</b> - Debugging detail level for this component. [0]</li>
</ul>
<p>The standard authentication methods that are currently provided include:</p>
<ul>
<li><b>BasicAuthenticator</b> - Implements HTTP BASIC authentication, as
described in RFC 2617.</li>
<li><b>DigestAuthenticator</b> - Implements HTTP DIGEST authentication, as
described in RFC 2617.</li>
<li><b>FormAuthenticator</b> - Implements FORM-BASED authentication, as
described in the Servlet API Specification.</li>
</ul>
</body>