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();
}
Related
I wrote a spring boot project.
It has three files.
Appconfig.java
package config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
#Configuration
#EnableWebMvc
#ComponentScan
(basePackages = {"controller"})
public class AppConfig {
}
ServletInitilizer.java
package config;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
public class ServletInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
#Override
protected Class<?>[] getRootConfigClasses() {
return new Class<?>[0];
}
#Override
protected Class<?>[] getServletConfigClasses() {
return new Class<?>[]{AppConfig.class};
}
#Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
}
HelloController.java
package controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
public class HelloController {
#RequestMapping("hi")
#ResponseBody
public String hi() {
return "Hello, world.";
}
}
When I try to run it, it has error "No mapping found for HTTP request with URI [/SpringC1_01/] in DispatcherServlet with name 'dispatcher'".
Is this because server didn't find the controller or other reason? Thx.
Yes. i suspect two issues in the code.
#SpringBootApplication annotation is missing in AppConfig.
#RestController annotation is missing in HelloController.
Most of all you are missing a couple of things here.
Main class which contains public static void main and this class should be annotated with #SpringBootApplication
HelloController should be annotated with #RestController
At method level it should definitely point to some HTTP method in your case perhaprs it is Get mapping, so add #GetMapping annotation arround the method.
Move RequestMapping annotation from method level and add it to HelloController class.
I know this question has been posted before, but I couldn't resolve it from that post. I get the error that "SpringApplicationConfiguration cannot be resolved to a type" in the following code:
package com.caveofprogramming.tests;
import static org.junit.Assert.assertNotNull;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import com.caveofprogramming.App;
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(App.class)
#WebAppConfiguration
public class StatusTest {
#Test
public void testDummy() {
long value = 7l;
assertNotNull("Value should not be null", value);
}
}
This has the following dependency in the pom.xml file:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>2.1.9.RELEASE</version><!--$NO-MVN-MAN-VER$-->
</dependency>
What can I do to get rid of this error but not cause any other issues? Thanks.
Here below is App.java:
package com.caveofprogramming;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
import org.springframework.context.annotation.Bean;
import org.springframework.web.servlet.view.UrlBasedViewResolver;
import org.springframework.web.servlet.view.tiles3.TilesConfigurer;
import org.springframework.web.servlet.view.tiles3.TilesView;
#SpringBootApplication
public class App extends SpringBootServletInitializer {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder
application) {
return application.sources(App.class);
}
#Bean
public TilesConfigurer tilesConfigurer() {
TilesConfigurer tilesConfigurer = new TilesConfigurer();
String[] defs = {"/WEB-INF/tiles.xml"};
tilesConfigurer.setDefinitions(defs);
return tilesConfigurer;
}
#Bean
public UrlBasedViewResolver tilesViewResolver() {
UrlBasedViewResolver tilesViewResolver = new UrlBasedViewResolver();
tilesViewResolver.setViewClass(TilesView.class);
return tilesViewResolver;
}
}
I believe a configuration like this will get you going
#RunWith(SpringRunner.class)
#ComponentScan(basePackages = {"com.caveofprogramming"})
#SpringBootTest
#AutoConfigureMockMvc
I assume there's a lot more to your test (or there intends to be) than what you've listed. If you're truly going to just make a simple test like this, run with the Mockito runner, and you don't need the rest of that nonsense.
#RunWith(MockitoJUnitRunner.class)
Note that this assumes you're attempting to perform a test of your controllers, based on the fact that you have annotated your test with #WebAppConfiguration
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
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!!!