How do I test main app without spring security? - java

I have a main app that exposes endpoint like this. I have a test like this. It simply uses TestRestTemplate, calls actuator endpoint and checks health:
#ExtendWith(SpringExtension.class)
#SpringBootTest(classes = HealthDataImporterApplication.class, webEnvironment = WebEnvironment.RANDOM_PORT)
#TestPropertySource(locations = "classpath:application-test.properties")
public class HealthDataImporterApplicationTest {
#Autowired
private TestRestTemplate testRestTemplate;
#LocalServerPort
private int serverPort;
#Test
public void health() {
assertThat("status", read(get("health"), "$.status").equals("UP"));
}
private String get(final String resource) {
return testRestTemplate.getForObject(String.format("http://localhost:%s/actuator/%s", serverPort, resource),
String.class);
}
}
I now added SecurityConfig class to add authentication to the app. Here is SecurityConfig class:
#EnableWebSecurity
public class SecurityConfig {
private static final String ACTUATOR_URL = "/actuator/**";
private static final String SCOPE_PREFIX = "SCOPE_";
private static final String PERMISSION_SCOPE = "scope:scope";
#Value("${auth0.audience}")
private String audience;
#Value("${spring.security.oauth2.resourceserver.jwt.issuer-uri}")
private String issuer;
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
/*
This is where we configure the security required for our endpoints and setup our app to serve as
an OAuth2 Resource Server, using JWT validation.
*/
http
.csrf().disable()
.authorizeRequests()
.antMatchers(HttpMethod.GET, ACTUATOR_URL).permitAll()
.anyRequest().hasAuthority(SCOPE_PREFIX + PERMISSION_SCOPE)
.and().cors()
.and().oauth2ResourceServer().jwt();
return http.build();
}
#Bean
JwtDecoder jwtDecoder() {
/*
By default, Spring Security does not validate the "aud" claim of the token, to ensure that this token is
indeed intended for our app. Adding our own validator is easy to do:
*/
NimbusJwtDecoder jwtDecoder = JwtDecoders.fromOidcIssuerLocation(issuer);
jwtDecoder.setJwtValidator(new DelegatingOAuth2TokenValidator<>(JwtValidators.createDefaultWithIssuer(issuer),
new AudienceValidator(audience)));
return jwtDecoder;
}
}
Main app imports this SecurityConfig class so authentication is there. However, it fails the existing test because it complains it is missing auth0.audience and other value. It seems like it is trying to actually call auth0 url, which I don't want the test to. Otherwise, I have to put actual auth0 audience and domain which is sensitive data.
How can I disable spring security with TestRestTemplate? Is it worth to test this class at all?

