org.onehippo.forge.security.support.springsecurity.container.SpringSecurityValve.java Source code

Java tutorial

Introduction

Here is the source code for org.onehippo.forge.security.support.springsecurity.container.SpringSecurityValve.java

Source

/*
 *  Copyright 2011 Hippo.
 *
 *  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.
 */
package org.onehippo.forge.security.support.springsecurity.container;

import java.security.Principal;
import java.util.HashSet;
import java.util.Set;

import javax.jcr.Credentials;
import javax.jcr.SimpleCredentials;
import javax.security.auth.Subject;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

import org.apache.commons.lang.StringUtils;
import org.hippoecm.hst.container.valves.AbstractOrderableValve;
import org.hippoecm.hst.core.container.ContainerConstants;
import org.hippoecm.hst.core.container.ContainerException;
import org.hippoecm.hst.core.container.ValveContext;
import org.hippoecm.hst.security.TransientRole;
import org.hippoecm.hst.security.TransientUser;
import org.hippoecm.hst.security.User;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;

/**
 * HST-2 request processing valve for integration with Spring Security Framework.
 * <P>
 * This is responsible for reading the <code>org.springframework.security.core.Authentication</code> instance if exists,
 * and establishing <code>javax.security.auth.Subject</code> for the whole HST-2 request processing
 * by converting <code>org.springframework.security.core.userdetails.UserDetails</code> into a set of
 * <code>java.security.Principal</code>s (user principal and role principals from the collection of 
 * <code>org.springframework.security.core.GrantedAuthority</code>).
 * </P>
 */
public class SpringSecurityValve extends AbstractOrderableValve {

    private static Logger log = LoggerFactory.getLogger(SpringSecurityValve.class);

    private static final char[] DUMMY_CHARS = { 'D', 'U', 'M', 'M', 'Y' };

    /**
     * Flag whether or not to store JCR credentials as Subject's private credentials
     * simply by converting username/password to <code>javax.jcr.SimpleCredentials</code> object.
     */
    private boolean storeSubjectRepositoryCredentials = true;

    /**
     * Returns true if the option to store JCR credentials as Subject's private credentials is turned on.
     * @return true if the option to store JCR credentials as Subject's private credentials is turned on
     */
    public boolean isStoreSubjectRepositoryCredentials() {
        return storeSubjectRepositoryCredentials;
    }

    /**
     * Sets the flag whether or not to store JCR credentials as Subject's private credentials.
     * @param storeSubjectRepositoryCredentials flag whether or not to store subject repository credentials
     */
    public void setStoreSubjectRepositoryCredentials(boolean storeSubjectRepositoryCredentials) {
        this.storeSubjectRepositoryCredentials = storeSubjectRepositoryCredentials;
    }

    @Override
    public void invoke(ValveContext context) throws ContainerException {
        HttpServletRequest request = context.getServletRequest();
        Principal userPrincipal = request.getUserPrincipal();

        // If user has not been authenticated yet by any mechanism, then simply move to the next valve chain.
        if (userPrincipal == null) {
            if (log.isDebugEnabled()) {
                log.debug("No user principal found. Skipping SpringSecurityValve...");
            }
            context.invokeNext();
            return;
        }

        // Get the current subject from http session if exists.
        HttpSession session = request.getSession(false);
        Subject subject = (session != null ? (Subject) session.getAttribute(ContainerConstants.SUBJECT_ATTR_NAME)
                : null);

        // If a subject has been established already (normally by HST-2's SecurityValve), then simply move to the next valve chain.
        if (subject != null) {
            if (log.isDebugEnabled()) {
                log.debug("Already subject has been created somewhere before. Skipping SpringSecurityValve...");
            }
            context.invokeNext();
            return;
        }

        // Get Spring Security Context object from thread local.
        SecurityContext securityContext = SecurityContextHolder.getContext();

        // If there's no Spring Security Context object, then just move to next valve chain.
        if (securityContext == null) {
            if (log.isDebugEnabled()) {
                log.debug("Spring Security hasn't established security context. Skipping SpringSecurityValve...");
            }
            context.invokeNext();
            return;
        }

        // Get the Authentication object from the Spring Security context object.
        Authentication authentication = securityContext.getAuthentication();

        // If there's no Authentication object, it's really weird, so leave warning logs, and move to next valve chain.
        if (authentication == null) {
            if (log.isWarnEnabled()) {
                log.warn(
                        "Spring Security hasn't establish security context with authentication object. Skipping SpringSecurityValve...");
            }
            context.invokeNext();
            return;
        }

        // Get principal object from the Spring Security authentication object.
        Object springSecurityPrincipal = authentication.getPrincipal();

        // We expect the principal is instance of UserDetails. Otherwise, let's skip it and leave warning logs.
        if (!(springSecurityPrincipal instanceof UserDetails)) {
            if (log.isWarnEnabled()) {
                log.warn(
                        "Spring Security hasn't establish security context with UserDetails object. We don't support non UserDetails authentication. Skipping SpringSecurityValve...");
            }
            context.invokeNext();
            return;
        }

        // Cast principal instance to UserDetails 
        UserDetails userDetails = (UserDetails) springSecurityPrincipal;

        // Create HST-2 TransientUser principal from the user principal.
        User user = new TransientUser(userPrincipal.getName());

        // Add both the existing user principal and new HST-2 user transient user principal
        // just for the case when HST-2 can inspect the user principals for some reasons.
        Set<Principal> principals = new HashSet<Principal>();
        principals.add(userPrincipal);
        principals.add(user);

        // Retrieve all the granted authorities from the UserDetail instance
        // and convert it into HST-2 TransientRoles.
        for (GrantedAuthority authority : userDetails.getAuthorities()) {
            String authorityName = authority.getAuthority();
            if (!StringUtils.isEmpty(authorityName)) {
                principals.add(new TransientRole(authorityName));
            }
        }

        Set<Object> pubCred = new HashSet<Object>();
        Set<Object> privCred = new HashSet<Object>();

        // If the flag is turned on, then store JCR credentials as well
        // just for the case the site is expected to use session stateful JCR sessions per authentication.
        if (storeSubjectRepositoryCredentials) {
            Credentials subjectRepoCreds = null;

            // Note: password should be null by default from some moment after Spring Security version upgraded a while ago.
            //       if password is null, let's store a dummy password instead.

            if (userDetails.getPassword() != null) {
                subjectRepoCreds = new SimpleCredentials(userDetails.getUsername(),
                        userDetails.getPassword().toCharArray());
            } else {
                subjectRepoCreds = new SimpleCredentials(userDetails.getUsername(), DUMMY_CHARS);
            }

            privCred.add(subjectRepoCreds);
        }

        subject = new Subject(true, principals, pubCred, privCred);

        // Save the created subject as http session attribute which can be read by HST-2 SecurityValve in the next valve chain.
        request.getSession(true).setAttribute(ContainerConstants.SUBJECT_ATTR_NAME, subject);

        context.invokeNext();
    }
}