/** * Licensed 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. See accompanying LICENSE file. */ package org.apache.hadoop.security.authentication.server; import org.apache.hadoop.security.authentication.client.AuthenticationException; import java.security.Principal; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.StringTokenizer; import javax.servlet.http.HttpServletRequest; /** * The {@link AuthenticationToken} contains information about an authenticated * HTTP client and doubles as the {@link Principal} to be returned by * authenticated {@link HttpServletRequest}s * <p/> * The token can be serialized/deserialized to and from a string as it is sent * and received in HTTP client responses and requests as a HTTP cookie (this is * done by the {@link AuthenticationFilter}). */ public class AuthenticationToken implements Principal { /** * Constant that identifies an anonymous request. */ public static final AuthenticationToken ANONYMOUS = new AuthenticationToken(); private static final String ATTR_SEPARATOR = "&"; private static final String USER_NAME = "u"; private static final String PRINCIPAL = "p"; private static final String EXPIRES = "e"; private static final String TYPE = "t"; private final static Set<String> ATTRIBUTES = new HashSet<String>(Arrays.asList(USER_NAME, PRINCIPAL, EXPIRES, TYPE)); private String userName; private String principal; private String type; private long expires; private String token; private AuthenticationToken() { userName = null; principal = null; type = null; expires = -1; token = "ANONYMOUS"; generateToken(); } private static final String ILLEGAL_ARG_MSG = " is NULL, empty or contains a '" + ATTR_SEPARATOR + "'"; /** * Creates an authentication token. * * @param userName user name. * @param principal principal (commonly matches the user name, with Kerberos is the full/long principal * name while the userName is the short name). * @param type the authentication mechanism name. * (<code>System.currentTimeMillis() + validityPeriod</code>). */ public AuthenticationToken(String userName, String principal, String type) { checkForIllegalArgument(userName, "userName"); checkForIllegalArgument(principal, "principal"); checkForIllegalArgument(type, "type"); this.userName = userName; this.principal = principal; this.type = type; this.expires = -1; } /** * Check if the provided value is invalid. Throw an error if it is invalid, NOP otherwise. * * @param value the value to check. * @param name the parameter name to use in an error message if the value is invalid. */ private static void checkForIllegalArgument(String value, String name) { if (value == null || value.length() == 0 || value.contains(ATTR_SEPARATOR)) { throw new IllegalArgumentException(name + ILLEGAL_ARG_MSG); } } /** * Sets the expiration of the token. * * @param expires expiration time of the token in milliseconds since the epoch. */ public void setExpires(long expires) { if (this != AuthenticationToken.ANONYMOUS) { this.expires = expires; generateToken(); } } /** * Generates the token. */ private void generateToken() { StringBuffer sb = new StringBuffer(); sb.append(USER_NAME).append("=").append(userName).append(ATTR_SEPARATOR); sb.append(PRINCIPAL).append("=").append(principal).append(ATTR_SEPARATOR); sb.append(TYPE).append("=").append(type).append(ATTR_SEPARATOR); sb.append(EXPIRES).append("=").append(expires); token = sb.toString(); } /** * Returns the user name. * * @return the user name. */ public String getUserName() { return userName; } /** * Returns the principal name (this method name comes from the JDK {@link Principal} interface). * * @return the principal name. */ @Override public String getName() { return principal; } /** * Returns the authentication mechanism of the token. * * @return the authentication mechanism of the token. */ public String getType() { return type; } /** * Returns the expiration time of the token. * * @return the expiration time of the token, in milliseconds since Epoc. */ public long getExpires() { return expires; } /** * Returns if the token has expired. * * @return if the token has expired. */ public boolean isExpired() { return expires != -1 && System.currentTimeMillis() > expires; } /** * Returns the string representation of the token. * <p/> * This string representation is parseable by the {@link #parse} method. * * @return the string representation of the token. */ @Override public String toString() { return token; } /** * Parses a string into an authentication token. * * @param tokenStr string representation of a token. * * @return the parsed authentication token. * * @throws AuthenticationException thrown if the string representation could not be parsed into * an authentication token. */ public static AuthenticationToken parse(String tokenStr) throws AuthenticationException { Map<String, String> map = split(tokenStr); if (!map.keySet().equals(ATTRIBUTES)) { throw new AuthenticationException("Invalid token string, missing attributes"); } long expires = Long.parseLong(map.get(EXPIRES)); AuthenticationToken token = new AuthenticationToken(map.get(USER_NAME), map.get(PRINCIPAL), map.get(TYPE)); token.setExpires(expires); return token; } /** * Splits the string representation of a token into attributes pairs. * * @param tokenStr string representation of a token. * * @return a map with the attribute pairs of the token. * * @throws AuthenticationException thrown if the string representation of the token could not be broken into * attribute pairs. */ private static Map<String, String> split(String tokenStr) throws AuthenticationException { Map<String, String> map = new HashMap<String, String>(); StringTokenizer st = new StringTokenizer(tokenStr, ATTR_SEPARATOR); while (st.hasMoreTokens()) { String part = st.nextToken(); int separator = part.indexOf('='); if (separator == -1) { throw new AuthenticationException("Invalid authentication token"); } String key = part.substring(0, separator); String value = part.substring(separator + 1); map.put(key, value); } return map; } }