Search code examples
spring-mvcspring-securityspring-cloud

How to mock Principal in spring cloud contract tests?


In REST controller I have several methods on which I need to create contract test and I don't know how to provide Principal for passing tests.

One of the method in Controller which has Principal in parameters:

    @PreAuthorize("hasRole('USER')")
    @GetMapping("/current")
    public Details getCurrent(Principal principal) {
        return houseManager.getById(Principals.getCurrentUserId(principal));
    }

I've created base class for tests:

@RunWith(SpringRunner.class)
@WebMvcTest(controllers = {Controller.class})
@ContextConfiguration(classes = {TestConfig.class, ControllerTestConfig.class})
@ComponentScan(basePackageClasses = {Controller.class})
@AutoConfigureStubRunner
public class ControllersWithSecurityBase {
    @Autowired 
    privet Service service;
    @Autowired
    WebApplicationContext context;
    @Mock
    private Principal mockedPrincipal;

    RestAssuredMockMvc.standaloneSetup(new Controller(service));
    RequestBuilder requestBuilder = MockMvcRequestBuilders
                .get("/") 
                .with(user("user")
                        .password("password")
                        .roles("USER"))
                .principal(mockedPrincipal)
                .accept(MediaType.APPLICATION_JSON);
        MockMvc mockMvc = MockMvcBuilders
                .webAppContextSetup(context)
                .defaultRequest(requestBuilder)
                .apply(springSecurity())
                .build();
        RestAssuredMockMvc.mockMvc(mockMvc);
}

Contract:

Contract.make {
    name("Should find current by principal")
    request {
        method(GET)
        urlPath(".../current")
    }

    response {
        status(200)
    }
}

As result of mvn clean install I've got next exception:

org.springframework.web.util.NestedServletException: Request processing failed; nested exception is java.lang.ClassCastException: org.springframework.security.authentication.UsernamePasswordAuthenticationToken cannot be cast to java.util.Map

What I need to do for correct mocking Principal and passing tests?


Solution

  • My project use Oauth2 security.

    So for mocking Principal object in contract tests I've created bean of OAuth2AuthenticationDetails (this class implements Principal interface).

    Class configuration with bean OAuth2AuthenticationDetails is:

    package com.example.config.fakesecurityconfig;
    
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.mock.web.MockHttpServletRequest;
    import org.springframework.security.authentication.TestingAuthenticationToken;
    import org.springframework.security.core.Authentication;
    import org.springframework.security.core.GrantedAuthority;
    import org.springframework.security.core.authority.SimpleGrantedAuthority;
    import org.springframework.security.oauth2.provider.OAuth2Authentication;
    import org.springframework.security.oauth2.provider.OAuth2Request;
    import org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationDetails;
    
    import java.util.*;
    
    @Configuration
    public class OAuth2TestConfig {
    
        @Bean
        public OAuth2Authentication oAuth2Authentication() {
            return new OAuth2Authentication(getStoredRequest(), getUserAuthentication());
        }
    
        @Bean
        public OAuth2AuthenticationDetails oAuth2AuthenticationDetails() {
            MockHttpServletRequest request = new MockHttpServletRequest();
            return new OAuth2AuthenticationDetails(request);
        }
    
        private OAuth2Request getStoredRequest() {
            Set<String> scope = new HashSet<>();
            scope.add("read");
            scope.add("write");
            return new OAuth2Request(
                    Collections.EMPTY_MAP,
                    "clientId",
                    getGrantedAuthorityCollection(),
                    true,
                    scope,
                    Collections.EMPTY_SET,
                    null,
                    Collections.EMPTY_SET,
                    Collections.EMPTY_MAP);
        }
    
        private Authentication getUserAuthentication() {
            String credentials = "PROTECTED";
            Authentication authentication = new TestingAuthenticationToken(getPrincipalMap(), credentials, getGrantedAuthorityAsList());
            return new OAuth2Authentication(getStoredRequest(), authentication);
        }
    
        private Map<String, String> getPrincipalMap() {
            Map<String, String> principalMap = new LinkedHashMap<>();
            principalMap.put("id", "5c49c98d3a0f3a23cd39a720");
            principalMap.put("username", "TestUserName");
            principalMap.put("password", "TestPassword");
            principalMap.put("createdAt", "2018-06-14 10:35:05");
            principalMap.put("userType", "USER");
            principalMap.put("authorities", getGrantedAuthorityCollectionAsMap().toString());
            principalMap.put("accountNonExpired", "true");
            principalMap.put("accountNonLocked", "true");
            principalMap.put("credentialsNonExpired", "true");
            principalMap.put("enabled", "true");
            principalMap.put("uniqueId", "null");
            principalMap.put("uniqueLink", "fc3552f4-0cdf-494d-bc46-9d1e6305400a");
            principalMap.put("uniqueLinkCreatedAt", "2019-09-06 10:44:36");
            principalMap.put("someId", "59b5a82c410df8000a83a1ff");
            principalMap.put("otherId", "59b5a82c410df8000a83a1ff");
            principalMap.put("name", "TestName");
            return principalMap;
        }
    
        private Collection<GrantedAuthority> getGrantedAuthorityCollection() {
            return Arrays.asList(
                    new SimpleGrantedAuthority("ROLE_ADMIN"),
                    new SimpleGrantedAuthority("ROLE_USER")
            );
        }
    
        private List<GrantedAuthority> getGrantedAuthorityAsList() {
            return new ArrayList<>(getGrantedAuthorityCollection());
        }
    
        private LinkedHashMap<String, GrantedAuthority> getGrantedAuthorityCollectionAsMap() {
            LinkedHashMap<String, GrantedAuthority> map = new LinkedHashMap<>();
            for (GrantedAuthority authority : getGrantedAuthorityCollection()) {
                map.put("authority", authority);
            }
            return map;
        }
    }
    

    As result my base class for contract tests is:

    @RunWith(SpringRunner.class)
    @WebMvcTest(controllers = {Controller.class})
    @ContextConfiguration(classes = {TestConfig.class, OAuth2TestConfig.class})
    @ComponentScan(basePackageClasses = {Controller.class})
    @AutoConfigureStubRunner
    @WebAppConfiguration
    public abstract class HousesControllersSecuredBase {
        @Autowired
        private Service service;
        @Autowired
        private WebApplicationContext context;
        @Autowired
        private OAuth2Authentication oAuth2Authentication;
        @Autowired
        private OAuth2AuthenticationDetails oAuth2AuthenticationDetails;
        @Autowired
        private MockMvc mockMvc;
    
        @Before
        public void settingUpTests() {
            RestAssuredMockMvc.standaloneSetup(Controller(houseService));
            mockMvc = MockMvcBuilders
                    .webAppContextSetup(context)
                    .build();
            RestAssuredMockMvc.mockMvc(mockMvc);
            oAuth2Authentication.setDetails(oAuth2AuthenticationDetails);
            RestAssuredMockMvc.authentication = 
                RestAssuredMockMvc.principal(oAuth2Authentication);
        }
    
        @After
        public void ShuttingDownTests() {
            RestAssuredMockMvc.reset();
        }
    }