You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tomcat.apache.org by ma...@apache.org on 2015/06/23 11:57:16 UTC

svn commit: r1687015 - in /tomcat/trunk/java/org/apache/catalina/authenticator/jaspic/provider: TomcatAuthConfig.java modules/BasicAuthModule.java

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-unsubscribe@tomcat.apache.org
For additional commands, e-mail: dev-help@tomcat.apache.org