Java Ninja Chronicles By Norris Shelton

Things I learned in the pursuit of code

I had previously made a blog entry to retrieve the Springframework security principal via @AuthenticationPrincipal in a controller method.
Getting the Spring Security Principal in a Spring MVC Controller method.

That is useful if you need the username or the password. What do you do if you need more?

I made a @PersonPrincipal annotation to do more than @AuthenticationPrincipal

package com.cdi.igs.hub.spring;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * Annotation that binds a method parameter or method return value to the
 * {@link com.cdi.igs.dao.person.Person}. This is necessary to signal that the
 * argument should be resolved to the current user rather than a user that might
 * be edited on a form.
 *
 * @author norris.shelton
 * @since 3.2
 */
@Target({ ElementType.PARAMETER, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface PersonPrincipal {
}

I then made a PersonArgumentResolver that can handle the processing required for the @PersonPrincipal annotation. The resolver first looks in the session to see if the Person object is there. If the Person object still isn’t found, the Authentication object will be retrieved from the security context. The SecurityUtil we be used to retrieve the person by username if necessary.

package com.cdi.igs.hub.spring;

import com.cdi.igs.dao.person.Person;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.MethodParameter;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.lang.annotation.Annotation;


/**
 * Allows resolving the {@link com.cdi.igs.dao.person.Person} using the
 * {@link com.cdi.igs.hub.spring.PersonPrincipal} annotation. For example, the following
 * {@link org.springframework.stereotype.Controller}:
 *
 * <pre>
 * @Controller
 * public class MyController {
 *     @RequestMapping("/user/current/show")
 *     public String show(@PersonPrincipal Person person) {
 *         // do something with Person
 *         return "view";
 *     }
 * </pre>
 *
 * <p>
 * Will resolve the Person argument using
 * {@link com.cdi.igs.dao.person.Person} from the {@link javax.servlet.http.HttpSession} if the person is an
 * attribute, else will use the {@link org.springframework.security.core.context.SecurityContextHolder} to get the
 * information to retrieve the person from the {@link com.cdi.igs.dao.person.PersonRepository}.
 * If the {@link org.springframework.security.core.Authentication} or {@link org.springframework.security.core.Authentication#getPrincipal()} is
 * null, it will return null.
 * </p>
 *
 * @author norris.shelton
 * @since 3.2
 */
@Component
public final class PersonArgumentResolver implements HandlerMethodArgumentResolver {

    @Autowired
    private SecurityUtil SecurityUtil;

    /** (non-Javadoc)
     * @see org.springframework.web.method.support.HandlerMethodArgumentResolver#supportsParameter(org.springframework.core.MethodParameter)
     */
    public boolean supportsParameter(MethodParameter parameter) {
        return findMethodAnnotation(PersonPrincipal.class, parameter) != null;
    }

    /** (non-Javadoc)
     * @see org.springframework.web.method.support.HandlerMethodArgumentResolver#resolveArgument(org.springframework.core.MethodParameter, org.springframework.web.method.support.ModelAndViewContainer, org.springframework.web.context.request.NativeWebRequest, org.springframework.web.bind.support.WebDataBinderFactory)
     */
    public Object resolveArgument(MethodParameter parameter,
                                  ModelAndViewContainer mavContainer,
                                  NativeWebRequest webRequest,
                                  WebDataBinderFactory binderFactory)
    throws Exception {
        Person person = null;

        // get "person" out of the session
        HttpServletRequest request = (HttpServletRequest) webRequest.getNativeRequest();
        HttpSession session = request.getSession(false);
        if (session != null) {
            person = (Person) session.getAttribute("person");

        }

        // if not found in the session, get by using the authenticated principal
        if (person == null) {
            Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
            person = SecurityUtil.getPersonByAuthentication(authentication);

            // if found, store in the session for next time
            if (person != null &amp;&amp; session != null) {
                session.setAttribute("person", person);
            }
        }
        return person;
    }

    /**
     * Obtains the specified {@link java.lang.annotation.Annotation} on the specified {@link org.springframework.core.MethodParameter}.
     *
     * @param annotationClass the class of the {@link java.lang.annotation.Annotation} to find on the {@link org.springframework.core.MethodParameter}
     * @param parameter the {@link org.springframework.core.MethodParameter} to search for an {@link java.lang.annotation.Annotation}
     * @return the {@link java.lang.annotation.Annotation} that was found or null.
     */
    private <T extends Annotation> T findMethodAnnotation(Class<T> annotationClass, MethodParameter parameter) {
        T annotation = parameter.getParameterAnnotation(annotationClass);
        if(annotation != null) {
            return annotation;
        }
        Annotation[] annotationsToSearch = parameter.getParameterAnnotations();
        for(Annotation toSearch : annotationsToSearch) {
            annotation = AnnotationUtils.findAnnotation(toSearch.annotationType(), annotationClass);
            if(annotation != null) {
                return annotation;
            }
        }
        return null;
    }
}

The SecurityUtil is pretty simple. Given an Authentication object, retrieve the username property and use that to retrieve a Person object.

package com.cdi.igs.hub.spring;

import com.cdi.igs.dao.person.Person;
import com.cdi.igs.dao.person.PersonRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.User;
import org.springframework.stereotype.Component;

/**
 * Spring security related tools.
 * @author norrisshelton
 */
@Component
public class SecurityUtil {

    @Autowired
    private PersonRepository personRepository;

    /**
     * Retrieves the person object associated with the given authentication.
     * @param authentication Represents the token for an authentication request
     * @return person object, if one could be found
     */
    public Person getPersonByAuthentication(Authentication authentication) {
        Person person = null;
        if (authentication != null) {
            User user = (User) authentication.getPrincipal();

            if (user != null) {
                // get the person from the database and store in the session
                person = personRepository.findByUserName(user.getUsername());
            }

        }
        return person;
    }
}

I then registered the PersonArgumentResolver with the other argument resolvers for the Spring MVC framework.

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans    http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/mvc      http://www.springframework.org/schema/mvc/spring-mvc.xsd">

    <!-- Needed for Spring MVC Mockito tests in addition to the production context-->
    <mvc:annotation-driven>
        <mvc:argument-resolvers>
            <bean class="org.springframework.security.web.bind.support.AuthenticationPrincipalArgumentResolver"/>
            <bean class="com.cdi.igs.hub.spring.PersonArgumentResolver"/>
        </mvc:argument-resolvers>
    </mvc:annotation-driven>

</beans>

February 27th, 2014

Posted In: Java, Spring, Spring MVC, Spring Security

4 Comments

  • Arik Cohen says:

    Hi Norris, great post. Interestingly I have the same requirement on a project I am working right now and my solution was to extend the JdbcDaoImpl so as to override the createUserDetails method to retrieve an extended UserDetails object with all the data I that need. I do however, like your approach better as it is cleaner and does not require any extending nor adding business logic to the security layer which is not ideal.

    • I think I will need to use the JdbcDaoImpl in a couple of weeks. I didn’t use it in this task, because I didn’t actually need to perform authentication. I just needed to get the object created and set the username.

      Thanks for reading and the great feedback.

  • […] => When the Spring Security Principal isn’t enough in a Spring MVC Controller […]

  • Brian says:

    I’ve also created my own UserDetails by extending it into my user object. The problem with this approach I’ve found is if the UserDetails object is updated, in order to get it into the session, you essentially need to re-login the user to set it into the session.

    Authentication authentication = new UsernamePasswordAuthenticationToken(currentUser, currentUser.getPassword(), currentUser.getAuthorities());
    SecurityContextHolder.getContext().setAuthentication(authentication);

    It seems bizarre that this would have to be called upon every update to a extended UserDetails object. This approach separates the UserDetails from the applications user object allowing for hibernate and spring to automatically update this object without the need to add the userdetails back into the session

Leave a Reply

Your email address will not be published. Required fields are marked *

LinkedIn Auto Publish Powered By : XYZScripts.com