Author: markt Date: Tue Jun 23 09:57:16 2015 New Revision: 1687015 URL: http://svn.apache.org/r1687015 Log: Implemented JASPIC module for BASIC authentication Patch by fjodorver
Added: tomcat/trunk/java/org/apache/catalina/authenticator/jaspic/provider/modules/BasicAuthModule.java (with props) Modified: tomcat/trunk/java/org/apache/catalina/authenticator/jaspic/provider/TomcatAuthConfig.java Modified: tomcat/trunk/java/org/apache/catalina/authenticator/jaspic/provider/TomcatAuthConfig.java URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/catalina/authenticator/jaspic/provider/TomcatAuthConfig.java?rev=1687015&r1=1687014&r2=1687015&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/catalina/authenticator/jaspic/provider/TomcatAuthConfig.java (original) +++ tomcat/trunk/java/org/apache/catalina/authenticator/jaspic/provider/TomcatAuthConfig.java Tue Jun 23 09:57:16 2015 @@ -29,6 +29,7 @@ import javax.security.auth.message.confi import javax.security.auth.message.config.ServerAuthContext; import org.apache.catalina.Realm; +import org.apache.catalina.authenticator.jaspic.provider.modules.BasicAuthModule; import org.apache.catalina.authenticator.jaspic.provider.modules.TomcatAuthModule; public class TomcatAuthConfig implements ServerAuthConfig { @@ -92,6 +93,7 @@ public class TomcatAuthConfig implements private Collection<TomcatAuthModule> getModules() { List<TomcatAuthModule> modules = new ArrayList<>(); + modules.add(new BasicAuthModule()); return modules; } } Added: tomcat/trunk/java/org/apache/catalina/authenticator/jaspic/provider/modules/BasicAuthModule.java URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/catalina/authenticator/jaspic/provider/modules/BasicAuthModule.java?rev=1687015&view=auto ============================================================================== --- tomcat/trunk/java/org/apache/catalina/authenticator/jaspic/provider/modules/BasicAuthModule.java (added) +++ tomcat/trunk/java/org/apache/catalina/authenticator/jaspic/provider/modules/BasicAuthModule.java Tue Jun 23 09:57:16 2015 @@ -0,0 +1,278 @@ +/* + * 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.provider.modules; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.text.MessageFormat; +import java.util.Iterator; +import java.util.Map; + +import javax.security.auth.Subject; +import javax.security.auth.callback.Callback; +import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.message.AuthException; +import javax.security.auth.message.AuthStatus; +import javax.security.auth.message.MessageInfo; +import javax.security.auth.message.MessagePolicy; +import javax.security.auth.message.callback.CallerPrincipalCallback; +import javax.security.auth.message.callback.GroupPrincipalCallback; +import javax.security.auth.message.callback.PasswordValidationCallback; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.catalina.realm.GenericPrincipal; +import org.apache.tomcat.util.buf.ByteChunk; +import org.apache.tomcat.util.buf.MessageBytes; +import org.apache.tomcat.util.codec.binary.Base64; + +public class BasicAuthModule extends TomcatAuthModule { + + private Class<?>[] supportedMessageTypes = new Class[] { HttpServletRequest.class, + HttpServletResponse.class }; + + private CallbackHandler handler; + + + @Override + public String getAuthenticationType() { + return "BASIC"; + } + + + @SuppressWarnings("rawtypes") + @Override + public void initialize(MessagePolicy requestPolicy, MessagePolicy responsePolicy, + CallbackHandler handler, Map options) throws AuthException { + this.handler = handler; + } + + + @Override + public AuthStatus validateRequest(MessageInfo messageInfo, Subject clientSubject, + Subject serviceSubject) throws AuthException { + if (!isMandatory(messageInfo)) { + return AuthStatus.SUCCESS; + } + + HttpServletRequest request = (HttpServletRequest) messageInfo.getRequestMessage(); + HttpServletResponse response = (HttpServletResponse) messageInfo.getResponseMessage(); + String authorization = request.getHeader(AUTHORIZATION_HEADER); + + String realmName = getRealmName(messageInfo); + + if (authorization == null) { + return sendUnauthorizedError(response, realmName); + } + + BasicCredentials credentials = parseAuthorizationString(authorization); + String username = credentials.getUsername(); + char[] password = credentials.getPassword().toCharArray(); + + try { + PasswordValidationCallback passwordCallback = new PasswordValidationCallback( + clientSubject, username, password); + handler.handle(new Callback[] { passwordCallback }); + + if (!passwordCallback.getResult()) { + sendUnauthorizedError(response, realmName); + } + + GenericPrincipal principal = getPrincipal(passwordCallback); + + CallerPrincipalCallback principalCallback = new CallerPrincipalCallback(clientSubject, + principal); + GroupPrincipalCallback groupCallback = new GroupPrincipalCallback(clientSubject, + principal.getRoles()); + handler.handle(new Callback[] { principalCallback, groupCallback }); + return AuthStatus.SUCCESS; + + } catch (Exception e) { + throw new AuthException(e.getMessage()); + } + } + + + private AuthStatus sendUnauthorizedError(HttpServletResponse response, String realmName) + throws AuthException { + String authHeader = MessageFormat.format("Basic realm=\"{0}\"", realmName); + response.setHeader(AUTH_HEADER_NAME, authHeader); + try { + response.sendError(HttpServletResponse.SC_UNAUTHORIZED); + } catch (IOException e) { + throw new AuthException(e.getMessage()); + } + return AuthStatus.SEND_CONTINUE; + } + + + private GenericPrincipal getPrincipal(PasswordValidationCallback passwordCallback) { + Iterator<Object> credentials = + passwordCallback.getSubject().getPrivateCredentials().iterator(); + return (GenericPrincipal) credentials.next(); + } + + + private BasicCredentials parseAuthorizationString(String authorization) { + MessageBytes authorizationBytes = MessageBytes.newInstance(); + authorizationBytes.setString(authorization); + authorizationBytes.toBytes(); + ByteChunk authorizationBC = authorizationBytes.getByteChunk(); + return new BasicCredentials(authorizationBC); + } + + + @Override + public AuthStatus secureResponse(MessageInfo messageInfo, Subject serviceSubject) + throws AuthException { + return null; + } + + + @Override + public void cleanSubject(MessageInfo messageInfo, Subject subject) throws AuthException { + + } + + + @Override + public Class<?>[] getSupportedMessageTypes() { + return supportedMessageTypes; + } + + + /** + * 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 have 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(); + } + } + } +} Propchange: tomcat/trunk/java/org/apache/catalina/authenticator/jaspic/provider/modules/BasicAuthModule.java ------------------------------------------------------------------------------ svn:eol-style = native --------------------------------------------------------------------- To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org For additional commands, e-mail: dev-h...@tomcat.apache.org