Testing a Feign Client - spring-cloud-feign

I've written a feign client and I would like to test that it works using a unit test.
For my case, integration tests is not the right approach for the current development stage.
The feign client is null, I receive a NullPointerException while running the test.
How can I autowire it?
Feign client
package com.myapp.clients;
import com.myapp.model.StatusResponse;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
#FeignClient(name="myClient", url="${feign.clients.my.url}")
public interface myClient {
#RequestMapping(method= RequestMethod.GET, value="/v1/users/{userId}")
StatusResponse getStatus(
#RequestHeader(value = "Auth", required = true) String authorizationHeader,
#RequestHeader(value = "my_tid", required = true) String tid,
#PathVariable("userId") String userId);
}
Tests:
package com.myapp.clients;
import com.intuit.secfraudshared.step.broker.model.StatusResponse;
import org.junit.Assert;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
public class MyClientTest {
#Autowired
MyClient myClient;
#Test
public void testmyClient_status200() {
StatusResponse myResponse = myClient.getStatus("", "tidTestSample", "4626745161770145");
Assert.assertNotNull(iusResponse);
}
}
How can autowire MyClient?

The method that has worked for me so far while trying to test Feign Clients is stubbing the response via wiremock. You would need to add dependency for wiremock.
testImplementation 'org.springframework.cloud:spring-cloud-contract-wiremock'
Then you would need to annotate as
#RunWith(SpringRunner.class)
#SpringBootTest(properties = "feign.clients.my.url=http://localhost:${wiremock.server.port}")
#AutoConfigureWireMock(port = 0)
And then stub using wiremock.
stubFor(post(urlPathMatching("/v1/users/([a-zA-Z0-9-]*)")).willReturn(aResponse().withStatus(200).withHeader("content-type", "application/json").withBody("{\"code\":200,\"status\":\"success\"})));
where ([a-zA-Z0-9-]*) is regex for {userId} assuming it is alphanumeric.
And then, of course, assert.
StatusResponse myResponse = myClient.getStatus("", "tidTestSample", "4626745161770145");
Assert.assertNotNull(myResponse);

Related

RestAssured LogConfig.blacklistedHeaders error

I am getting below error when run serenity-cucumber6 test with rest-assured.
Step failed
java.lang.NoSuchMethodError: io.restassured.config.LogConfig.blacklistedHeaders()Ljava/util/Set;
at net.serenitybdd.rest.utils.RestReportingHelper.registerCall(RestReportingHelper.java:69)
at net.serenitybdd.rest.decorators.request.RequestSpecificationDecorated.reportQuery(RequestSpecificationDecorated.java:292)
at net.serenitybdd.rest.decorators.request.RequestSpecificationDecorated.execute(RequestSpecificationDecorated.java:284)
at net.serenitybdd.rest.decorators.request.RequestSpecificationDecorated.get(RequestSpecificationDecorated.java:67)
at net.serenitybdd.rest.decorators.request.RequestSpecificationDecorated.get(RequestSpecificationDecorated.java:38)
The versions I am using are:
<spring-boot.version>2.4.5</spring-boot.version>
<serenity.plugin.version>2.4.34</serenity.plugin.version>
<serenity.version>2.4.34</serenity.version>
<serenity.cucumber.version>2.4.34</serenity.cucumber.version>
<cucumber.version>6.10.4</cucumber.version>
<java.version>11</java.version>
The test main class
import org.junit.runner.RunWith;
import io.cucumber.junit.CucumberOptions;
import net.serenitybdd.cucumber.CucumberWithSerenity;
#RunWith(CucumberWithSerenity.class)
#CucumberOptions(
plugin = {"pretty", "html:target/reports/cucumber-html-report",
"html:target/cucumber-reports/cucumber-pretty",
"json:target/cucumber.json"},
tags = "not #skip",
glue = {"my.stepdefinitions"},
features = "src/it/resources/features/")
public class MyServiceCucumberTests {
}
Request call method
public static Response request(Function<RequestSpecification, Response> method, RequestSpecification spec) {
RequestSpecification call = rest()
.spec(spec)
.when();
return method.apply(call)
.then()
.extract().response();
}

Bad state: Mock method was not called within `when()`. Was a real method called?

I'm trying to make a mock of an httpRequest in flutter using mockito.
Here I define a global http client:
library utgard.globals;
import 'package:http/http.dart' as http;
http.Client httpClient = http.Client();
Then I replace in integration testing:
import 'package:flutter_driver/driver_extension.dart';
import 'package:http/http.dart' as http;
import 'package:utgard/globals.dart' as globals;
import 'package:mockito/mockito.dart';
import 'package:utgard/main.dart' as app;
class MockClient extends Mock implements http.Client {}
void main() {
final MockClient client = MockClient();
globals.httpClient = client;
enableFlutterDriverExtension();
app.main();
}
Then I try to use when of mockito:
test('login with correct password', () async {
final client = MockClient();
when(globals.httpClient.post('http://www.google.com'))
.thenAnswer((_) async => http.Response('{"title": "Test"}', 200));
await driver.enterText('000000');
await driver.tap(loginContinuePasswordButton);
});
But I receive the following error:
Bad state: Mock method was not called within when(). Was a real method called?
This issue may happen when you implement a method you want to mock instead of letting Mockito do that.
This code below will return Bad state: Mock method was not called within when(). Was a real method called?:
class MockFirebaseAuth extends Mock implements FirebaseAuth {
FirebaseUser _currentUser;
MockFirebaseAuth(this._currentUser);
// This method causes the issue.
Future<FirebaseUser> currentUser() async {
return _currentUser;
}
}
final user = MockFirebaseUser();
final mockFirebaseAuth = MockFirebaseAuth(user);
// Will throw `Bad state: Mock method was not called within `when()`. Was a real method called?`
when(mockFirebaseAuth.currentUser())
.thenAnswer((_) => Future.value(user));
What do you want instead is:
class MockFirebaseAuth extends Mock implements FirebaseAuth {}
final user = MockFirebaseUser();
final mockFirebaseAuth = MockFirebaseAuth();
// Will work as expected
when(mockFirebaseAuth.currentUser())
.thenAnswer((_) => Future.value(user));
Also this issue happens when you try to call when() on a non-mock sublass:
class MyClass {
String doSomething() {
return 'test';
}
}
final myClassInstance = MyClass();
// Will throw `Bad state: Mock method was not called within `when()`. Was a real method called?`
when(myClassInstance.doSomething())
.thenReturn((_) => 'mockedValue');
There is one more possibility. If this happens to your mock object, then probably it may be outdated. In this case, try regenerating your mock objects by using
flutter pub run build_runner build --delete-conflicting-outputs
please declare resetMocktailState() in teardown or at the end of setup, so the test cases should not affect later test cases.
The solution I found was to define the mock in test_driver/app.dart and call the runApp function after that, this way you can apply the mock even with flutter integration testing:
import 'package:flutter/widgets.dart';
import 'package:flutter_driver/driver_extension.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:utgard/business/config/globals.dart';
import 'package:utgard/main.dart' as app;
class MockClient extends Mock implements http.Client {}
void main() {
enableFlutterDriverExtension();
final MockClient client = MockClient();
// make your mocks here
httpClient = client;
runApp(app.MyApp());
}
Since it can become a huge code to mock all the requests there you can make a separate function in order to better organize the code:
import 'package:flutter/widgets.dart';
import 'package:flutter_driver/driver_extension.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:utgard/business/config/globals.dart';
import 'package:utgard/main.dart' as app;
class MockClient extends Mock implements http.Client {}
void main() {
enableFlutterDriverExtension();
final MockClient client = MockClient();
makeMock();
httpClient = client;
runApp(app.MyApp());
}

How to enable SecurityDefinitions in "../v2/api-docs" json generated file

I want to use swagger client generator and feed the json generated by "../v2/api-docs" from the jHipster application. The problem is that without the security definitions the generated code will not work. The JWT token is not added to the API requests, the code is generated without authentication. The http://petstore.swagger.io/v2/swagger.json example has security and securityDefinitions. Where to modify/configure the jhipster application so that the security and security definitions are generated in the json file? {I manually added the security and security definitions to the json file and after that the generated code works and JWT is enabled in the jHipster application, but I don't want to edit the file each time the API changes... } The "securityDefinitions" and "security":[{"petstore_auth":["write:pets","read:pets"]}] sections are completely missing from the generated json file from the jHipster application, even if JWT is enabled and needed to make API requests.
Update 28-09-2020:
Since the update to SpringFox 3, classes are now called
SpringfoxCustomizer
JHipsteSpringfoxCustomizer
Better late than never.
JHipster applications depend on the JHipster Framework, which is in charge of the springfox's Docket configuration.
JHipster Framework's SwaggerAutoConfiguration customizes the springfox Docket with every SwaggerCustomizer bean registered in the application. JHipster registers it's own swagger customizer for the default docket configuration.
This said, you need to add your own docket customizer ir order to include the desired security definitions and any other additional configuration to the springfox's docket. In order to do this you need to:
Create the swagger pacakage inside the already existing config package. Inside it, create a CustomSwaggerConfig class:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
#Configuration
public class CustomSwaggerConfig {
public CustomSwaggerConfig() {
}
#Bean
public ApplicationSwaggerCustomizer applicationSwaggerCustomizer() {
return new ApplicationSwaggerCustomizer();
}
}
And create the ApplicationSwaggerCustomizer class:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.github.jhipster.config.apidoc.customizer.SwaggerCustomizer;
import springfox.documentation.spring.web.plugins.Docket;
public class ApplicationSwaggerCustomizer implements SwaggerCustomizer {
private final Logger log = LoggerFactory.getLogger(ApplicationSwaggerCustomizer.class);
public ApplicationSwaggerCustomizer() {
}
#Override
public void customize(Docket docket) {
log.debug("Customizing springfox docket...");
// TODO Here you can add all the configurations to the docket
}
}
Now you can add any additional docket configuration.
You can clone default implementation with:
package <YOUR_PACKAGE>;
import static io.github.jhipster.config.JHipsterConstants.SPRING_PROFILE_SWAGGER;
import static springfox.documentation.builders.PathSelectors.regex;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.data.domain.Pageable;
import org.springframework.http.ResponseEntity;
import org.springframework.util.StopWatch;
import org.springframework.util.StringUtils;
import io.github.jhipster.config.JHipsterProperties;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.ApiKey;
import springfox.documentation.service.AuthorizationScope;
import springfox.documentation.service.Contact;
import springfox.documentation.service.SecurityReference;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.contexts.SecurityContext;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger.web.ApiKeyVehicle;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
/**
* Springfox Swagger configuration.
* <p>
* Warning! When having a lot of REST endpoints, Springfox can become a performance issue.
* In that case, you can use the "no-swagger" Spring profile, so that this bean is ignored.
*/
#Configuration
#Profile(SPRING_PROFILE_SWAGGER)
#EnableSwagger2
public class SwaggerConfiguration {
static final String STARTING_MESSAGE = "Starting Swagger with JWT";
static final String STARTED_MESSAGE = "Started Swagger with JWT in {} ms";
static final String MANAGEMENT_TITLE_SUFFIX = "Management API";
static final String MANAGEMENT_GROUP_NAME = "management";
static final String MANAGEMENT_DESCRIPTION = "Management endpoints documentation";
public static final String AUTHORIZATION_HEADER = "Authorization";
private final Logger log = LoggerFactory.getLogger(SwaggerConfiguration.class);
private final JHipsterProperties.Swagger properties;
public SwaggerConfiguration(JHipsterProperties jHipsterProperties) {
this.properties = jHipsterProperties.getSwagger();
}
/**
* Springfox configuration for the API Swagger with JWT docs.
*
* #return the Swagger Springfox configuration
*/
#Bean
public Docket swaggerSpringfoxApiDocket() {
log.debug(STARTING_MESSAGE);
StopWatch watch = new StopWatch();
watch.start();
Docket docket = createDocket();
Contact contact = new Contact(
properties.getContactName(),
properties.getContactUrl(),
properties.getContactEmail()
);
ApiInfo apiInfo = new ApiInfo(
properties.getTitle(),
properties.getDescription(),
properties.getVersion(),
properties.getTermsOfServiceUrl(),
contact,
properties.getLicense(),
properties.getLicenseUrl(),
new ArrayList<>()
);
docket.host(properties.getHost())
.protocols(new HashSet<>(Arrays.asList(properties.getProtocols())))
.securitySchemes(Arrays.asList((apiKey())))
.securityContexts(Arrays.asList(
SecurityContext.builder()
.securityReferences(
Arrays.asList(SecurityReference.builder()
.reference("JWT")
.scopes(new AuthorizationScope[0])
.build()
)
)
.build())
)
.apiInfo(apiInfo)
.useDefaultResponseMessages(properties.isUseDefaultResponseMessages())
.forCodeGeneration(true)
.directModelSubstitute(ByteBuffer.class, String.class)
.genericModelSubstitutes(ResponseEntity.class)
.ignoredParameterTypes(Pageable.class)
.select()
.paths(regex(properties.getDefaultIncludePattern()))
.build();
watch.stop();
log.debug(STARTED_MESSAGE, watch.getTotalTimeMillis());
return docket;
}
/**
* Springfox configuration for the management endpoints (actuator) Swagger docs.
*
* #param appName the application name
* #param managementContextPath the path to access management endpoints
* #return the Swagger Springfox configuration
*/
#Bean
#ConditionalOnMissingBean(name = "swaggerSpringfoxManagementDocket")
public Docket swaggerSpringfoxManagementDocket(#Value("${spring.application.name:application}") String appName,
#Value("${management.endpoints.web.base-path}") String managementContextPath) {
ApiInfo apiInfo = new ApiInfo(
StringUtils.capitalize(appName) + " " + MANAGEMENT_TITLE_SUFFIX,
MANAGEMENT_DESCRIPTION,
properties.getVersion(),
"",
ApiInfo.DEFAULT_CONTACT,
"",
"",
new ArrayList<>()
);
return createDocket()
.apiInfo(apiInfo)
.useDefaultResponseMessages(properties.isUseDefaultResponseMessages())
.groupName(MANAGEMENT_GROUP_NAME)
.host(properties.getHost())
.protocols(new HashSet<>(Arrays.asList(properties.getProtocols())))
.securitySchemes(Arrays.asList((apiKey())))
.securityContexts(Arrays.asList(
SecurityContext.builder()
.securityReferences(
Arrays.asList(SecurityReference.builder()
.reference("JWT")
.scopes(new AuthorizationScope[0])
.build()
)
)
.build())
)
.forCodeGeneration(true)
.directModelSubstitute(ByteBuffer.class, String.class)
.genericModelSubstitutes(ResponseEntity.class)
.ignoredParameterTypes(Pageable.class)
.select()
.paths(regex(managementContextPath + ".*"))
.build();
}
protected Docket createDocket() {
return new Docket(DocumentationType.SWAGGER_2);
}
private ApiKey apiKey() {
return new ApiKey("JWT", AUTHORIZATION_HEADER, ApiKeyVehicle.HEADER.getValue());
}
} // END
At first i got a similar problem like yours and i searched to find your post.
But my project uses .net core,and from the url below i found a solution.
Hope it could help you if you haven't got your problem fixed.
https://github.com/domaindrivendev/Swashbuckle.AspNetCore#add-security-definitions-and-requirements

Simulate Anonymous Authentication in Spring MVC Unit Test

I am trying to write a unit test for a guest user account. The code under test checks guest by calling this method, which in Unit Test returns null for the guest account.
/**
* Determines if the user is a guest account.
*
* #return True if the account is guest account, false otherwise.
*/
public boolean isGuest() {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth != null) {
if (auth instanceof AnonymousAuthenticationToken) {
return true;
} else {
return false;
}
} else {
return false;
}
}
In the server Tomcat container, the anonymous user is okay it returns an instance of AnonymousAuthenticationToken. Because the container environment & unit test environment both share the same security configuration class, assume the security config is probably correct.
The test code below also works with the MockUser so I also think the security test configuration is probably okay:
#Test
#WithMockUser(username="Test.Customer.1#mailinator.com", roles = {"ADMIN"})
public void testCheckoutPage() throws Exception{
logger.entry();
String targetView = OrderViews.convertViewReference(getPageDirectory(), OrderViews.CHECKOUT_LOGIN_PAGE, false);
String targetUrl = "/checkout";
Order order = OrderBuilder.buildSampleGuestOrder(OrderStatus.NEW, 5);
prepareMocks(order);
Map<String, Object> sessionAttrs = new HashMap<>();
sessionAttrs.put(OrderConstants.OPEN_ORDER_ID_ATTRIBUTE, order.getId());
this.mockMvc.perform(get(targetUrl).sessionAttrs(sessionAttrs))
.andExpect(status().isOk())
.andExpect(view().name(targetView))
.andExpect(model().attribute("order", order))
.andExpect(model().attributeExists("loginForm"));
this.mockMvc.perform(MockMvcRequestBuilders.post(targetUrl))
.andExpect(status().isMethodNotAllowed());
logger.exit();
}
Does anyone have an idea how to simulate the anonymous authentication token in Unit Test?
In Spring Security 4.1 (not yet GA) we are introducing support for #WithAnonymousUser.
The #WithAnonymousUser support is built using #WithSecurityContext. This means you could easily add the support to your codebase in 4.0.x until 4.1.x is released. To get it to work you would need to copy the following classes to your test source folders:
package org.springframework.security.test.context.support;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.core.context.SecurityContext;
#Target({ ElementType.METHOD, ElementType.TYPE })
#Retention(RetentionPolicy.RUNTIME)
#Inherited
#Documented
#WithSecurityContext(factory = WithAnonymousUserSecurityContextFactory.class)
public #interface WithAnonymousUser {}
package org.springframework.security.test.context.support;
import java.util.List;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.core.Authentication;
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;
final class WithAnonymousUserSecurityContextFactory implements
WithSecurityContextFactory<WithAnonymousUser> {
public SecurityContext createSecurityContext(WithAnonymousUser withUser) {
List<GrantedAuthority> authorities = AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS");
Authentication authentication = new AnonymousAuthenticationToken("key", "anonymous", authorities);
SecurityContext context = SecurityContextHolder.createEmptyContext();
context.setAuthentication(authentication);
return context;
}
}
Then you can use the following to run as an anonymous user:
#Test
#WithAnonymousUser
public void testAnonymous() throws Exception {
// ...
}
NOTE: It is important to note that just as you needed to do for #WithMockUser you need to ensure you setup MockMvc with apply(springSecurity()) as outlined in the reference.
Set the authentication before running test
#Before
public void setupAuthentication(){
SecurityContextHolder.getContext().setAuthentication(new AnonymousAuthenticationToken("GUEST","USERNAME", AuthorityUtils
.createAuthorityList("ROLE_ONE", "ROLE_TWO")));
}

Why is the configured servlet path correctly used by the REST controllers, but ignored in Spring Security features?

I'm new to Spring and I try to create a secured rest application using Spring Boot and Spring Security. I'm searching for weeks for a solution now...
I'm using Spring Boots embedded web container (Tomcat) and the spring-boot-starter-parent 1.2.6.RELEASE in my pom.
My endpoints:
/login (to authenticate)
/application/{id} (some service which I want to secure)
I configured my servlet path in my application.properties like this:
server.servletPath: /embedded
so I expect my services e.g. on //localhost/embedded/login
Ok so now the problem: If I run the application without security everything is fine, I can call http//localhost/embedded/application and get an answer.
If I now add my security configuration like this:
import javax.servlet.ServletContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.servlet.configuration.EnableWebMvcSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
#Configuration
#EnableWebMvcSecurity
#EnableScheduling
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private TokenAuthenticationService tokenAuthenticationService;
#Value("${server.servletPath}")
private String servletPath;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/hello/**", "/login").permitAll()
.antMatchers("/application/**").authenticated().and()
.addFilterBefore(new TokenAuthenticationFilter(tokenAuthenticationService), UsernamePasswordAuthenticationFilter.class);
http.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.httpBasic().disable();
}
}
when running the application //localhost/application/{id} is secured instead of
//localhost/embedded/application/{id} as I would have expected.
For some reason the servlet path is ignored there. I tought "ok so I just add the servlet path manually" and make it look like this:
...antMatchers(servletPath+"/application/**").authenticated()...
This works in my application. However I also use MockMvc to test my services and for some reason there the servlet path is correctly added to the matchers. So if I start the tests the security filters are mapped to //localhost/embedded/embedded/application/{id} while the controllers themselves still are mapped to //localhost/embedded/application/{id} which is very annoying...
I took a look at here http://spring.io/blog/2013/07/03/spring-security-java-config-preview-web-security/ and thought I could fix the issue by using AbstractSecurityWebApplicationInitializer instead of SpringBootServletInitializer but it changed nothing.
This is my application class by the way:
com.sebn.gsd.springservertemplate.service.security.WebSecurityConfig;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.context.web.SpringBootServletInitializer;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
#Configuration
#ComponentScan
#EnableAutoConfiguration
public class Application extends SpringBootServletInitializer {
public static void main(String[] args) {
System.out.println("Run from main");
SpringApplication.run(applicationClass, args);
}
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(applicationClass, WebSecurityConfig.class);
}
private static Class<Application> applicationClass = Application.class;
}
The application.properties doesn't contain any more interesting information I think. To be complete this is my MockMvc testing class:
import com.fasterxml.jackson.databind.ObjectMapper;
import com.sebn.gsd.springservertemplate.service.api.LoginData;
import com.sebn.gsd.springservertemplate.service.security.Session_model;
import com.sebn.gsd.springservertemplate.service.security.WebSecurityConfig;
import java.util.Arrays;
import org.hamcrest.Matchers;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.web.servlet.MockMvc;
import static org.hamcrest.Matchers.notNullValue;
import org.junit.Assert;
import org.junit.Before;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.security.web.FilterChainProxy;
import org.springframework.test.web.servlet.ResultActions;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
import static org.springframework.test.web.servlet.setup.MockMvcBuilders.webAppContextSetup;
import org.springframework.web.context.WebApplicationContext;
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = {Application.class, WebSecurityConfig.class })
#WebAppConfiguration
#ActiveProfiles(profiles = "development")
public class SecurityTests {
private MockMvc mockMvc;
#Autowired
private WebApplicationContext webApplicationContext;
private HttpMessageConverter mappingJackson2HttpMessageConverter;
private ObjectMapper o = new ObjectMapper();
#Autowired
private FilterChainProxy filterChainProxy;
#Value("${server.servletPath}")
private String servletPath;
#Before
public void setup() throws Exception {
this.mockMvc = webAppContextSetup(webApplicationContext).addFilter(filterChainProxy).build();
}
#Test
public void testLoginSecurity() throws Exception {
int applicationId = 1;
// Try to access secured api
ResultActions actions = mockMvc.perform(get("/application/" + applicationId))
.andDo(MockMvcResultHandlers.print())
.andExpect(status().isForbidden());
//login
String username = "user";
LoginData loginData = new LoginData();
loginData.setPasswordBase64("23j4235jk26=");
loginData.setUsername(username);
actions = mockMvc.perform(post("/login").content(o.writeValueAsString(loginData)).contentType(MediaType.APPLICATION_JSON_VALUE))
.andDo(MockMvcResultHandlers.print())
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$.login", Matchers.equalTo(username)))
.andExpect(jsonPath("$.token", notNullValue()))
.andExpect(jsonPath("$.expirationDate", notNullValue()));
Session_model session = getResponseContentAsJavaObject(actions.andReturn().getResponse(), Session_model.class);
Assert.assertNotNull(session);
// Try to access secured api again
actions = mockMvc.perform(get("/application/" + applicationId).header("X-AUTH-TOKEN", session.getToken()))
.andDo(MockMvcResultHandlers.print())
.andExpect(status().isOk());
}
private <T> T getResponseContentAsJavaObject(MockHttpServletResponse response, Class<T> returnType) throws Exception{
return o.readValue(response.getContentAsString(), returnType);
}
#Autowired
void setConverters(HttpMessageConverter<?>[] converters) {
this.mappingJackson2HttpMessageConverter = Arrays.asList(converters).stream().filter(
hmc -> hmc instanceof MappingJackson2HttpMessageConverter).findAny().get();
Assert.assertNotNull("the JSON message converter must not be null",
this.mappingJackson2HttpMessageConverter);
}
}
Maybe I misunderstood something. I hope you can tell me.
Summary
In short you need to map Spring Security to use include the servlet path. Additionally, you need to include the servlet path in your MockMvc requests. To do so you can perform something like:
#Before
public void setup() throws Exception {
this.mockMvc = webAppContextSetup(webApplicationContext)
// ADD LINE BELOW!!!
.defaultRequest(get("/").servletPath(servletPath))
.addFilter(filterChainProxy)
.build();
}
Detailed Response
Spring Security Matches Based on Context Root
Spring Security's matchers are relative to the application's context root. It is not relative to the servlet path. This is deliberate because it should protect all the servlets (not just Spring MVC). If it were relative to the servlet, consider the following:
servlet1-path/abc -> Only users with role ROLE_ADMIN can access
servlet2-path/abc -> Only users with role ROLE_USER can access
How would you differentiate between these two mappings if Spring Security were relative to the servlet path?
Working in Mock MVC
The reason Spring Security is working in MockMvc is because when you are using MockMvc the servlet path is no longer considered. Your requests are being sent to Spring Security and Spring MVC as though the servlet path is "". To fix this you need to include the servlet path in the request.
#Before
public void setup() throws Exception {
this.mockMvc = webAppContextSetup(webApplicationContext)
// ADD LINE BELOW!!!
.defaultRequest(get("/").servletPath(servletPath))
.addFilter(filterChainProxy)
.build();
}

Resources