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

How do you mock a Springframework security principal for testing spring MVC controllers?

  1. Create a spring security principal
  2. Create a spring authentication
  3. Add the spring security authentication to the security context

The last step should not be needed because MockMVC has a method to set the principal (line 16). However, we had an annotation that used the security principal to retrieve the username that was then used to retrieve another object. We couldn’t find the value there. We set the spring security authentication manually via the spring security context (line 19).

public class TestActivityController {
    @Autowired
    private WebApplicationContext wac;

    private MockMvc mockMvc;

    @Before
    public void setup() {
        mockMvc = MockMvcBuilders.webAppContextSetup(wac).build();
    }

    @Test
    public void testInitialDisplay() throws Exception {
        User user = new User("screen011","", AuthorityUtils.createAuthorityList("ROLE_PATRON"));
        TestingAuthenticationToken testingAuthenticationToken = new TestingAuthenticationToken(user,null);
        SecurityContextHolder.getContext().setAuthentication(testingAuthenticationToken);

        mockMvc.perform(post("/activity/initial")
                //.principal(testingAuthenticationToken))
                .andExpect(status().isOk())
                .andDo(print());
    }
}

February 27th, 2014

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

Tags: , , , , , , , , , , , , , ,

2 Comments

There will be sometimes in your code that you need access to the Springframework security principal object. Here is how you get it. You will need to create a Spring configuration class and annotate it with @EnableWebMvcSecurity

package com.cdi.igs.hub.spring;

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.servlet.configuration.EnableWebMvcSecurity;

/**
 * Spring Security configuration.
 * @author norris.shelton
 */
@Configuration
@EnableWebMvcSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    // nothing needed here
}

The documentation then says to add a method parameter to a Spring MVC controller method with @AuthenticationPrincipal and you are done, like the following:

    @RequestMapping(value = "/{personId}", method = RequestMethod.GET)
    public ModelAndView getPersonAccount(@PathVariable int personId, @AuthenticationPrincipal User user) {
        ModelAndView modelAndView =  new ModelAndView("dashboard/account");
        modelAndView.addObject("person", personRepository.findOne(personId));
        return modelAndView;
    }

What they don’t tell you is that you need to configure a AuthenticationPrincipalArgumentResolver. Nor they tell you how to do it. This is the missing piece.

    <mvc:annotation-driven>
        <mvc:argument-resolvers>
            <bean class="org.springframework.security.web.bind.support.AuthenticationPrincipalArgumentResolver"/>
        </mvc:argument-resolvers>
    </mvc:annotation-driven>

January 14th, 2014

Posted In: Spring, Spring MVC, Spring Security

Tags: , , , , ,

17 Comments

LinkedIn Auto Publish Powered By : XYZScripts.com