As you are using spring-boot, do not override the JwtDecoder just to check audiences. Use spring.security.oauth2.resourceserver.jwt.audiences property instead (this is a comma separated list of acceptable audience).
Rather than deactivating security, I prefer to use mocked identities and include access-control in test coverage, using MockMvc (yes, even in #SpringBootTest with many components wired).
Refer to this other answer for details on how to do that in unit-test and integration-test.
Because I find request post-processors not that readable and I happen to unit-test secured components which are not controllers (like #Service or #Respository with method-security like #PreAuthorize, #PostFilter, etc.), I created a lib with test annotations for OAuth2:
#SpringBootTest(webEnvironment = WebEnvironment.MOCK)
#AutoConfigureMockMvc
class ApplicationIntegrationTest {
#Autowired
MockMvc api;
// actuator
#Test
void givenRequestIsAnonymous_whenGetStatus_thenOk() throws Exception {
api.get("/actuator/health")
.andExpect(status().isOk())
.andExpect(jsonPath("$.status").value("UP"));
}
// secured resource
#Test
void givenRequestIsAnonymous_whenGetMachin_thenUnauthorized() throws Exception {
api.perform(get("/api/v1/machin"))
.andExpect(status().isUnauthorized());
}
#Test
#WithMockJwtAuth("SCOPE_openid", "SCOPE_scope:scope")
void givenUserIsGrantedWithExpectedAuthority_whenGetMachin_thenOk() throws Exception {
api.perform(get("/api/v1/machin"))
.andExpect(status().isOk());
}
#Test
#WithMockJwtAuth("SCOPE_openid")
void givenUserIsNotGrantedWithExpectedAuthority_whenGetMachin_thenForbidden() throws Exception {
api.perform(get("/api/v1/machin"))
.andExpect(status().isForbidden());
}
}
As comparison, the same sample with just spring-security-test:
#SpringBootTest(webEnvironment = WebEnvironment.MOCK)
#AutoConfigureMockMvc
class ApplicationIntegrationTest {
#Autowired
MockMvc api;
// actuator
#Test
void givenRequestIsAnonymous_whenGetStatus_thenOk() throws Exception {
api.perform(get("/actuator/health/liveness"))
.andExpect(status().isOk());
}
// secured resource
#Test
void givenRequestIsAnonymous_whenGetMachin_thenUnauthorized() throws Exception {
api.perform(get("/api/v1/machin"))
.andExpect(status().isUnauthorized());
}
#Test
void givenUserIsGrantedWithExpectedAuthority_whenGetMachin_thenOk() throws Exception {
api.perform(get("/api/v1/machin")
.with(jwt().jwt(jwt -> jwt.authorities(List.of(
new SimpleGrantedAuthority("SCOPE_openid"),
new SimpleGrantedAuthority("SCOPE_scope:scope"))))))
.andExpect(status().isOk());
}
#Test
void givenUserIsNotGrantedWithExpectedAuthority_whenGetMachin_thenForbidden() throws Exception {
api.perform(get("/api/v1/machin")
.with(jwt().jwt(jwt -> jwt.authorities(List.of(
new SimpleGrantedAuthority("SCOPE_openid"))))))
.andExpect(status().isForbidden());
}
}

In JHipster, we generate a TestSecurityConfiguration class that mocks the JwtDecoder bean so no identity provider is needed. You can see the code here. I've copied it below for your convenience.
package com.auth0.flickr2.config;
import static org.mockito.Mockito.mock;
import java.util.HashMap;
import java.util.Map;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;
import org.springframework.security.oauth2.client.InMemoryOAuth2AuthorizedClientService;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.jwt.JwtDecoder;
/**
* This class allows you to run unit and integration tests without an IdP.
*/
#TestConfiguration
#Import(OAuth2Configuration.class)
public class TestSecurityConfiguration {
#Bean
ClientRegistration clientRegistration() {
return clientRegistrationBuilder().build();
}
#Bean
ClientRegistrationRepository clientRegistrationRepository(ClientRegistration clientRegistration) {
return new InMemoryClientRegistrationRepository(clientRegistration);
}
private ClientRegistration.Builder clientRegistrationBuilder() {
Map<String, Object> metadata = new HashMap<>();
metadata.put("end_session_endpoint", "https://jhipster.org/logout");
return ClientRegistration
.withRegistrationId("oidc")
.issuerUri("{baseUrl}")
.redirectUri("{baseUrl}/{action}/oauth2/code/{registrationId}")
.clientAuthenticationMethod(ClientAuthenticationMethod.BASIC)
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.scope("read:user")
.authorizationUri("https://jhipster.org/login/oauth/authorize")
.tokenUri("https://jhipster.org/login/oauth/access_token")
.jwkSetUri("https://jhipster.org/oauth/jwk")
.userInfoUri("https://api.jhipster.org/user")
.providerConfigurationMetadata(metadata)
.userNameAttributeName("id")
.clientName("Client Name")
.clientId("client-id")
.clientSecret("client-secret");
}
#Bean
JwtDecoder jwtDecoder() {
return mock(JwtDecoder.class);
}
#Bean
OAuth2AuthorizedClientService authorizedClientService(ClientRegistrationRepository clientRegistrationRepository) {
return new InMemoryOAuth2AuthorizedClientService(clientRegistrationRepository);
}
}
This class is then referenced in the test and overrides the SecurityConfig.
#SpringBootTest(classes = { Flickr2App.class, TestSecurityConfiguration.class })
The full project repo is at https://github.com/oktadev/auth0-full-stack-java-example.

Related

Cannot mock #PreAuthorize when using with OAuth2 in Spring Boot

I am trying to write a Unit tests for all of my service classes, but I cannot find a solution on how to mock a #PreAuthorize above my controller methods. As an example:
I have this function in controller:
#GetMapping("/users")
#PreAuthorize("hasAuthority('ADMIN')")
public ResponseEntity<List<User>> getUsers() {
return service.getUsers();
}
And this in my service class:
public ResponseEntity<List<User>> getUsers() {
return new ResponseEntity<>(userRepository.findAll(), HttpStatus.OK);
}
WebSecurity class:
protected void configure(HttpSecurity http) throws Exception {
http = http.cors().and().csrf().disable();
http = http
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and();
http = http
.exceptionHandling()
.authenticationEntryPoint(
(request, response, ex) -> response.sendError(
HttpServletResponse.SC_UNAUTHORIZED,
ex.getMessage()
)
)
.and();
http.authorizeRequests()
.antMatchers(HttpMethod.GET, "/").permitAll()
.anyRequest().authenticated();
http.oauth2ResourceServer().jwt().jwtAuthenticationConverter(jwtAuthenticationConverter());
}
public JwtAuthenticationConverter jwtAuthenticationConverter() {
JwtAuthenticationConverter converter = new JwtAuthenticationConverter();
converter.setJwtGrantedAuthoritiesConverter(jwt ->
Optional.ofNullable(jwt.getClaimAsStringList("permissions"))
.stream()
.flatMap(Collection::stream)
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList())
);
return converter;
}
Now I am trying to write a unit test:
#SpringBootTest
#AutoConfigureMockMvc
public class UserControllerTests {
#Autowired
private MockMvc mvc;
#MockBean
private UserService userService;
#Test
#WithMockJwtAuth(claims = #OpenIdClaims(otherClaims
= #Claims(stringClaims = #StringClaim(name = "permissions", value = "{ADMIN}"))))
public void getAllUsers_shouldBeSuccess() throws Exception {
ArrayList<User> users = new ArrayList<>();
users.add(new User("0", true, new Role("USER")));
when(userService.getUsers()).thenReturn(new ResponseEntity<>(users, HttpStatus.OK));
mvc.perform(get("/users"))
.andExpect(status().isOk());
}
}
But I receive an error on mvc.perform call:
java.lang.NoClassDefFoundError: `org/springframework/security/web/context/SecurityContextHolderFilter`
UPDATE:
I've tried https://github.com/ch4mpy/spring-addons and added #WithMockBearerTokenAuthentication, but I still receive the same error. Also to note: if I removed all # and left only with #Test above the method, I receive 401 error.
#Test
#WithMockBearerTokenAuthentication(attributes = #OpenIdClaims(otherClaims
= #Claims(stringClaims = #StringClaim(name = "permissions", value = "{ADMIN}"))))
public void getAllUsers_shouldBeSuccess() throws Exception {
ArrayList<User> users = new ArrayList<>();
users.add(new User("0", true, new Role("USER")));
when(userService.getUsers()).thenReturn(new ResponseEntity<>(users, HttpStatus.OK));
mvc.perform(get("/users"))
.andExpect(status().isOk());
}
Sadly, spring-security team chose to include in test framework MockMvc request post-processors and WebTestClient request mutators only, which limits OAuth2 authentication mocking to controllers unit-tests.
Hopefully, I kept my work on test annotations in a set of libs I publish on maven-central: https://github.com/ch4mpy/spring-addons. You can test any #Component with it (sample adapted from here):
//---------------------------------------------------//
// Test secured #Component which isn't a #Controller //
//---------------------------------------------------//
// Import web-security configuration and tested component class
#Import({ SampleApi.WebSecurityConfig.class, MessageService.class })
#ExtendWith(SpringExtension.class)
class MessageServiceTests {
// Auto-wire tested component
#Autowired
private MessageService messageService;
// Mock tested component dependencies
#MockBean
GreetingRepo greetingRepo;
#BeforeEach
public void setUp() {
when(greetingRepo.findUserSecret("ch4mpy")).thenReturn("Don't tel it");
}
#Test()
void greetWitoutAuthentication() {
// Call tested component methods directly (don't use MockMvc nor WebTestClient)
assertThrows(Exception.class, () -> messageService.getSecret("ch4mpy"));
}
#Test
#WithMockJwtAuth(authorities = "ROLE_AUTHORIZED_PERSONNEL", claims = #OpenIdClaims(preferredUsername = "ch4mpy"))
void greetWithMockJwtAuth() {
assertThat(messageService.getSecret("ch4mpy")).isEqualTo("Don't tel it");
}
}
Edit for updated question
Only #Controller unit-tests should run within the context of an HTTP request.
This means that #WebMvcTest (or #WebFluxTest) and MockMvc (or WebTestClient) must be used in #Controller tests only.
Unit-tests for any other type of #Component (#Service or #Repository for instance) should be written without the context of a request. This means that none of #WebMvcTest, #WebFluxTest, MockMvcand WebTestClient should be used in such tests.
The sample above shows how to structure such tests:
Trigger spring-context configuration
#Import web-security configuration and your #Component class
provide #MockBean for all of your #Component dependencies
in the test, call tested component methods directly (do not try to create a mockMvc request)
Edit for the 2nd question modification
I am trying to write a Unit tests for all of my service classes
Apparently, this first statement is not your main concern anymore as you're trying to write integration-tests for #Controller along with the #Services, #Repositories and other #Components it is injected. This is actually a completely different question than unit-testing each of those separately (mocking others).
NoClassDefFoundError on SecurityContextHolderFilter means that spring-security-web is not on your classpath. It should be, even during the tests. Check your dependencies (pom or gradle file that you did not include in your question)
Please also note that:
you might want to write value = "ADMIN" instead of value = "{ADMIN}" (unless you really want curly-braces in your authority name)
you can use just #WithMockJwtAuth("ADMIN") instead of #WithMockJwtAuth(claims = #OpenIdClaims(otherClaims = #Claims(stringClaims = #StringClaim(name = "permissions", value = "ADMIN"))))
you are allowed to read docs (you and I would save quite some time). This includes Spring doc and mine: home one and more importantly tutorials

Spring Boot: Integration test with HttpSecurity

I have the following controller:
#CrossOrigin
#RestController
#RequestMapping("api")
public class MyController {
#GetMapping("/principal")
public void principalEndpoint(Principal user) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
System.out.println(user);
System.out.println(authentication);
}
}
and the corresponding integration test whcich uses #WithMockUser as described in the docs:
/**
* implementation according to https://docs.spring.io/spring-security/reference/servlet/test/index.html
*/
#ExtendWith(SpringExtension.class)
#SpringBootTest
#WebAppConfiguration
#ContextConfiguration
public class MyControllerIT {
#Autowired
private WebApplicationContext context;
private MockMvc mvc;
#BeforeEach
public void setup() {
mvc = MockMvcBuilders
.webAppContextSetup(context)
.apply(springSecurity())
.build();
}
#Test
#WithMockUser(value = "Dani", username = "TAATIDA3")
public void testWithPrincipal() throws Exception {
mvc.perform(get("/api/principal").principal(new PrincipalImpl()))
.andExpect(status().isOk());
}
}
PrincipalImpl is a simple implementation of Principal:
public class PrincipalImpl implements Principal {
#Override
public String getName() {
return "MOCKUSER";
}
}
I also have the following SpringBoot configuration to authorize requests under the /api path:
#Configuration
#EnableResourceServer
#EnableCaching
#EnableScheduling
#EnableMongoAuditing
public class MyApiConfig extends ResourceServerConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
http.antMatcher("/api/**").authorizeRequests()
.antMatchers("/api/**").not().anonymous();
}
}
My problem is that the request dispatched by mockMvc in MyControllerIT fails because a HTTP Status 401 is returned (not authorized). It would work if I change the HttpSecurity configuration to this
http.antMatcher("/api/**").authorizeRequests()
.antMatchers("/api/**").permitAll();
then the request succeeds (HTTP status 200), but no Principal is injected and the Authentication object from SecurityContextHolder.getContext().getAuthentication() is from an anonymous user:
null
AnonymousAuthenticationToken [Principal=anonymousUser, Credentials=[PROTECTED], Authenticated=true, Details=WebAuthenticationDetails [RemoteIpAddress=127.0.0.1, SessionId=null], Granted Authorities=[ROLE_ANONYMOUS]]
If I change the paths in MyApiConfig e.g. to this:
http.antMatcher("/someOtherApi/**").authorizeRequests()
.antMatchers("/someOtherApi/**").permitAll();
then the call from MyControllerIT succeeds and also a Principal is injected, which is what I want. However, in this case the actual api under /api/** is not secured anymore...
I'm quite new to the concepts of Spring Boot Security. Somehow I would have to override the MyApiConfig to configure HttpSecurity differently for tests (or use a separate configuration for test while at the same time excluding MyApiConfig). How do I do that, or what's the best way to make the HttpSecurity setup not interfere with MockMvc setup?

How to avoid using an interceptor in Spring boot integration tests

I have an issue while testing REST requests. On my application I have an interceptor that checks for token validity before allowing the requests. However for my integration tests I would like to bypass the check. In other words I'd like either to shunt the interceptor or to mock it to always return true.
Here is my simplified code:
#Component
public class RequestInterceptor implements HandlerInterceptor {
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
String token = request.getHeader("Authorization");
if (token != null) {
return true;
} else {
return false;
}
}
}
#Configuration
public class RequestInterceptorAppConfig implements WebMvcConfigurer {
#Autowired
RequestInterceptor requestInterceptor;
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(requestInterceptor).addPathPatterns("/**");
}
}
and the tests:
#SpringBootTest(classes = AppjhipsterApp.class)
#AutoConfigureMockMvc
#WithMockUser
public class DocumentResourceIT {
#Autowired
private DocumentRepository documentRepository;
#Autowired
private MockMvc restDocumentMockMvc;
private Document document;
public static Document createEntity() {
Document document = new Document()
.nom(DEFAULT_NOM)
.emplacement(DEFAULT_EMPLACEMENT)
.typeDocument(DEFAULT_TYPE_DOCUMENT);
return document;
}
#BeforeEach
public void initTest() {
document = createEntity();
}
#Test
#Transactional
public void createDocument() throws Exception {
int databaseSizeBeforeCreate = documentRepository.findAll().size();
// Create the Document
restDocumentMockMvc.perform(post("/api/documents")
.contentType(MediaType.APPLICATION_JSON)
.content(TestUtil.convertObjectToJsonBytes(document)))
.andExpect(status().isCreated());
}
}
When running the tests it always go through the interceptor and gets rejected since I have no valid token. My code here is simplified, I can not get a valid token for testing and so I really need to skip the interceptor.
Thanks for your help
To mock it (in an integration test):
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
// non-static imports
#SpringBootTest
// other stuff
class IntegrationTest {
#MockBean
RequestInterceptor interceptor;
// other stuff
#BeforeEach
void initTest() {
when(interceptor.preHandle(any(), any(), any())).thenReturn(true);
// other stuff
}
// tests
}
What #BeforeEach and #SpringBootTest do, you know; Mockito's any() just says "regardless of argument"; for #MockBean and Mockito's when-then, the Javadoc is good enough that I feel no need to add information.
I would solve this by using a profile on the interceptor. In your test you dont run with the profile (bean is not injected). In your production or which ever env you need it you run with the new profile.
of course you need to change the usage a bit. This should work:
#Configuration
public class RequestInterceptorAppConfig implements WebMvcConfigurer {
#Autowired
Collection<RequestInterceptor> requestInterceptors;
#Override
public void addInterceptors(InterceptorRegistry registry) {
requestInterceptors.forEach(interceptor -> registry.addInterceptor(interceptor).addPathPatterns("/**");
}
}

Unit testing Spring-WS SOAP endpoint fails in MockFilterChain returning 404

I have used Spring-WS to write a SOAP endpoint, and am now in the process of writing some tests. However, when I try to send my request, I am getting a 404 occurring in doFilter() of the MockFilterChain class of Spring and I can't figure out why. Is there something else I need to mock?
My endpoint is a SOAP simulator, which pulls in the WSDL-generated source files it uses from another package in my project to avoid unnecessary duplication. So I do not have direct access to the WSDL files as a classpath resource.
My project looks like this:
Endpoint
import org.springframework.ws.server.endpoint.annotation.Endpoint;
import org.springframework.ws.server.endpoint.annotation.PayloadRoot;
import org.springframework.ws.server.endpoint.annotation.RequestPayload;
import org.springframework.ws.server.endpoint.annotation.ResponsePayload;
#Endpoint
public class MockSoapEndpoint {
private final ObjectFactory objectFactory = new ObjectFactory();
...
...
#PayloadRoot(namespace = NAMESPACE, localPart = "soapEndpoint")
#ResponsePayload
public JAXBElement<MySoapResponse> chargeVolume(#RequestPayload JAXBElement<MySoapRequest> request) {
...
...
}
Config
#EnableWs
#Configuration
public class MockSoapConfig extends WsConfigurerAdapter {
#Bean
public ServletRegistrationBean messageDispatcherServlet(final ApplicationContext applicationContext) {
MessageDispatcherServlet messageDispatcherServlet = new MessageDispatcherServlet();
messageDispatcherServlet.setApplicationContext(applicationContext);
messageDispatcherServlet.setTransformWsdlLocations(true);
return new ServletRegistrationBean(messageDispatcherServlet, "/ws/*");
}
}
Test
#WebMvcTest
#ContextConfiguration(classes = { MockSoapConfig.class, MockSoapEndpoint.class })
#ExtendWith(SpringExtension.class)
class MockSoapEndpointTest {
private static final String SOAP_REQUEST = ""
+ "<soapenv:Envelope xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\"\n"
+ "<soapenv:Header/>..."</soapenv:Envelope>\n";
#Autowired
private MockMvc mockMvc;
#Test
void givenNonRejectedMdnReturnChargingInformationWithCorrectMinMajCodes() throws Exception {
final String endpoint = "http://localhost:8080/ws";
mockMvc.perform(post(endpoint).contentType(MediaType.TEXT_XML)
.content(SOAP_REQUEST)
.accept(MediaType.TEXT_XML))
.andExpect(status().isOk());
}
}
This test returns the 404 response code, rather than the 200 I'm expecting. When I manually test the endpoint, using http://localhost:8080/ws and the same XML chunk, it works fine.
You should use MockWebServiceClient instead of MockMvc.
As it was mentioned here, MockMvc scans only #RestController/#Controller and omits #Endpoint, so your mapping /ws/* doesn't exist and you've got no proper MessageDispatcher.
Your test is going to look like:
import org.springframework.xml.transform.StringSource;
import org.springframework.ws.test.server.MockWebServiceClient;
import static org.springframework.ws.test.server.RequestCreators.*;
import static org.springframework.ws.test.server.ResponseMatchers.*;
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration("applicationContext.xml")
public class MyWebServiceIntegrationTest {
// a standard MessageDispatcherServlet application context, containing endpoints, mappings, etc.
#Autowired
private ApplicationContext applicationContext;
private MockWebServiceClient mockClient;
#Before
public void createClient() throws Exception {
mockClient = MockWebServiceClient.createClient(applicationContext);
}
// test the CustomerCountEndpoint, which is wired up in the application context above
// and handles <customerCount/> messages
#Test
public void customerCountEndpoint() throws Exception {
Source requestPayload = new StringSource(
"<customerCountRequest xmlns='http://springframework.org/spring-ws'>" +
"<customerName>John Doe</customerName>" +
"</customerCountRequest>");
Source expectedResponsePayload = new StringSource(
"<customerCountResponse xmlns='http://springframework.org/spring-ws'>" +
"<customerCount>42</customerCount>" +
"</customerCountResponse>");
mockClient.sendRequest(withPayload(requestPayload)).andExpect(payload(expectedResponsePayload));
}
}

Use #WithMockUser (with #SpringBootTest) inside an oAuth2 Resource Server Application

Environment:
I have a spring boot based microservice architecture application consisting of multiple infrastructural services and resource services (containing the business logic). Authorization and authentication is handled by an oAuth2-Service managing the user entities and creating JWT tokens for the clients.
To test a single microservice application in its entirety i tried to build tests with testNG, spring.boot.test, org.springframework.security.test ...
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK, properties = {"spring.cloud.discovery.enabled=false", "spring.cloud.config.enabled=false", "spring.profiles.active=test"})
#AutoConfigureMockMvc
#Test
public class ArtistControllerTest extends AbstractTestNGSpringContextTests {
#Autowired
private MockMvc mvc;
#BeforeClass
#Transactional
public void setUp() {
// nothing to do
}
#AfterClass
#Transactional
public void tearDown() {
// nothing to do here
}
#Test
#WithMockUser(authorities = {"READ", "WRITE"})
public void getAllTest() throws Exception {
// EXPECT HTTP STATUS 200
// BUT GET 401
this.mvc.perform(get("/")
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
}
}
where the security (resource server) config is the following
#Configuration
#EnableResourceServer
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
// get the configured token store
#Autowired
TokenStore tokenStore;
// get the configured token converter
#Autowired
JwtAccessTokenConverter tokenConverter;
/**
* !!! configuration of springs http security !!!
*/
#Override
public void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/**").authenticated();
}
/**
* configuration of springs resource server security
*/
#Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
// set the configured tokenStore to this resourceServer
resources.resourceId("artist").tokenStore(tokenStore);
}
}
and the following method based security check annotated inside the controller class
#PreAuthorize("hasAuthority('READ')")
#RequestMapping(value = "/", method = RequestMethod.GET)
public List<Foo> getAll(Principal user) {
List<Foo> foos = fooRepository.findAll();
return foos;
}
I thought that would work but when running the test i only get an assertion error
java.lang.AssertionError: Status
Expected :200
Actual :401
Question:
Is there something totally obvious that i am doing wrong? Or is #WithMockUser not going to work with #SpringBootTest and #AutoConfigureMockMvc in an oAuth2 environment? If this is the case... what would be the best approach for testing route and method based security configurations as part of such an (integration) test like this one?
Appendix:
I also tried different approaches like something like the following... but it led to the same result :(
this.mvc.perform(get("/")
.with(user("admin").roles("READ","WRITE").authorities(() -> "READ", () -> "WRITE"))
.accept(MediaType.APPLICATION_JSON))
see:
spring security testing
spring boot 1.4 testing
#WithMockUser creates the authentication in SecurityContext.
Same applies for with(user("username")).
By default the OAuth2AuthenticationProcessingFilter does not use the SecurityContext, but always build the authentication from the token ("stateless").
You can easily change this behavior be setting the stateless flag in the resource server security configuration to false:
#Configuration
#EnableResourceServer
public class ResourceServerConfiguration implements ResourceServerConfigurer {
#Override
public void configure(ResourceServerSecurityConfigurer security) throws Exception {
security.stateless(false);
}
#Override
public void configure(HttpSecurity http) {}
}
Another option is to extend ResourceServerConfigurerAdapter, but the problem with that is that it comes with configuration that forces all requests to be authenticated. Implementing the interface leaves your main security config unchanged apart from the statelessness.
Of course, set the flag to to false in your test contexts, only.
I had de same issue, and the only way I found was creating a token and using it in the mockMvc perform
mockMvc.perform(get("/resource")
.with(oAuthHelper.bearerToken("test"))
And the OAuthHelper:
#Component
#EnableAuthorizationServer
public class OAuthHelper extends AuthorizationServerConfigurerAdapter {
#Autowired
AuthorizationServerTokenServices tokenservice;
#Autowired
ClientDetailsService clientDetailsService;
public RequestPostProcessor bearerToken(final String clientid) {
return mockRequest -> {
OAuth2AccessToken token = createAccessToken(clientid);
mockRequest.addHeader("Authorization", "Bearer " + token.getValue());
return mockRequest;
};
}
OAuth2AccessToken createAccessToken(final String clientId) {
ClientDetails client = clientDetailsService.loadClientByClientId(clientId);
Collection<GrantedAuthority> authorities = client.getAuthorities();
Set<String> resourceIds = client.getResourceIds();
Set<String> scopes = client.getScope();
Map<String, String> requestParameters = Collections.emptyMap();
boolean approved = true;
String redirectUrl = null;
Set<String> responseTypes = Collections.emptySet();
Map<String, Serializable> extensionProperties = Collections.emptyMap();
OAuth2Request oAuth2Request = new OAuth2Request(requestParameters, clientId, authorities,
approved, scopes, resourceIds, redirectUrl, responseTypes, extensionProperties);
User userPrincipal = new User("user", "", true, true, true, true, authorities);
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(userPrincipal, null, authorities);
OAuth2Authentication auth = new OAuth2Authentication(oAuth2Request, authenticationToken);
return tokenservice.createAccessToken(auth);
}
#Override
public void configure(final ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("test")
.authorities("READ");
}
}
As I was specifically trying to write tests against our ResourceServerConfiguration, I worked around the issue by creating a test wrapper for it which set security.stateless to false:
#Configuration
#EnableResourceServer
public class ResourceServerTestConfiguration extends ResourceServerConfigurerAdapter {
private ResourceServerConfiguration configuration;
public ResourceServerTestConfiguration(ResourceServerConfiguration configuration) {
this.configuration = configuration;
}
#Override
public void configure(ResourceServerSecurityConfigurer security) throws Exception {
configuration.configure(security);
security.stateless(false);
}
#Override
public void configure(HttpSecurity http) throws Exception {
configuration.configure(http);
}
}

Categories