| /* |
| * 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.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 static final Log log = LogFactory.getLog(BasicAuthenticator.class); |
| |
| |
| // --------------------------------------------------------- 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 |
| public boolean authenticate(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); |
| 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('\"'); |
| 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. |
| */ |
| protected 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 ByteChunk authorization; |
| private 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 |
| * |
| * @throws IllegalArgumentException If the header does not conform |
| * to RFC 2617 |
| */ |
| public BasicCredentials(ByteChunk input) |
| throws IllegalArgumentException { |
| authorization = input; |
| initialOffset = input.getOffset(); |
| 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, StandardCharsets.ISO_8859_1); |
| // password will remain null! |
| } |
| else { |
| username = new String( |
| decoded, 0, colon, StandardCharsets.ISO_8859_1); |
| password = new String( |
| decoded, colon + 1, decoded.length - colon - 1, |
| StandardCharsets.ISO_8859_1); |
| // tolerate surplus white space around credentials |
| if (password.length() > 1) { |
| password = password.trim(); |
| } |
| } |
| // tolerate surplus white space around credentials |
| if (username.length() > 1) { |
| username = username.trim(); |
| } |
| } |
| } |
| } |