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!!!
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 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())
}
}
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 am trying to secure a REST endpoint via the #Secured annotation of Spring Security. My main application (Spring Boot App) with the security config and the rest controller are in different packages and project.
Main app package: com.myapp.api.web
Rest controller packge: com.myapp.api.rest
Mainapp:
#SpringBootApplication
#ComponentScan(basePackages = "com.myapp.api")
#EntityScan("com.myapp.api")
#RestController
public class ApiApplication extends SpringBootServletInitializer
{
public static void main(String[] args)
{
SpringApplication.run(ApiApplication.class, args);
}
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application)
{
return application.sources(ApiApplication.class);
}
}
Security Config:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true, proxyTargetClass = true,
securedEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter
{
private static final String USERS_CONFIG_FILE_NAME = "users.yml";
#Override
protected void configure(HttpSecurity http) throws Exception
{
http
.httpBasic()
.and()
.csrf().disable();
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
#Bean
public PasswordEncoder passwordEncoder()
{
return new BCryptPasswordEncoder();
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth,
InMemoryUserDetailsManager inMemoryUserDetailsManager, PasswordEncoder passwordEncoder) throws Exception
{
auth.userDetailsService(inMemoryUserDetailsManager).passwordEncoder(passwordEncoder);
}
#Bean
public InMemoryUserDetailsManager inMemoryUserDetailsManager() throws IOException
{
return new InMemoryUserDetailsManager(
PropertiesLoaderUtils.loadAllProperties(USERS_CONFIG_FILE_NAME, getClass().getClassLoader()));
}
}
Rest controller:
#RestController
public class RestController
{
private final RestService service;
#PostMapping("/rest/v1")
#Secured({"ROLE_ADMIN"})
public List<String> getStates(#RequestBody List<String> Ids)
{
...
}
My rest endpoint is working as long as I am not setting securedEnabled = true. After setting it true I am getting a 404 Not Found as respond message. I've already debugged it and found out that the Spring Security somewhen stops in the filter chain and that the request never reaches the controller.
As far as I tested it, as long as the rest controller is in a different project this error will occure. After moving it to the same project it is working as it should.
Is there something missing in my Securityconfig or what could the problem be?
I am able to get values by your code , I have only changed Password Encoder to default and changed inMemoryAuthentication .
I did it as I don't have your file "users.yml" If you can share a sample , we will look in it , But below is my code. I kept all logic in 2 files just to verify.
Configuration Class
package com.myapp.api.web.Api;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.password.PasswordEncoder;
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true, proxyTargetClass = true,
securedEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter
{
private static final String USERS_CONFIG_FILE_NAME = "users.yml";
#Override
protected void configure(HttpSecurity http) throws Exception
{
http
.httpBasic()
.and()
.csrf().disable();
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
#Bean
public PasswordEncoder passwordEncoder() {
return new PasswordEncoder() {
#Override
public String encode(CharSequence rawPassword) {
return rawPassword.toString();
}
#Override
public boolean matches(CharSequence rawPassword, String encodedPassword) {
return rawPassword.toString().equals(encodedPassword);
}
};
}
#Override
protected void configure(AuthenticationManagerBuilder builder) throws Exception {
builder.inMemoryAuthentication()
.withUser("user").password("user").roles("USER")
.and().withUser("admin").password("admin").roles("ADMIN");
}
/*
#Bean
public InMemoryUserDetailsManager inMemoryUserDetailsManager() throws IOException
{
return new InMemoryUserDetailsManager(
PropertiesLoaderUtils.loadAllProperties(USERS_CONFIG_FILE_NAME, getClass().getClassLoader()));
}*/
}
Main Class
package com.myapp.api.web.Api;
import java.util.List;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.security.access.annotation.Secured;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
#ComponentScan(basePackages = "com.myapp.api")
#RestController
#SpringBootApplication
public class ApiApplication {
public static void main(String[] args) {
SpringApplication.run(ApiApplication.class, args);
}
#PostMapping("/rest/v1")
#Secured({"ROLE_ADMIN"})
public List<String> getStates(#RequestBody List<String> Ids)
{
return Ids;
// ...
}
}
I have search a lot but I did not find answer to my question, So I am posting my question here. Please look and suggest me the solution where I am mistaken.
I have created spring boot web mvc project with thymeleaf support using Spring Tool Suite(STS). When I run it give me "Whitelabel Error Page" page. Which means mapping not found.
Efforts:
WebConfig.java
package com.springthymeleaf.config;
import org.springframework.boot.context.embedded.ServletRegistrationBean;
import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ReloadableResourceBundleMessageSource;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.thymeleaf.spring4.SpringTemplateEngine;
import org.thymeleaf.spring4.view.ThymeleafViewResolver;
import org.thymeleaf.templateresolver.ServletContextTemplateResolver;
#Configuration
#ComponentScan("com.springthymeleaf")
#EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {
#Bean
ServletRegistrationBean servletRegistration(){
ServletRegistrationBean registrationBean = new ServletRegistrationBean();
registrationBean.addUrlMappings("/console/*");
return registrationBean;
}
//start Thymeleaf specific configuration
#Bean(name ="templateResolver")
public ServletContextTemplateResolver getTemplateResolver() {
ServletContextTemplateResolver templateResolver = new ServletContextTemplateResolver();
// templateResolver.setPrefix("/templates/");
templateResolver.setSuffix(".html");
templateResolver.setTemplateMode("XHTML");
return templateResolver;
}
#Bean(name ="templateEngine")
public SpringTemplateEngine getTemplateEngine() {
SpringTemplateEngine templateEngine = new SpringTemplateEngine();
templateEngine.setTemplateResolver(getTemplateResolver());
return templateEngine;
}
#Bean(name="viewResolver")
public ThymeleafViewResolver getViewResolver(){
ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();
viewResolver.setTemplateEngine(getTemplateEngine());
return viewResolver;
}
//end Thymeleaf specific configuration
#Bean(name ="messageSource")
public MessageSource getMessageSource() {
ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
messageSource.setBasename("/WEB-INF/i18/thymeleafResource");
messageSource.setDefaultEncoding("UTF-8");
return messageSource;
}
}
SecurityConfiguration.java
package com.springthymeleaf.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
#Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.authorizeRequests().antMatchers("/").permitAll();
}
}
ServletInitializer.java
package com.springthymeleaf;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.context.web.SpringBootServletInitializer;
public class ServletInitializer extends SpringBootServletInitializer {
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(SpringThymeLeafApplication.class);
}
}
SpringThymeLeafApplication.java
package com.springthymeleaf;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
#SpringBootApplication
public class SpringThymeLeafApplication {
public static void main(String[] args) {
SpringApplication.run(SpringThymeLeafApplication.class, args);
}
}
IndexController.java
package com.springthymeleaf.controllers;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
#Controller
public class IndexController {
#RequestMapping("/")
public String index(){
return "index";
}
}
I have created index.html file in resources/templates folder. Still I am getting that error. I have searched a lot on web, but did not get clue. Please somebody help me.
Actually Spring Boot configures Thymeleaf out of the box. It should work with the following setup:
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter
{
#Override
protected void configure(HttpSecurity http) throws Exception
{
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin().loginPage("/login").defaultSuccessUrl("/").permitAll() // http://docs.spring.io/spring-security/site/docs/4.0.3.RELEASE/reference/htmlsingle/#jc-form
.and()
.logout().permitAll(); // http://docs.spring.io/spring-security/site/docs/4.0.3.RELEASE/reference/htmlsingle/#jc-logout
}
#Override
public void configure(WebSecurity web) throws Exception
{
web
.ignoring()
.antMatchers("/resources/**"/*, ... */);
}
}
#Controller
public class LoginController
{
#RequestMapping("/login")
static String login(Model model)
{
return "login";
}
}
Spring Boot already configures Thymeleaf for you, so no need to configure that manually. Remove all Thymeleaf related configuration, also remove #EnableWebMvc as that interferes with the Spring Boot auto configuration. The #ComponentScan is also redundant.
Spring Boot also registered a MessageSource for you so no need to configure that. Not sure what the servlet registration is you do but that is the only thing you need.
Also I suggest to remove your controller and use a view controller which you can configure in your WebConfig class. Saves you a controller.
#Configuration
public class WebConfig extends WebMvcConfigurerAdapter {
#Bean
ServletRegistrationBean servletRegistration(){
ServletRegistrationBean registrationBean = new ServletRegistrationBean();
registrationBean.addUrlMappings("/console/*");
return registrationBean;
}
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("index");
}
}
To let the auto configured message source pickup your custom bundles add the following to src/main/resources/application.properties.
spring.messages.basename=/WEB-INF/i18/thymeleafResource
I would also suggest to simply let the SpringThymeLeafApplication extend the SpringBootServletInitializer.
#SpringBootApplication
public class SpringThymeLeafApplication extends SpringBootServletInitializer {
public static void main(String[] args) {
SpringApplication.run(SpringThymeLeafApplication.class, args);
}
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(SpringThymeLeafApplication.class);
}
}
Also make sure that your templates are in src/main/resources/templates and not in src/main/resources/resources/templates else those will not be found.
Spring boot does all the automatic configuration when you add the thymeleaf dependency. then you should do the following.
Remove all the thymeleaf configuration you have on your WebConfig.java
Make sure you have the following dependency your pom.xml if you are using Maven, otherwise check the spring website for the equivalent if you are using gradle:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
thirdly you make sure that you scanning where your controllers are, add the following on your SpringThymeLeafApplication.java:
#ComponentScan(basePackages = "your.path.to.controllers")
Finally you have to add your .html files to resources/templates