I use Spring Boot with Spring Security. I want to disable security so that #PreAuthorize. I partially managed to do it, but there is still an error.
security is partially disabled. but some part is included. finally, I would like to disable security for certain tests
org.springframework.security.access.AccessDeniedException: Access denied
at org.springframework.security.access.vote.AffirmativeBased.decide(AffirmativeBased.java:73) ~[spring-security-core-5.5.3.jar:5.5.3]
at org.springframework.security.access.intercept.AbstractSecurityInterceptor.attemptAuthorization(AbstractSecurityInterceptor.java:238) ~[spring-security-core-5.5.3.jar:5.5.3]
This is my controller
#PreAuthorize("hasAnyAuthority('ADMIN')")
#GetMapping("/hello")
fun hello(): String {
return "Hello"
}
This my Spring Configutarion for tests.
#TestConfiguration
#Order(1)
class TestSecurityConfig : WebSecurityConfigurerAdapter() {
#Override
override fun configure(httpSecurity: HttpSecurity) {
http.authorizeRequests()
.anyRequest().permitAll();
http.csrf().disable()
.httpBasic().disable()
.formLogin().disable()
.logout().disable();
}
}
And finally my test class:
#TestInstance(TestInstance.Lifecycle.PER_CLASS)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
classes = [TestSecurityConfig::class])
#ActiveProfiles("test")
#ExtendWith(SpringExtension::class)
class HelloControllerTest {
#LocalServerPort
private var port: Int = 8281
#Value("#{servletContext.contextPath}")
private lateinit var contextPath: String
private lateinit var url: String
#Autowired
private lateinit var testRestTemplate: TestRestTemplate
#BeforeAll
fun setUp() {
url = UriComponentsBuilder
.fromUriString("http://localhost")
.port(port)
.path(contextPath)
.pathSegment("hello")
.toUriString()
}
#Test
fun hello() {
val responseEntity = testRestTemplate.getForEntity(url, String::class.java)
assertNotNull(responseEntity)
assertEquals(HttpStatus.OK, responseEntity.statusCode)
val response = responseEntity.body
}
Usually you can use mock system for authorization with spring-test
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-test</artifactId>
<scope>test</scope>
</dependency>
For web FLUX
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient;
import org.springframework.beans.factory.annotation.Autowired;
#WithMockUser
#SpringBootTest
#AutoConfigureWebTestClient
class MyTest {
#Autowired
WebTestClient rest;
#Test
void fooTest() {
StatusAssertions.isOk(rest.get().uri(path).exchange().expectStatus())
}
}
For web MVC
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.beans.factory.annotation.Autowired;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
#WithMockUser
#AutoConfigureMockMvc
#SpringBootTest
class MyTest {
#Autowired
private MockMvc mvc;
#Test
void fooTest() {
mvc.perform(get("path")).andExpect(status().isOk())
}
}
Related
I am struggling with the setup of a Rest Controller test due to Spring Security.
I am trying to test a basic rest endpoint:
#RestController
#RequestMapping(value = "/product")
public class ProductInformationController {
private final ProductInformationService productInformationService;
...
#DeleteMapping(value = "/{id}")
public ResponseEntity<Void> deleteProductById(#PathVariable int id) {
productInformationService.deleteProductById(id);
return ResponseEntity.noContent().build();
}
The basic smoke test looks as follows:
#ExtendWith(SpringExtension.class)
#ContextConfiguration
#WebMvcTest({ProductInformationController.class, CommonControllerAdvice.class})
class ProductInformationControllerTest {
private static final String SINGLE_RESOURCE_ENDPOINT_URL = "/product/{id}";
#Autowired
MockMvc mockMvc;
#MockBean
ProductInformationService service;
...
#Test
#WithMockUser(roles={"USER","ADMIN"})
void shouldReturn204ForDeleteProduct() throws Exception {
var productId = 1;
mockMvc.perform(delete(SINGLE_RESOURCE_ENDPOINT_URL, productId))
.andExpect(status().isNoContent());
}
I have Spring Security among the dependencies but the configuration that exposes the SecurityFilterChain as a #Bean is not among the classes that are loaded for this WebMvcTest.
However, even despite having added both theoretically possible User Roles the test returns a HTTP 403 and does not even propagate the call to any handler:
MockHttpServletRequest:
HTTP Method = DELETE
Request URI = /product/1
Parameters = {}
Headers = []
Body = null
Session Attrs = {org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository.CSRF_TOKEN=org.springframework.security.web.csrf.DefaultCsrfToken#7ba1cdbe, SPRING_SECURITY_CONTEXT=SecurityContextImpl [Authentication=UsernamePasswordAuthenticationToken [Principal=org.springframework.security.core.userdetails.User [Username=user, Password=[PROTECTED], Enabled=true, AccountNonExpired=true, credentialsNonExpired=true, AccountNonLocked=true, Granted Authorities=[ROLE_ADMIN, ROLE_USER]], Credentials=[PROTECTED], Authenticated=true, Details=null, Granted Authorities=[ROLE_ADMIN, ROLE_USER]]]}
Handler:
Type = null
...
MockHttpServletResponse:
Status = 403
Error message = Forbidden
Headers = [X-Content-Type-Options:"nosniff", X-XSS-Protection:"1; mode=block", Cache-Control:"no-cache, no-store, max-age=0, must-revalidate", Pragma:"no-cache", Expires:"0", X-Frame-Options:"DENY"]
Content type = null
Body =
Forwarded URL = null
Redirected URL = null
Cookies = []
Manual tests of the endpoint work as expected. Any tests using GET work as expected as well, other HTTP verbs run into the same issue.
I assume the issue is the partial setup of Spring Security here. Since I only want to test the Controller I don't want to include the entire Spring Security configuration.
How can I make this controller test work with the most minimal set of other classes and a most lean application context?
I think I stumbled across the same issue. As a solution I conditionally added some dummy users to the security chain.
As a base for mvc tests with Spring-Security as of Spring-Boot version 2.7.0 I made this abstract base class:
import org.junit.jupiter.api.BeforeEach;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.boot.test.web.server.LocalServerPort;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.RequestPostProcessor;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
import javax.servlet.Filter;
import java.util.List;
#SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
#AutoConfigureMockMvc
public abstract class AbstractMvcTest {
#LocalServerPort
protected int port;
#Autowired
protected TestRestTemplate restTemplate;
protected MockMvc mockMvc;
#Autowired
private Filter springSecurityFilterChain;
#Autowired
private WebApplicationContext context;
#Autowired
private PasswordEncoder passwordEncoder;
private static final SimpleGrantedAuthority USER_AUTHORITY = new SimpleGrantedAuthority("USER");
private static final SimpleGrantedAuthority ADMIN_AUTHORITY = new SimpleGrantedAuthority("ADMIN");
#BeforeEach
void setUp() {
mockMvc = MockMvcBuilders.webAppContextSetup(context).addFilters(springSecurityFilterChain).build();
}
protected RequestPostProcessor makeAuthorizedAdminUser() {
return SecurityMockMvcRequestPostProcessors.user(new User(
"Admin#example.com",
passwordEncoder.encode("verys3cur3"),
List.of(ADMIN_AUTHORITY, USER_AUTHORITY)));
}
protected RequestPostProcessor makeAuthorizedUser() {
return SecurityMockMvcRequestPostProcessors.user(new User(
"User#example.com",
passwordEncoder.encode("foobar123"),
List.of(USER_AUTHORITY)));
}
}
A concrete test would look like this:
class IndexControllerTest extends AbstractMvcTest
{
#Autowired
private IndexController controller;
#Test
void contextLoads()
{
assertThat(controller).isNotNull();
}
#Test
void userLoggedIn_returnOk() throws Exception
{
this.mockMvc.perform(get("/")
.with(makeAuthorizedUser()))
.andDo(print())
.andExpect(status().isOk());
}
}
Where indexController just returns serves an index.html under root.
I am new to Spring boot with Swagger UI. I'm just trying to configure my Rest controller endpoints to show on swagger UI screen but it shows No operations for specs defined. Pretty sure, its a configuration issue.
I have tried #EnableAutoConfiguration, still it can't find the controller
SwaggerDemoApplication.java
package com.example.config;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
#SpringBootApplication
public class SwaggerDemoApplication {
public static void main(String[] args) {
SpringApplication.run(SwaggerDemoApplication.class, args);
}
}
SwaggerConfig.java
package com.example.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
import static springfox.documentation.builders.PathSelectors.regex;
#EnableSwagger2
#Configuration
public class SwaggerConfig {
#Bean
public Docket productApi() {
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(RequestHandlerSelectors.basePackage("com.example.controller"))
.paths(regex("/test.*"))
.build();
}
}
TestController.java
package com.example.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import io.swagger.annotations.Api;
#RestController
#RequestMapping(value = "/test")
#Api(value="onlinestore", description="Operations pertaining to products in Online Store")
public class TestController {
#RequestMapping(value = "/test-swagger", method= RequestMethod.GET)
public String home() {
return "Spring is here!";
}
}
Expected: Rest endpoint
Actual: No operations defined in spec
I had exactly the same problem like you and it was caused with the wrong placement of my #SpringBootApplication class, just like you have it.
(In the code snippets, I am intentionally omitting less relevant annotations, they still must be there as you have them in your post.
I also quote the words like "under", "root package" etc., as technically Java does not recognize anything like a "sub-package". All the packages in Java are at the same "level", even if the dots and the resemblance of the web domains "mislead" us to think about them hierarchically. However Spring extensively works with "sub-packages" and "root-packages".)
package com.example.config;
#SpringBootApplication
public class SwaggerDemoApplication {
...
}
package com.example.config;
#EnableSwagger2
public class SwaggerConfig {
...
.apis(RequestHandlerSelectors.basePackage("com.example.controller"))
...
}
#RestController
package com.example.controller; // notice that this is not "under" com.example.config where SwaggerDemoApplication resides
public class TestController {
....
}
I observed that if the #SpringBootApplication class is not in the "root package" of the #RestController class, it breaks the automagical behaviour and the package name set in the call of apis(...) is ignored and the package is not scanned. To be honest, I am not very much sure how the apis() is supposed to work and whether it is a bug or a feature.
To fix it without adding #ComponentScan, your package organisation should be something like this:
package com.example.myapplication; // the main class is placed in the "root" package
#SpringBootApplication
public class SwaggerDemoApplication {
...
}
package com.example.myapplication.config;
#EnableSwagger2
public class SwaggerConfig {
...
.apis(RequestHandlerSelectors.basePackage("com.example.myapplication"))
...
}
package com.example.myapplication.controller;
#RestController
public class TestController {
...
}
You may also further filter the API scanning by specifying
.apis(
withClassAnnotation(RestController.class)
.and(basePackage("com.example.myapplication.controller))
)
The problem is not your Swagger Configuration but your Controller is not scanned as Spring resource class.
Because your application startup class (Main class) has no #ComponentScan annotation.
So, your class should be like this:
#EnableSwagger2
#SpringBootApplication
#ComponentScan("com.example")
public class SwaggerDemoApplication {
public static void main(String[] args) {
SpringApplication.run(SwaggerDemoApplication.class, args);
}
}
Now you can access your API documentation from here http://localhost:8098/swagger-ui.html#
You can try it in this way.
Path you specified can be the problematic.
#Configuration
#EnableSwagger2
public class SwaggerConfig {
#Bean
public Docket docket() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.basePackage("<your-package>"))
.paths(PathSelectors.any())
.build();
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("API Documentation")
.description("This API documentation is related <something>")
.version("1.0.0")
.build();
}
}
SwaggerConfig.java
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
#Configuration
#EnableSwagger2
public class SwaggerConfig implements WebMvcConfigurer{
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("swagger-ui.html").addResourceLocations("classpath:/META-INF/resources/");
registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
}
#Bean
public Docket apiDocket() {
Docket docket = new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(RequestHandlerSelectors.basePackage("com.example"))
.paths(PathSelectors.any())
.build();
return docket;
}
}
SwaggerDemoApplication.java
#EnableSwagger2
#SpringBootApplication
public class SwaggerDemoApplication {
public static void main(String[] args) {
SpringApplication.run(SwaggerDemoApplication.class, args);
}
pom.xml
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-core</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
In Case you are using Spring Security you have to add below Configurations
#Override
public void configure(WebSecurity web) throws Exception {
web
.ignoring()
.antMatchers("/v2/api-docs", "/configuration/**", "/swagger*/**", "/webjars/**");
}
#Override
protected void configure(HttpSecurity http) throws Exception{
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/v2/api-docs", "/configuration/**", "/swagger*/**", "/webjars/**")
.permitAll()
.anyRequest().authenticated();
}
I have an annotation that pulls in some configurations (via #Import). I want to test running with and without that annotation. I want to do this in one test class.
I know that I can change the spring context per method #DirtiesContext(classMode = BEFORE_EACH_TEST_METHOD), but I don't know how to have it run with or without the annotation on different methods.
How would I achieve this?
What I want to do:
package com.test.reference.cors;
import com.test.EnableCors;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.context.WebApplicationContext;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.options;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
#RunWith(SpringJUnit4ClassRunner.class)
#WebMvcTest(secure = false)
#ContextConfiguration(classes = {ControllerConfig.class, ReferenceController.class})
public class TestCORS
{
private MockMvc mockMvc;
private ObjectMapper objectMapper;
#Autowired
private RestTemplate restTemplate;
#Autowired
private WebApplicationContext webApplicationContext;
#Before
public void setup()
{
//Create an environment for it
mockMvc = MockMvcBuilders.webAppContextSetup(this.webApplicationContext)
.dispatchOptions(true).build();
//Create our marshaller
objectMapper = new ObjectMapper();
}
#Test
public void testWithoutCors() throws Exception
{
//Call to test a date
MvcResult result = mockMvc.perform(
options("/v1/testdate")
.contentType(MediaType.APPLICATION_JSON)
//CORS HEADERS
.header("Access-Control-Request-Method", "DELETE")
.header("Origin", "https://evil.com")
).andExpect(status().isForbidden())
.andReturn();
}
#Test
#EnableCors
public void testWithCors() throws Exception
{
//Call to test a date
MvcResult result = mockMvc.perform(
options("/v1/testdate")
.contentType(MediaType.APPLICATION_JSON)
//CORS HEADERS
.header("Access-Control-Request-Method", "POST")
.header("Origin", "http://evil.com")
).andExpect(status().isOk())
.andReturn();
}
}
Using nested classes, you could do something like this:
class NewFeatureTest {
#SpringBootTest
protected static class NewFeatureWithDefaultConfigTest {
#Autowired
ApplicationContext context;
#Test
void yourTestMethod() {
}
#Configuration(proxyBeanMethods = false)
#EnableAutoConfiguration
protected static class Config {
// your 1st config
}
}
#SpringBootTest
protected static class NewFeatureWithDefaultsTests {
#Autowired
ApplicationContext context;
#Test
void yourOtherTestMethod() {
}
#Configuration(proxyBeanMethods = false)
#EnableAutoConfiguration
protected static class NoDefaultsConfig {
// your 2nd config
}
}}
I use spring boot version "1.3.0.M5" (I also tried version "1.2.5.RELEASE"). I added spring security:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
and code:
#SpringBootApplication
public class SpringBootMainApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootMainApplication.class, args);
}
}
#Configuration
#EnableWebSecurity
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("user").password("password").roles("USER");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/api/sampleentity").authenticated()
.and().authorizeRequests()
.and().formLogin().permitAll()
.and().logout().permitAll().logoutUrl("/logout")
.logoutSuccessUrl("/");
}
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
#RestController
#RequestMapping("/api/sampleentity")
public class SampleEntityController {
#RequestMapping(method= RequestMethod.GET)
public Iterable<SampleEntity> getAll() {
return ImmutableSet.of();
}
#RequestMapping(method=RequestMethod.POST)
#ResponseStatus(value= HttpStatus.CREATED)
public SampleEntity create(#RequestBody SampleEntity sampleEntity) {
return sampleEntity;
}
}
and test that is failing when /api/sampleentity is access: org.springframework.web.client.HttpClientErrorException: 403 Forbidden (...)
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = SpringBootMainApplication.class)
#WebAppConfiguration
#IntegrationTest({"server.port=0"})
public class SampleEntityTest {
#Value("${local.server.port}")
private int port;
private String url;
private RestTemplate restTemplate;
#Autowired
private ApplicationContext context;
#BeforeClass
public static void authenticate(){
//ONE TRY
// Authentication authentication =
// new UsernamePasswordAuthenticationToken("user", "password",
// AuthorityUtils.createAuthorityList("USER")); //tried "ROLE_USER"
// SecurityContextHolder.getContext().setAuthentication(authentication);
}
#Before
public void setUp() {
url = String.format("http://localhost:%s/api/sampleentity", port);
restTemplate = new RestTemplate();
//ANOTHER TRY
// AuthenticationManager authenticationManager = context.getBean(AuthenticationManager.class);
// Authentication authentication = authenticationManager
// .authenticate(new UsernamePasswordAuthenticationToken("user", "password", AuthorityUtils.createAuthorityList("USER"))); //tried "ROLE_USER"
// SecurityContextHolder.getContext().setAuthentication(authentication);
}
//THIS METHOD SHOULD WORK !
#Test
//ANOTHER TRY
//#WithMockUser(username="user",password = "password", roles={"USER"})//tried "ROLE_USER"
public void testEntity_create() throws Exception {
SampleEntity sampleEntity = create("name", 1);
ResponseEntity<SampleEntity> response = restTemplate.postForEntity(url, sampleEntity, SampleEntity.class);
assertEquals(HttpStatus.CREATED, response.getStatusCode());
}
private SampleEntity create(String name, int id) {
SampleEntity entity = new SampleEntity();
entity.setName(name);
entity.setId(id);
return entity;
}
}
When I run application from main() and access url:
http://localhost:8080/api/sampleentity
I am redirected to login page.
How can I run my test and disable security or just log in user ?
--my solution: exclude security from the test using profiles:
#SpringBootApplication
#EnableAutoConfiguration(exclude = { SecurityAutoConfiguration.class})
public class SpringBootMainApplication {body the same}
#EnableWebSecurity
#Import(SecurityAutoConfiguration.class)
#Profile("!test")
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {body the same}
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = SpringBootMainApplication.class)
#WebAppConfiguration
#IntegrationTest({"server.port=0"})
#ActiveProfiles("test")
public class SampleEntityTest {body the same}
You have to do some changes to your config and test to solve your problem(s).
First I'll explain why your solution isn't working:
The Spring RestTemplate class is a possible way to access your REST service but lacks some header informations the way it is constructed (Which doesn't mean it's impossible with the RestTemplate). Thats why the authentication didn't work.
My first solution attempt isn't working because of the usage of the RestTemplate class, as the RestTemplate request is likely to create a new session. It sets an entirely different environment. My code works if you want to test Methods secured with the #PreAuthorize annotation but only if you want to execute such a method directly in your test and you need a valid authentication.
You can't automatically authorize any user as of your current spring security configuration.
Second, here are the necessary changes to your code:
First the configuration class
#Configuration
#EnableWebSecurity
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("user").password("password").roles("USER" );
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.httpBasic().and().csrf().disable()
.authorizeRequests().antMatchers("/api/sampleentity").authenticated()
.and().authorizeRequests().antMatchers("/users").hasRole("ADMIN")
.and().formLogin().permitAll()
.and().logout().permitAll().logoutUrl("/logout")
.logoutSuccessUrl("/");
}
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
I had to add httpBasic Authentication support (to enable authentication via http header attribute) and I disabled csrf tokens (the latter just for convenience, you should reenable them according to criticality of your application).
And second the Testclass:
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.Arrays;
import javax.servlet.Filter;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.IntegrationTest;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.mock.http.MockHttpOutputMessage;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.*;
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = SpringBootMainApplication.class)
#WebAppConfiguration
#IntegrationTest({ "server.port=0" })
public class SampleEntityTest {
private String url;
private MockMvc mockMvc;
private HttpMessageConverter mappingJackson2HttpMessageConverter;
private MediaType contentType = new MediaType(
MediaType.APPLICATION_JSON.getType(),
MediaType.APPLICATION_JSON.getSubtype(), Charset.forName("utf8"));
#Autowired
private WebApplicationContext webApplicationContext;
#Autowired
private Filter springSecurityFilterChain;
#Autowired
void setConverters(HttpMessageConverter<?>[] converters) {
for (HttpMessageConverter hmc : Arrays.asList(converters)) {
if (hmc instanceof MappingJackson2HttpMessageConverter) {
this.mappingJackson2HttpMessageConverter = hmc;
}
}
Assert.assertNotNull("the JSON message converter must not be null",
this.mappingJackson2HttpMessageConverter);
}
#Before
public void setUp() {
url = "/api/sampleentity";
mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext)
.addFilters(springSecurityFilterChain).build();
}
#Test
public void testEntityGet() throws Exception {
mockMvc.perform(
get(url)
.with(httpBasic("user", "password")))
.andExpect(status().isOk());
}
#Test
public void testEntityPost() throws Exception {
SampleEntity sampleEntity = new SampleEntity();
sampleEntity.setName("name");
sampleEntity.setId(1);
String json = json(sampleEntity);
mockMvc.perform(
post(url)
.contentType(contentType)
.content(json)
.with(httpBasic("user", "password")))
.andExpect(status().isCreated());
}
protected String json(Object o) throws IOException {
MockHttpOutputMessage mockHttpOutputMessage = new MockHttpOutputMessage();
this.mappingJackson2HttpMessageConverter.write(o,
MediaType.APPLICATION_JSON, mockHttpOutputMessage);
return mockHttpOutputMessage.getBodyAsString();
}
}
I have used the spring/ spring security test approach here.
Versions used:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.2.5.RELEASE</version>
</parent>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-core</artifactId>
<version>4.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>4.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>4.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<version>4.0.2.RELEASE</version>
<scope>test</scope>
</dependency>
If you want to test your rest api i can recommend you the Postman plugin for Chrome. As that can help you identify the problem much faster.
I hope this helps you to finally solve your problem.
If you want to see what's being auto-configured, launch your web app and access the autoconfig endpoint (e.g., http://localhost:8080/autoconfig). Then search for 'Security' to see which 'AutoConfiguration' classes are being detected.
You can then disable auto-configuration of security by excluding those classes like this:
#EnableAutoConfiguration(exclude = { SecurityAutoConfiguration.class, ManagementSecurityAutoConfiguration.class })
Of course, you won't want to exclude them for production deployments. Thus you'll need to have a separate #Configuration class for production and tests.
Or if you want a detailed answer go for below-mentioned steps
Add annotation #Profile(value = {"development", "production"}) to my implementation of WebSecurityConfigurerAdapter
#Configuration
#EnableWebSecurity
#Profile(value = {"development", "production"})
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
Now, in test/resources, create application-test.yml to define properties for test profile and add this -
# Security enable/disable
security:
basic:
enabled: false
Now, to your test cases, add this annotation to apply the active profile #ActiveProfiles(value = "test"). This is how my class looked -
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = Application.class)
#WebAppConfiguration
#ActiveProfiles(value = "test")
#IntegrationTest({"server.port=0"})
public class SampleControllerIntegrationTest {
Doing this will disabled security for tests.
Best of luck!!!
I am using SpringConfig.java instead of SpringConfig.xml. In this class I return:
#Bean(name = "restTemplate")
public RestTemplate restTemplate() {
return new RestTemplate();
}
instead of writing
And I am using #Autowired and I am using SpringJunit4Classrunner. How can I configure SpringConfig.java and my JUnit test file to do proper dependency injection.
If you want to use only annotations, you don't have to use #Autowired in other classes, all configurations have to be in SprinConfig.java. have a look in
SpringConfig Annotations
For Junit Configuration, you have to configure dependencies in pom.xml like this:
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
and your test class will be like this:
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import package.SpringConfig;
public class SimpleTests {
#Test
public void Test() {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(
SpringConfig.class);
//...
}
Place the following annotations above your class delcaration in your unit test:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = SpringConfig.class),