BAEL-5325: Add Spring Security login/logout API with Springdoc (#13027)
* BAEL-5325: Add Spring Security login/logout API with Springdoc * Updated Spring version and using annotations for Basic Auth config * Added tests * Removed unused dependencies * Removed dependencies which were accidentally added. * Removed plugins which were accidentally added. * Removed property which was accidentally added.
This commit is contained in:
parent
10d561451a
commit
f45b2c8659
@ -43,6 +43,7 @@
|
||||
<module>spring-security-web-rest-basic-auth</module>
|
||||
<module>spring-security-web-rest-custom</module>
|
||||
<module>spring-security-web-rest</module>
|
||||
<module>spring-security-web-springdoc</module>
|
||||
<module>spring-security-web-sockets</module>
|
||||
<module>spring-security-web-thymeleaf</module>
|
||||
<module>spring-security-web-x509</module>
|
||||
|
@ -0,0 +1,15 @@
|
||||
## Spring Security Web Springdoc
|
||||
|
||||
This module contains articles about Springdoc with Spring Security
|
||||
|
||||
### Relevant Articles:
|
||||
|
||||
- [Documenting a Spring REST API Using OpenAPI 3.0](https://www.baeldung.com/spring-rest-openapi-documentation)
|
||||
- [Configure JWT Authentication for OpenAPI](https://www.baeldung.com/openapi-jwt-authentication)
|
||||
|
||||
### Running This Project:
|
||||
|
||||
To run the projects use the commands:
|
||||
- `mvn spring-boot:run -Dstart-class=com.baeldung.basicauth.SpringBootSpringdocBasicAuth`
|
||||
- `mvn spring-boot:run -Dstart-class=com.baeldung.formlogin.SpringBootSpringdocFormLogin`
|
||||
|
@ -0,0 +1,54 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>spring-security-web-springdoc</artifactId>
|
||||
<version>0.1-SNAPSHOT</version>
|
||||
<name>spring-security-web-springdoc</name>
|
||||
<packaging>jar</packaging>
|
||||
<description>Spring Security with Springdoc tutorial</description>
|
||||
|
||||
<parent>
|
||||
<groupId>com.baeldung</groupId>
|
||||
<artifactId>parent-boot-2</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<relativePath>../../parent-boot-2</relativePath>
|
||||
</parent>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-security</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springdoc</groupId>
|
||||
<artifactId>springdoc-openapi-ui</artifactId>
|
||||
<version>${springdoc.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springdoc</groupId>
|
||||
<artifactId>springdoc-openapi-security</artifactId>
|
||||
<version>${springdoc.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
<version>${guava.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<properties>
|
||||
<springdoc.version>1.6.13</springdoc.version>
|
||||
</properties>
|
||||
|
||||
</project>
|
@ -0,0 +1,60 @@
|
||||
package com.baeldung.basicauth;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
public class Foo implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = -5422285893276747592L;
|
||||
|
||||
private long id;
|
||||
private String name;
|
||||
|
||||
public Foo(final String name) {
|
||||
super();
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(final long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(final String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + ((name == null) ? 0 : name.hashCode());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object obj) {
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null)
|
||||
return false;
|
||||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
final Foo other = (Foo) obj;
|
||||
if (name == null) {
|
||||
return other.name == null;
|
||||
} else return name.equals(other.name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Foo [name=" + name + "]";
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
package com.baeldung.basicauth;
|
||||
|
||||
import io.swagger.v3.oas.annotations.OpenAPIDefinition;
|
||||
import io.swagger.v3.oas.annotations.info.Info;
|
||||
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.apache.commons.lang3.RandomStringUtils.randomAlphabetic;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
@RestController
|
||||
@OpenAPIDefinition(info = @Info(title = "Foos API", version = "v1"))
|
||||
@SecurityRequirement(name = "basicAuth")
|
||||
@RequestMapping(value = "foos", produces = MediaType.APPLICATION_JSON_VALUE, consumes = MediaType.APPLICATION_JSON_VALUE)
|
||||
public class FooController {
|
||||
|
||||
private static final int STRING_LENGTH = 6;
|
||||
|
||||
@GetMapping(value = "/{id}")
|
||||
public Foo findById(@PathVariable("id") final Long id) {
|
||||
return new Foo(randomAlphabetic(STRING_LENGTH));
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
public List<Foo> findAll() {
|
||||
return Lists.newArrayList(new Foo(randomAlphabetic(STRING_LENGTH)), new Foo(randomAlphabetic(STRING_LENGTH)), new Foo(randomAlphabetic(STRING_LENGTH)));
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
@ResponseStatus(HttpStatus.CREATED)
|
||||
public Foo create(@RequestBody final Foo foo) {
|
||||
return foo;
|
||||
}
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
package com.baeldung.basicauth;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
@SpringBootApplication
|
||||
public class SpringBootSpringdocBasicAuth {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(SpringBootSpringdocBasicAuth.class, args);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
package com.baeldung.basicauth.config;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
|
||||
@Configuration
|
||||
public class PasswordEncoderConfiguration {
|
||||
|
||||
@Bean
|
||||
public PasswordEncoder passwordEncoder() {
|
||||
return new BCryptPasswordEncoder();
|
||||
}
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
package com.baeldung.basicauth.config;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
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.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
public class SecurityConfiguration {
|
||||
|
||||
@Bean
|
||||
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.csrf().disable()
|
||||
.authorizeRequests()
|
||||
.antMatchers("/v3/api-docs/**",
|
||||
"/swagger-ui/**",
|
||||
"/swagger-ui.html").permitAll()
|
||||
.anyRequest().authenticated()
|
||||
.and()
|
||||
.httpBasic();
|
||||
return http.build();
|
||||
}
|
||||
|
||||
@Autowired
|
||||
public void configureGlobal(AuthenticationManagerBuilder auth, PasswordEncoder passwordEncoder) throws Exception {
|
||||
auth.inMemoryAuthentication()
|
||||
.withUser("user")
|
||||
.password(passwordEncoder.encode("password"))
|
||||
.roles("USER");
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
package com.baeldung.basicauth.config;
|
||||
|
||||
import io.swagger.v3.oas.annotations.enums.SecuritySchemeType;
|
||||
import io.swagger.v3.oas.annotations.security.SecurityScheme;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration
|
||||
@SecurityScheme(
|
||||
type = SecuritySchemeType.HTTP,
|
||||
name = "basicAuth",
|
||||
scheme = "basic")
|
||||
public class SpringdocConfig {}
|
@ -0,0 +1,13 @@
|
||||
package com.baeldung.formlogin;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
@SpringBootApplication
|
||||
public class SpringBootSpringdocFormLogin {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(SpringBootSpringdocFormLogin.class, args);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
package com.baeldung.formlogin.config;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
|
||||
@Configuration
|
||||
public class PasswordEncoderConfiguration {
|
||||
|
||||
@Bean
|
||||
public PasswordEncoder passwordEncoder() {
|
||||
return new BCryptPasswordEncoder();
|
||||
}
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
package com.baeldung.formlogin.config;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
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.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
public class SecurityConfiguration {
|
||||
|
||||
@Bean
|
||||
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.csrf().disable()
|
||||
.authorizeRequests()
|
||||
.antMatchers("/v3/api-docs/**",
|
||||
"/swagger-ui/**",
|
||||
"/swagger-ui.html").permitAll()
|
||||
.anyRequest().authenticated()
|
||||
.and()
|
||||
.formLogin()
|
||||
.defaultSuccessUrl("/foos");
|
||||
return http.build();
|
||||
}
|
||||
|
||||
@Autowired
|
||||
public void configureGlobal(AuthenticationManagerBuilder auth, PasswordEncoder passwordEncoder) throws Exception {
|
||||
auth.inMemoryAuthentication()
|
||||
.withUser("user")
|
||||
.password(passwordEncoder.encode("password"))
|
||||
.roles("USER");
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
package com.baeldung.formlogin.controller;
|
||||
|
||||
import com.baeldung.formlogin.model.Foo;
|
||||
import com.google.common.collect.Lists;
|
||||
import io.swagger.v3.oas.annotations.OpenAPIDefinition;
|
||||
import io.swagger.v3.oas.annotations.info.Info;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.apache.commons.lang3.RandomStringUtils.randomAlphabetic;
|
||||
|
||||
@RestController
|
||||
@RequestMapping(value = "foos", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
@OpenAPIDefinition(info = @Info(title = "Foos API", version = "v1"))
|
||||
public class FooController {
|
||||
|
||||
private static final int STRING_LENGTH = 6;
|
||||
|
||||
@GetMapping(value = "/{id}")
|
||||
public Foo findById(@PathVariable("id") final Long id) {
|
||||
return new Foo(randomAlphabetic(STRING_LENGTH));
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
public List<Foo> findAll() {
|
||||
return Lists.newArrayList(new Foo(randomAlphabetic(STRING_LENGTH)), new Foo(randomAlphabetic(STRING_LENGTH)), new Foo(randomAlphabetic(STRING_LENGTH)));
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
@ResponseStatus(HttpStatus.CREATED)
|
||||
public Foo create(@RequestBody final Foo foo) {
|
||||
return foo;
|
||||
}
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
package com.baeldung.formlogin.controller;
|
||||
|
||||
import io.swagger.v3.oas.annotations.OpenAPIDefinition;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.info.Info;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@RestController
|
||||
@OpenAPIDefinition(info = @Info(title = "logout-endpoint"))
|
||||
public class LogoutController {
|
||||
|
||||
@PostMapping("logout")
|
||||
@Operation(description = "End authenticated user session")
|
||||
public void logout() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
@ -0,0 +1,62 @@
|
||||
package com.baeldung.formlogin.model;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
public class Foo implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = -5422285893276747592L;
|
||||
|
||||
private long id;
|
||||
private String name;
|
||||
|
||||
public Foo(final String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public Foo() {
|
||||
}
|
||||
|
||||
public long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(final long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(final String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + ((name == null) ? 0 : name.hashCode());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object obj) {
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null)
|
||||
return false;
|
||||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
final Foo other = (Foo) obj;
|
||||
if (name == null) {
|
||||
return other.name == null;
|
||||
} else return name.equals(other.name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Foo [name=" + name + "]";
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1 @@
|
||||
springdoc.show-login-endpoint=true
|
@ -0,0 +1,64 @@
|
||||
package com.baeldung.basicauth;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.boot.test.web.client.TestRestTemplate;
|
||||
import org.springframework.boot.web.server.LocalServerPort;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
|
||||
class OpenAPIIntegrationTest {
|
||||
|
||||
@LocalServerPort
|
||||
private int port;
|
||||
|
||||
@Autowired
|
||||
private TestRestTemplate restTemplate;
|
||||
|
||||
@Test
|
||||
void whenInvokeSwagger_thenRenderIndexPage() {
|
||||
String response = this.restTemplate.getForObject("http://localhost:" + port + "/swagger-ui/index.html", String.class);
|
||||
|
||||
assertNotNull(response);
|
||||
assertTrue(response.contains("Swagger UI"));
|
||||
assertTrue(response.contains("<div id=\"swagger-ui\"></div>"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenInvokeOpenApi_thenCheckHeaders() {
|
||||
ResponseEntity<String> response = this.restTemplate.getForEntity("http://localhost:" + port + "/v3/api-docs", String.class);
|
||||
|
||||
assertNotNull(response);
|
||||
assertEquals(HttpStatus.OK, response.getStatusCode());
|
||||
assertNotNull(response.getHeaders().get("Content-Type"));
|
||||
assertEquals(1, response.getHeaders().get("Content-Type").size());
|
||||
assertEquals("application/json", response.getHeaders().get("Content-Type").get(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenInvokeOpenApi_thenVerifyOpenApiDoc() {
|
||||
ResponseEntity<String> response = this.restTemplate.getForEntity("http://localhost:" + port + "/v3/api-docs", String.class);
|
||||
|
||||
assertNotNull(response);
|
||||
assertNotNull(response.getBody());
|
||||
assertTrue(response.getBody().contains("\"openapi\":"));
|
||||
assertTrue(response.getBody().contains("Foos API"));
|
||||
assertTrue(response.getBody().contains("\"post\""));
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenInvokeOpenApi_thenCheckSecurityConfig() {
|
||||
ResponseEntity<String> response = this.restTemplate.getForEntity("http://localhost:" + port + "/v3/api-docs", String.class);
|
||||
|
||||
assertNotNull(response);
|
||||
assertNotNull(response.getBody());
|
||||
assertTrue(response.getBody().contains("\"securitySchemes\""));
|
||||
assertTrue(response.getBody().contains("\"type\":\"http\""));
|
||||
assertTrue(response.getBody().contains("\"scheme\":\"basic\""));
|
||||
}
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
package com.baeldung.formlogin;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.boot.test.web.client.TestRestTemplate;
|
||||
import org.springframework.boot.web.server.LocalServerPort;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
|
||||
class OpenAPIIntegrationTest {
|
||||
|
||||
@LocalServerPort
|
||||
private int port;
|
||||
|
||||
@Autowired
|
||||
private TestRestTemplate restTemplate;
|
||||
|
||||
@Test
|
||||
void whenInvokeSwagger_thenRenderIndexPage() {
|
||||
String response = this.restTemplate.getForObject("http://localhost:" + port + "/swagger-ui/index.html", String.class);
|
||||
|
||||
assertNotNull(response);
|
||||
assertTrue(response.contains("Swagger UI"));
|
||||
assertTrue(response.contains("<div id=\"swagger-ui\"></div>"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenInvokeOpenApi_thenCheckHeaders() {
|
||||
ResponseEntity<String> response = this.restTemplate.getForEntity("http://localhost:" + port + "/v3/api-docs", String.class);
|
||||
|
||||
assertNotNull(response);
|
||||
assertEquals(HttpStatus.OK, response.getStatusCode());
|
||||
assertNotNull(response.getHeaders().get("Content-Type"));
|
||||
assertEquals(1, response.getHeaders().get("Content-Type").size());
|
||||
assertEquals("application/json", response.getHeaders().get("Content-Type").get(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenInvokeOpenApi_thenVerifyOpenApiDoc() {
|
||||
ResponseEntity<String> response = this.restTemplate.getForEntity("http://localhost:" + port + "/v3/api-docs", String.class);
|
||||
|
||||
assertNotNull(response);
|
||||
assertNotNull(response.getBody());
|
||||
assertTrue(response.getBody().contains("\"openapi\":"));
|
||||
assertTrue(response.getBody().contains("Foos API"));
|
||||
assertTrue(response.getBody().contains("\"post\""));
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user