Search code examples
javaspringunit-testingauthenticationlombok

Mocking @AuthenticationPrincipal for a unit test


I am new to spring and lombok. I am working on a unit test for a controller where the method uses @AuthenticationPrincipal. From what I have read it passes the information of the authenticated user into the method. Is there a way to mock this for the unit test?


Solution

  • The easiest solution that comes to mind is @WithMockUser. It does what you asked for. But, in case you are not satisfied with it, you can create an annotation and tune it to your needs.

    I am assuming that you have tried @WithMockUser and it really didn't fit your needs.

    There are three important tasks. The first is creating an annotation followed by creating a class that represents a user of your system and finally creating SecurityContextFactory.

    1. Let's start with the annotation
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    
    import org.springframework.security.test.context.support.WithSecurityContext;
    
    @Retention(RetentionPolicy.RUNTIME)
    @WithSecurityContext(factory = WithMockAaronSecurityContextFactory.class)
    public @interface WithAaronUser {
        String username() default "TestUser";
        String[] roles() default { "ROLE_ADMIN" };
    }
    
    
    1. A mock user class
    import java.util.Collection;
    import java.util.stream.Collectors;
    import java.util.stream.Stream;
    
    import org.springframework.security.core.GrantedAuthority;
    import org.springframework.security.core.authority.SimpleGrantedAuthority;
    
    public class MockUserDetails {
    
        private String username;
        private Collection<? extends GrantedAuthority> authorities;
        
        public MockUserDetails(String username, String[] roles) {
            this.username = username;
            this.authorities = Stream.of(roles)
                    .map(role -> new SimpleGrantedAuthority(role)).collect(Collectors.toSet());
        }
    
        public String getUsername() {
            return username;
        }
    
        public Collection<? extends GrantedAuthority> getAuthorities() {
            return authorities;
        }
    }
    

    3 Finally the SecurityContextFactory. Note that the implementation uses the same factory class (WithSecurityContextFactory) that @WithMockUser uses.

    import java.io.Serializable;
    import java.util.Collections;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    import java.util.Set;
    
    import org.springframework.mock.web.MockHttpServletRequest;
    import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
    import org.springframework.security.core.GrantedAuthority;
    import org.springframework.security.core.authority.AuthorityUtils;
    import org.springframework.security.core.context.SecurityContext;
    import org.springframework.security.core.context.SecurityContextHolder;
    import org.springframework.security.oauth2.provider.OAuth2Authentication;
    import org.springframework.security.oauth2.provider.OAuth2Request;
    import org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationDetails;
    import org.springframework.security.test.context.support.WithSecurityContextFactory;
    
    public class WithMockAaronSecurityContextFactory implements WithSecurityContextFactory<WithAaronUser> {
    
        @Override
        public SecurityContext createSecurityContext(WithAaronUser user) {
    
            MockUserDetails principal = new MockUserDetails(user.username(), user.roles());
    
            MockHttpServletRequest request = new MockHttpServletRequest();
            request.setServerName("www.example.com");
            request.setRequestURI("/token");
            request.setQueryString("param1=value1&param");
            request.setAttribute(OAuth2AuthenticationDetails.ACCESS_TOKEN_VALUE, "mocked_token");
            OAuth2AuthenticationDetails authDetails = new OAuth2AuthenticationDetails(request);
            
            Map<String, String> decodedDetails = new HashMap<>();
            decodedDetails.put("first_name", "Jean-Claude");
            decodedDetails.put("last_name", "Van Damme");
            authDetails.setDecodedDetails(decodedDetails);
            
            
            UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(principal.getUsername(), "password", principal.getAuthorities());
            auth.setDetails(authDetails);
            
            
            List<GrantedAuthority> authorities = AuthorityUtils.createAuthorityList("ROLE_ADMIN");
            OAuth2Request oAuth2Request = new OAuth2Request(Collections.emptyMap(), "", authorities, true, Collections.emptySet(), Collections.emptySet(), "http://somethig.com", Collections.emptySet(), Collections.emptyMap());
                        
                        
            OAuth2Authentication oAuth = new OAuth2Authentication(getOauth2Request(), auth);
            oAuth.setDetails(authDetails);
                
            SecurityContext context = SecurityContextHolder.createEmptyContext();
            context.setAuthentication(oAuth);
            return context;
        }
    }