I've got java Spring Security app (built with jhipster) and I'm trying to add some unit tests that test logic based on the current authenticated user.
To do that i've configured my MockMvc object to use a web application context and spring security as so:
#Test
#Transactional
public void getAllContacts() throws Exception {
restContactMockMvc = MockMvcBuilders
.webAppContextSetup(context)
.apply(springSecurity())
.build();
restContactMockMvc.perform(get("/api/contacts")).andExpect(status().isOk());
}
The problem is that I've also configured the app to require HTTPS, and whenever I run this unit test I get a 302 redirect response since it appears to be trying to send an HTTP request instead of HTTPS.
The way I've enforced HTTPS is with the following line in SecurityConfiguration.configure(HttpSecurity http):
if (env.acceptsProfiles("!dev"))http.requiresChannel().anyRequest().requiresSecure();
So, my question is how can I get my unit test to run correctly? Probably either by sending a mock HTTPS request, or by disabling HTTPS in the case of running a unit test?
Using Springs MockMvc stuff you can also specify that you want to send a mock HTTPS request using the secure(true) method as follows:
restContactMockMvc.perform( get("/api/contacts").secure( true ) ).andExpect( status().isOk() )
I'm answering my own question with the fix that i've started using, in hopes that this can help others. I'm still open to other better solutions.
In SecurityConfiguration.configure(HttpSecurity http) method, i've modified the if statement for enforcing https to skip it if we are running an integrationTest:
if (env.acceptsProfiles("!dev") && !((StandardEnvironment) env).getPropertySources().contains("integrationTest")) {
http.requiresChannel().anyRequest().requiresSecure();
}
In addition to the correct answer of #rhinds you can also add this code to your #Before method to have all calls be secure by default:
import static org.springframework.test.web.servlet.setup.MockMvcBuilders.standaloneSetup;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.testSecurityContext;
public class YourTestClass {
#Autowired
private Filter springSecurityFilterChain;
#Autowired
private SecurityTestController securityTestController;
private MockMvc mockMvc;
#Before
public void setup() {
mockMvc = standaloneSetup(securityTestController)
.addFilters(springSecurityFilterChain)
.defaultRequest(get("/").secure(true).with(testSecurityContext()))
.build();
}
// Your Tests Here
#Test
public void httpsRedirect() throws Exception {
mockMvc.perform(get("/").secure(false))
.andExpect(status().isFound())
.andExpect(result -> StringUtils.isNotEmpty(result.getResponse().getHeader("Location")))
.andExpect(result -> result.getResponse().getHeader("Location").startsWith("https://"));
}
}
With a bad configuration, Spring Security can change response status to "302 FOUND". Double check your security config.
#Bean
#Throws(Exception::class)
fun authTokenFilterBean(): CustomAuthTokenFilter {
val authTokenFilter = CustomAuthTokenFilter()
// if you don't have these lines, add them:
authTokenFilter.setAuthenticationSuccessHandler(MyAuthSuccessHandler())
authTokenFilter.setAuthenticationFailureHandler(MyAuthFailureHandler())
return authTokenFilter
}
where
CustomAuthTokenFilter is your custom implementation of AbstractAuthenticationProcessingFilter;
MyAuthSuccessHandler and MyAuthFailureHandler are handlers provided by you. They can be empty, or they can contain your custom logic.
Related
I am writing a unit test for spring security and JWT validation. There are 3 basic cases I want to start the test with:
When no token -> expect 401
When token but wrong scope -> expect 403
When token and scope -> expect 200
I tested my code using postman and they return expected responses. In my unit test I have this:
#SpringBootTest
#ContextConfiguration(classes = SecurityConfig.class)
#AutoConfigureMockMvc
#EnableAutoConfiguration
#Import(DataImporterControllerConfig.class)
public class SecurityConfigTest {
#Autowired
private MockMvc mockMvc;
#Test
void doImportExpect200() throws Exception {
mockMvc.perform(put(URI).with(jwt().authorities(new SimpleGrantedAuthority(
"SCOPE_data:write"))).contentType(APPLICATION_JSON_VALUE).accept(APPLICATION_JSON)
.content(BODY)).andExpect(status().isOk());
}
It does pass validation part and try to return some value from the controller:
#RestController
#RequestMapping(value = "${data.uriPrefix}")
#Loggable(value = INFO, trim = false)
public class DataImporterController {
private final DataImporterService dataImporterService;
#PutMapping(path = "/someurl", produces = APPLICATION_JSON_VALUE, consumes = APPLICATION_JSON_VALUE)
public dataImportResponse doImport(#RequestHeader(name = ACCEPT, required = true) final String acceptHeader,
final #RequestBody #NotBlank String body) {
return new DataImportResponse(dataImporterService.doImport(body));
}
The logic inside dataImporterService.doImport(body) require some db operation, so ideally, I want to mock it and make it return some value (something like when(dataImporterServiceMock.doImport(body)).thenReturn(something).
However, when I try it, it doesn't work. I think it is because I am not creating a controller with mocked service. I tried to create one, but due to configuration for SecurityConfig class, it is not that easy. Here is SecurityConfig class:
#EnableWebSecurity
public class SecurityConfig {
#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/**").permitAll()
.antMatchers(HttpMethod.PUT, "/dataimporter/**").hasAuthority("SCOPE_data:write")
.anyRequest().authenticated()
.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 = (NimbusJwtDecoder)
JwtDecoders.fromOidcIssuerLocation(issuer);
OAuth2TokenValidator<Jwt> audienceValidator = new AudienceValidator(audience);
OAuth2TokenValidator<Jwt> withIssuer = JwtValidators.createDefaultWithIssuer(issuer);
OAuth2TokenValidator<Jwt> withAudience = new DelegatingOAuth2TokenValidator<>(withIssuer, audienceValidator);
jwtDecoder.setJwtValidator(withAudience);
return jwtDecoder;
}
}
I got this code from Auth0 and just modified antMatcher portion. How can I test it so it returns 200 (mock service or something)?
As already stated in the answer to your other question "How to write unit test for SecurityConfig for spring security", for unit-testing a #Controller, use #WebMvcTest (with mocked dependencies), not #SpringBootTest which is intended for integration testing, loads much more config, and instantiate & autowire actual components (and, as a consequence, is slower and less focused)
#WebMvcTest(controllers = DataImporterController.class)
public class DataImporterControllerUnitTest {
#MockBean
DataImporterService dataImporterService;
#Autowired
private MockMvc mockMvc;
#BeforeEach
public void setUp() {
when(dataImporterService.doImport(body)).thenReturn(something);
}
#Test
void doImportExpect200() throws Exception {
mockMvc.perform(put(URI).with(jwt().authorities(new SimpleGrantedAuthority(
"SCOPE_data:write"))).contentType(APPLICATION_JSON_VALUE).accept(APPLICATION_JSON)
.content(BODY)).andExpect(status().isOk());
}
}
If you had followed the link I already provided, you'd have find that in the main README, in the many samples, and in the tutorials
Notes
I am writing a unit test for spring security and JWT validation.
This are completely separate concerns and is to be done separately:
spring security access-control is unit-tested with secured components: #Controller of course, but also #Service, #Repository, etc. where method-security is used (refer to your previous question for instructions and to my repo for samples)
Authorization header is not considered when building #WebMvcTest or #WebfluxTest security-context. Authentication instances are built based on MockMvc request post-processors, WebTestClient mutators or annotations and this does not involve any token decoding or validation. JWT validation has to be tested in a JwtDecoder unit-test (only if you keep your own, which you shouldn't, see below)
Spring Security does not validate the "aud" claim of the token
This is wrong with spring-boot since 2.7: spring.security.oauth2.resourceserver.jwt.audiences property does just that. There is no need for you to override the JwtDecoder provided by spring-boot (actually, you shouldn't), and, as a consequence to unit-test your own.
When no token -> expect 401
When token but wrong scope -> expect 403
When token and scope -> expect 200
To be exact, the phrasing would better be:
"anonymous" rather than "no token"
"authenticated" rather than just "token"
"authorities" rather than "scope"
The reason for that are:
the OAuth2 token is validated and then turned into an Authentication instance: by default AnonymousAuthenticationToken if token is missing or validation fails, and whatever the configured authentication-converter returns if validation is successful
spring-security Role-Based Access-Control is completely generic (nothing specific to OAuth2) and relies on GrantedAuthority, not scope. This common confusion is due to the fact that there is nothing related to RBAC in OAuth2 nor OpenID standards and the default authentication-converter had to choose a claim that is always there to map authorities from. scope claim was picked as default, adding the SCOPE_ prefix. You should refer to how RBAC is implemented by your own authorization server and provide an authentication-converter bean to map authorities from the right claim(s).
Auth0 uses roles and permissions claims when RBAC is enabled, Keycloak uses realm_access.roles and resource_access.{client-id}.roles, etc., reason for me implementing a configurable authorities-converter, which picks the claims that should be used as authorities source from application properties (and how to map it: prefix and case transformation).
Last, your configuration is still risky (enabled sessions with disabled CSRF protection and poor CORS config). You should really consider using "my" starters, or follow the tutorial I wrote for configuring a resource-server with just spring-boot-oauth2-resource-server.
I have an endpoint that comes from a gradle dependency, lets say its /v3/doc
I needed to expose an endpoint, lets call it, /v1/info that hits /v3/doc internally, massages, some of the data that comes out, then returns that piece of data to whomever called /v1/info
I wrote the code for it (below i am omitting the massaging part for simplicity), but the unit tests throw the error described during test execution.
My endpoint looks like this:
#RestController
public class MyController {
#GetMapping("v1/info")
public #ResponseBody SomePojo swag(HttpServletRequest request) {
String s = retrieve(request
.getRequestURL()
.toString()
.split("v1")[0] // get the base URL and tack on what i need
.concat("v3/doc")
);
return new Gson().fromJson(s, SomePojo.class)
}
private String retrieve(String url) {
return new RestTemplate()
.getForEntity(url, String.class)
.getBody();
}
}
unit test looks like:
#WebMvcTest(MyController.class)
#ActiveProfiles(profiles = "local")
public class MyControllerTest {
#Autowired
private MockMvc mockMvc;
#Test
public void testEndpoint() throws Exception {
mockMvc.perform(get("/v1/info")
.contentType(MediaType.APPLICATION_JSON)
.characterEncoding("utf-8"))
.andExpect(status().isOk());
}
}
error looks like:
org.springframework.web.util.NestedServletException: Request processing failed; nested exception is org.springframework.web.client.ResourceAccessException: I/O error on GET request for "http://localhost/v3/doc": Connection refused (Connection refused); nested exception is java.net.ConnectException: Connection refused (Connection refused)
im wondering if it has something to do with the port?? because when i run the app on 8001 i can hit /v3/doc and /v1/info no problem. But as you can see when the unit tests run, there is no port, just localhost. I didnt think that mattered though. Will i have to create some kind of fake mvc stub?
You could try not autowiring the MockMvc and instead instantiate it in the setup like:
#Autowired
private WebApplicationContext context;
#Before
public void setUp() {
mockMvc = MockMvcBuilders
.webAppContextSetup(context)
.build();
}
Your test context created only around #WebMvcTest(MyController.class). Thats means no another endpoints as well as applications server also did not running in this case. And when you call restTemplate it fails.
In case of you wanna test you should move RestTemplate creation outside method and made #Autowirible for example. After you can easily mock it in your test.
Or in case of your third party service is always available you got error because the port is not defined, but it is look conceptually incorrect for unit test.
I want to test the login process in a Spring Boot Application using MockMvc. After the successful login, the user gets redirected to /home. To test this, I use:
#Test
public void testLogin() throws Exception {
RequestBuilder requestBuilder = formLogin().user("test#tester.de").password("test");
mockMvc.perform(requestBuilder).andExpect(redirectedUrl("/home")).andExpect(status().isFound());
}
This test delivers the expected results.
In addition, I must test the HTTP status code of the redirected page (/home). Lets say the /home-page returns an HTTP 500 internal Server error, I need to be able to test this.
I tried the following:
#Test
public void testLogin() throws Exception {
RequestBuilder requestBuilder = formLogin().user("test#tester.de").password("test");
mockMvc.perform(requestBuilder).andExpect(redirectedUrl("/home")).andExpect(status().isFound());
mockMvc.perform(get("/home").with(csrf())).andExpect(status().isOk());
}
Instead if getting a 200 or a 500 (in case of an error), I get the status code 302.
Is there any way to correctly test the HTTP status code when following a redirect-URL?
Thanks and best regards
First, I would split your test into 2 separate tests, because you are testing 2 quite different scenarios:
#Test
public void testSuccessfulLogin() throws Exception {
RequestBuilder requestBuilder = formLogin().user("test#tester.de").password("test");
mockMvc.perform(requestBuilder).andExpect(redirectedUrl("/home")).andExpect(status().isFound());
}
#Test
public void testHomepageThrows500() throws Exception {
// configure a mock service in the controller to throw an exception
RequestBuilder requestBuilder = formLogin().user("test#tester.de").password("test");
mockMvc.perform(requestBuilder).andExpect(redirectedUrl("/home")).andExpect(status().is5xxServerError());
}
Your first test is that of the successful login scenario.
The second test, as you've worded it in your question, is that where the home page (assuming a controller) returns a HTTP 500.
To get to the home page, you still need to login - it's not the act of logging on that generates the error, its the controller itself once you've logged on.
To make the controller return a HTTP 500 you are going to need to simulate some error. Without seeing your controller, I can only guess there is some service that is injected in. In your test you should be able to provide a mock of that, and then configure the mock to throw an exception.
You should be able to inject a mock something like this:
#RunWith(SpringJUnit4ClassRunner.class)
#WebMvcTest(HomeController.class)
public class HomeControllerIntegrationTest {
#Autowired
private MockMvc mockMvc;
#MockBean
private YourService yourService;
And then within your test do something like the following (I'm using the BDD methods of mockito):
#Test
public void testHomepageThrows500() throws Exception {
given(yourService.someMethod()).willThrow(new Exception("something bad happened");
RequestBuilder requestBuilder = formLogin().user("test#tester.de").password("test");
mockMvc.perform(requestBuilder).andExpect(redirectedUrl("/home")).andExpect(status().is5xxServerError());
}
I am writing unit test cases for Controller layer. I have a call where i am getting user from Spring SecurityContextHolder. When i run my test case i get Null pointer exception because I don't know how to mock Spring security context.
Below is my code, any suggestion how to do it?
Controller Methhod:
#RequestMapping(method = RequestMethod.POST)
public void saveSettings(#RequestBody EmailSettingDTO emailSetting) {
User user = ((CurrentUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal()).getUser();
settings.saveUserEmailSetting(user, emailSetting);
}
My Test case :
#Test public void testSaveSettings() throws Exception {
mockMvc.perform(post(BASE_URL).content(this.objectMapper.writeValueAsString(emailDto))
.contentType(MediaTypes.HAL_JSON)).andExpect(status().isOk());
}
There is a Spring Security Test library for this purpose.
You can use #WithMockUser to achieve this. See the post
You can use #WithUserDetails
this annotation can be added to a test method to emulate running with a UserDetails returned from the UserDetailsService.
By using this, you create a context to run a test in, for example:
#Test
#WithUserDetails("admin")
public void testAdmin() throws Exception {
mockMvc.perform(...);
}
This will execute testAdmin() with the SecurityContext of admin.
But please note, in order to use this; there must be a User persisted with the name admin, otherwise you will get result exceptions.
I have a class that makes use of a Feign client. Previously I used Mockito and gave a stored response for each of the method calls in the Feign client. Now I want to use WireMock, so that I can see that my code handles different kinds of response codes correctly. How do I go about doing this? I can't figure out how to wire up my Feign client in the test, and wire it up so that it uses Wiremock instead of the URL I've setup in my application.yml file. Any pointers would be greatly appreciated.
Maybe you want to look at this project https://github.com/ePages-de/restdocs-wiremock
This helps you generate and publish wiremock snippets in your spring mvc tests (using spring-rest-docs).
Finally you can use these snippets to start a wiremock server to serve these recorded requests in your test.
If you shy away from this integrated solution you could just use the wiremock JUnit rule to fire up your wiremock server during your test.
http://wiremock.org/docs/junit-rule/
Here is a sample test that uses a dynamic wiremock port and configures ribbon to use this port: (are you using feign and ribbon?)
#WebAppConfiguration
#RunWith(SpringRunner.class)
#SpringBootTest()
#ActiveProfiles({"test","wiremock"})
public class ServiceClientIntegrationTest {
#Autowired //this is the FeignClient service interface
public ServiceClient serviceClient;
#ClassRule
public static WireMockRule WIREMOCK = new WireMockRule(
wireMockConfig().fileSource(new ClasspathFileSource("path/to/wiremock/snipptes")).dynamicPort());
#Test
public void createSome() {
ServiceClient.Some t = serviceClient.someOperation(new Some("some"));
assertTrue(t.getId() > 0);
}
//using dynamic ports requires to configure the ribbon server list accordingly
#Profile("wiremock")
#Configuration
public static class TestConfiguration {
#Bean
public ServerList<Server> ribbonServerList() {
return new StaticServerList<>(new Server("localhost", WIREMOCK.port()));
}
}
}