com.kolich.havalo.filters.HavaloAuthenticationFilter.java Source code

Java tutorial

Introduction

Here is the source code for com.kolich.havalo.filters.HavaloAuthenticationFilter.java

Source

/**
 * Copyright (c) 2015 Mark S. Kolich
 * http://mark.koli.ch
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following
 * conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 */

package com.kolich.havalo.filters;

import com.kolich.curacao.CuracaoContext;
import com.kolich.curacao.annotations.Injectable;
import com.kolich.curacao.mappers.request.filters.CuracaoRequestFilter;
import com.kolich.havalo.components.RepositoryManagerComponent;
import com.kolich.havalo.entities.types.KeyPair;
import com.kolich.havalo.exceptions.authentication.AuthenticationException;
import com.kolich.havalo.exceptions.authentication.BadCredentialsException;
import org.slf4j.Logger;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.http.HttpServletRequest;
import java.util.UUID;

import static com.google.common.net.HttpHeaders.*;
import static org.apache.commons.codec.binary.Base64.encodeBase64;
import static org.apache.commons.codec.binary.StringUtils.getBytesUtf8;
import static org.apache.commons.codec.binary.StringUtils.newStringUtf8;
import static org.apache.commons.io.IOUtils.LINE_SEPARATOR_UNIX;
import static org.slf4j.LoggerFactory.getLogger;

public final class HavaloAuthenticationFilter implements CuracaoRequestFilter {

    private static final Logger logger__ = getLogger(HavaloAuthenticationFilter.class);

    public static final String HAVALO_AUTHENTICATION_ATTRIBUTE = "havalo.authentication";

    private static final String HAVALO_AUTHORIZATION_PREFIX = "Havalo ";
    private static final String HAVALO_AUTHORIZATION_SEPARATOR = ":";

    private final HavaloUserService userService_;

    @Injectable
    public HavaloAuthenticationFilter(final RepositoryManagerComponent component) {
        userService_ = new HavaloUserService(component.getRepositoryManager());
    }

    @Override
    public final void filter(final CuracaoContext context) throws Exception {
        try {
            final HttpServletRequest request = context.request_;
            // Extract the Authorization header from the incoming HTTP request.
            String header = request.getHeader(AUTHORIZATION);
            // If the header does not exist or does not start with the correct
            // token, give up immediately.
            if (header == null || !header.startsWith(HAVALO_AUTHORIZATION_PREFIX)) {
                throw new AuthenticationException(
                        "Request did not contain " + "a valid '" + AUTHORIZATION + "' header.");
            }
            // Extract just the part of the Authorization header that follows
            // the Havalo authorization prefix.
            header = header.substring(HAVALO_AUTHORIZATION_PREFIX.length());
            final String[] tokens = header.split(HAVALO_AUTHORIZATION_SEPARATOR, 2);
            if (tokens == null || tokens.length != 2) {
                throw new AuthenticationException(
                        "Failed to extract correct " + "number of tokens from '" + AUTHORIZATION + "' header.");
            }
            // If we get here, then we must have had some valid input
            // Authorization header with a real access key and signature.
            final String accessKey = tokens[0], signature = tokens[1];
            // request.getRequestURI();
            // Extract username from incoming signed request header.
            // Expected format is ...
            //    Authorization: Havalo AccessKey:Signature
            // ... where the AccessKey is the unique UUID used to identify the user.
            // And, the Signature is ...
            //    Base64( HMAC-SHA256( UTF-8-Encoding-Of( AccessSecret, StringToSign ) ) );
            // And, the StringToSign is ....
            //    HTTP-Verb (GET, PUT, POST, or DELETE) + "\n" +
            //    RFC822 Date (from 'Date' request header, must exist) + "\n" +
            //    Content-Type (from 'Content-Type' request header, optional) + "\n" +
            //    CanonicalizedResource (the part of this request's URL from
            //        the protocol name up to the query string in the first line
            //        of the HTTP request)
            // Call the user details service to load the user data for the UUID.
            final KeyPair userKp = userService_.loadKeyPairById(UUID.fromString(accessKey));
            if (userKp == null) {
                throw new AuthenticationException(
                        "User service returned " + "null, which is an interface contract violation.");
            }
            // Get the string to sign -- will fail gracefully if the incoming
            // request does not have the proper headers attached to it.
            final String stringToSign = getStringToSign(request);
            // Compute the resulting signed signature.
            final String computed = HMACSHA256Signer.sign(userKp, stringToSign);
            // Does the signature match what was passed to us in the
            // Authorization request header?
            if (!computed.equals(signature)) {
                throw new BadCredentialsException(
                        "Signatures did not " + "match (request=" + signature + ", computed=" + computed + ")");
            }
            // Success!
            request.setAttribute(HAVALO_AUTHENTICATION_ATTRIBUTE, userKp);
        } catch (Exception e) {
            logger__.debug("Authentication failure; service failed " + "to authenticate request.", e);
            throw new AuthenticationException(
                    "Authentication " + "failed; either the provided signature did not match, "
                            + "or you do not have permission to access the requested " + "resource.",
                    e);
        }
    }

    private static final String getStringToSign(final HttpServletRequest request) {
        final StringBuilder sb = new StringBuilder();
        // HTTP-Verb (GET, PUT, POST, or DELETE) + "\n"
        sb.append(request.getMethod().toUpperCase()).append(LINE_SEPARATOR_UNIX);
        // RFC822 Date (from 'Date' request header, must exist) + "\n" +
        final String dateHeader;
        if ((dateHeader = request.getHeader(DATE)) == null) {
            throw new BadCredentialsException(
                    "Incoming request missing " + "required '" + DATE + "' request header.");
        }
        sb.append(dateHeader).append(LINE_SEPARATOR_UNIX);
        // Content-Type (from 'Content-Type' request header, optional) + "\n" +
        final String contentType;
        if ((contentType = request.getHeader(CONTENT_TYPE)) != null) {
            sb.append(contentType);
        }
        sb.append(LINE_SEPARATOR_UNIX);
        // CanonicalizedResource
        sb.append(request.getRequestURI());
        return sb.toString();
    }

    /**
    * Computes an HMAC-SHA256 signature.
    */
    private static final class HMACSHA256Signer {

        private static final String HMAC_SHA256_ALGORITHM_NAME = "HmacSHA256";

        /**
          * Returns a Base-64 encoded HMAC-SHA256 signature.
          */
        public static final String sign(final KeyPair kp, final String input) {
            try {
                // Get a new instance of the HMAC-SHA256 algorithm.
                final Mac mac = Mac.getInstance(HMAC_SHA256_ALGORITHM_NAME);
                // Init it with our secret and the secret-key algorithm.
                mac.init(new SecretKeySpec(getBytesUtf8(kp.getSecret()), HMAC_SHA256_ALGORITHM_NAME));
                // Actually sign the input.
                return newStringUtf8(encodeBase64(mac.doFinal(getBytesUtf8(input))));
            } catch (Exception e) {
                throw new AuthenticationException("Failed to SHA-256 " + "sign input string: " + input, e);
            }
        }

    }

}