BAEL-5534 Configure JWT Authentication for OpenAPI (#12288)
Co-authored-by: Bhaskara Navuluri <bhaskara.navuluri@hpe.com>
This commit is contained in:
+86
@@ -0,0 +1,86 @@
|
||||
package com.baeldung.jwt;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.oauth2.jwt.JwtClaimsSet;
|
||||
import org.springframework.security.oauth2.jwt.JwtEncoder;
|
||||
import org.springframework.security.oauth2.jwt.JwtEncoderParameters;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* REST APIs that contains all the operations that can be performed for authentication like login, registration etc
|
||||
*/
|
||||
@RequestMapping("/api/auth")
|
||||
@RestController
|
||||
@Tag(name = "Authentication", description = "The Authentication API. Contains operations like change password, forgot password, login, logout, etc.")
|
||||
public class AuthenticationApi {
|
||||
|
||||
private final UserDetailsService userDetailsService;
|
||||
private final JwtEncoder encoder;
|
||||
|
||||
public AuthenticationApi(UserDetailsService userDetailsService, JwtEncoder encoder) {
|
||||
this.userDetailsService = userDetailsService;
|
||||
this.encoder = encoder;
|
||||
}
|
||||
|
||||
/**
|
||||
* API to Login
|
||||
*
|
||||
* @param user The login entity that contains username and password
|
||||
* @return Returns the JWT token
|
||||
* @see com.baeldung.jwt.User
|
||||
*/
|
||||
@Operation(summary = "User Authentication", description = "Authenticate the user and return a JWT token if the user is valid.")
|
||||
@io.swagger.v3.oas.annotations.parameters.RequestBody(content = @io.swagger.v3.oas.annotations.media.Content(mediaType = "application/json", examples = @io.swagger.v3.oas.annotations.media.ExampleObject(value = "{\n" + " \"username\": \"jane\",\n"
|
||||
+ " \"password\": \"password\"\n" + "}", summary = "User Authentication Example")))
|
||||
@PostMapping(value = "/login", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public ResponseEntity<String> login(@RequestBody User user) {
|
||||
UserDetails userDetails = userDetailsService.loadUserByUsername(user.getUsername());
|
||||
if (user.getPassword().equalsIgnoreCase(userDetails.getPassword())) {
|
||||
String token = generateToken(userDetails);
|
||||
HttpHeaders httpHeaders = new HttpHeaders();
|
||||
httpHeaders.set("X-AUTH-TOKEN", token);
|
||||
return ResponseEntity.ok().headers(httpHeaders).contentType(MediaType.APPLICATION_JSON).body("{\"token\":\"" + token + "\"}");
|
||||
} else {
|
||||
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).contentType(MediaType.APPLICATION_JSON).body("Invalid username or password");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the JWT token with claims
|
||||
*
|
||||
* @param userDetails The user details
|
||||
* @return Returns the JWT token
|
||||
*/
|
||||
private String generateToken(UserDetails userDetails) {
|
||||
Instant now = Instant.now();
|
||||
long expiry = 36000L;
|
||||
// @formatter:off
|
||||
String scope = userDetails.getAuthorities().stream()
|
||||
.map(GrantedAuthority::getAuthority)
|
||||
.collect(Collectors.joining(" "));
|
||||
JwtClaimsSet claims = JwtClaimsSet.builder()
|
||||
.issuer("self")
|
||||
.issuedAt(now)
|
||||
.expiresAt(now.plusSeconds(expiry))
|
||||
.subject(userDetails.getUsername())
|
||||
.claim("scope", scope)
|
||||
.build();
|
||||
// @formatter:on
|
||||
return this.encoder.encode(JwtEncoderParameters.from(claims)).getTokenValue();
|
||||
}
|
||||
|
||||
}
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
package com.baeldung.jwt;
|
||||
|
||||
import io.swagger.v3.oas.annotations.OpenAPIDefinition;
|
||||
import io.swagger.v3.oas.annotations.info.Contact;
|
||||
import io.swagger.v3.oas.annotations.info.Info;
|
||||
import io.swagger.v3.oas.annotations.info.License;
|
||||
import io.swagger.v3.oas.annotations.servers.Server;
|
||||
import io.swagger.v3.oas.models.Components;
|
||||
import io.swagger.v3.oas.models.OpenAPI;
|
||||
import io.swagger.v3.oas.models.security.SecurityRequirement;
|
||||
import io.swagger.v3.oas.models.security.SecurityScheme;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration
|
||||
//@formatter:off
|
||||
@OpenAPIDefinition(
|
||||
info = @Info(title = "User API", version = "${api.version}",
|
||||
contact = @Contact(name = "Baeldung", email = "user-apis@baeldung.com", url = "https://www.baeldung.com"),
|
||||
license = @License(name = "Apache 2.0", url = "https://www.apache.org/licenses/LICENSE-2.0"), termsOfService = "${tos.uri}",
|
||||
description = "${api.description}"),
|
||||
servers = {
|
||||
@Server(url = "http://localhost:8080", description = "Development"),
|
||||
@Server(url = "${api.server.url}", description = "Production")})
|
||||
//@formatter:on
|
||||
public class OpenAPI30Configuration {
|
||||
|
||||
/**
|
||||
* Configure the OpenAPI components.
|
||||
*
|
||||
* @return Returns fully configure OpenAPI object
|
||||
* @see OpenAPI
|
||||
*/
|
||||
@Bean
|
||||
public OpenAPI customizeOpenAPI() {
|
||||
//@formatter:off
|
||||
final String securitySchemeName = "bearerAuth";
|
||||
return new OpenAPI()
|
||||
.addSecurityItem(new SecurityRequirement()
|
||||
.addList(securitySchemeName))
|
||||
.components(new Components()
|
||||
.addSecuritySchemes(securitySchemeName, new SecurityScheme()
|
||||
.name(securitySchemeName)
|
||||
.type(SecurityScheme.Type.HTTP)
|
||||
.scheme("bearer")
|
||||
.description(
|
||||
"Provide the JWT token. JWT token can be obtained from the Login API. For testing, use the credentials <strong>john/password</strong>")
|
||||
.bearerFormat("JWT")));
|
||||
//@formatter:on
|
||||
|
||||
}
|
||||
}
|
||||
+124
@@ -0,0 +1,124 @@
|
||||
package com.baeldung.jwt;
|
||||
|
||||
import com.nimbusds.jose.jwk.JWK;
|
||||
import com.nimbusds.jose.jwk.JWKSet;
|
||||
import com.nimbusds.jose.jwk.RSAKey;
|
||||
import com.nimbusds.jose.jwk.source.ImmutableJWKSet;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
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.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer;
|
||||
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.oauth2.jwt.JwtDecoder;
|
||||
import org.springframework.security.oauth2.jwt.JwtEncoder;
|
||||
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
|
||||
import org.springframework.security.oauth2.jwt.NimbusJwtEncoder;
|
||||
import org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationEntryPoint;
|
||||
import org.springframework.security.oauth2.server.resource.web.access.BearerTokenAccessDeniedHandler;
|
||||
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
|
||||
import java.security.interfaces.RSAPrivateKey;
|
||||
import java.security.interfaces.RSAPublicKey;
|
||||
|
||||
import org.springframework.security.core.userdetails.User;
|
||||
|
||||
/**
|
||||
* This class is inspired from
|
||||
* https://github.com/spring-projects/spring-security-samples/blob/5.7.x/servlet/spring-boot/java/jwt/login/src/main/java/example/RestConfig.java
|
||||
*/
|
||||
@EnableWebSecurity
|
||||
@Configuration
|
||||
public class SecurityConfiguration {
|
||||
|
||||
@Value("${jwt.public.key}")
|
||||
RSAPublicKey publicKey;
|
||||
|
||||
@Value("${jwt.private.key}")
|
||||
RSAPrivateKey privateKey;
|
||||
|
||||
/**
|
||||
* This bean is used to configure the JWT token. Configure the URLs that should not be protected by the JWT token.
|
||||
*
|
||||
* @param http the HttpSecurity object
|
||||
* @return the HttpSecurity object
|
||||
* @throws Exception if an error occurs
|
||||
*/
|
||||
@Bean
|
||||
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||
//@formatter:off
|
||||
return http
|
||||
.authorizeHttpRequests(authorizeRequests -> authorizeRequests
|
||||
.antMatchers("/api/auth/**", "/swagger-ui.html", "/swagger-ui/**", "/v3/api-docs/**", "/webjars/**",
|
||||
"/swagger-ui/index.html")
|
||||
.permitAll()
|
||||
.anyRequest()
|
||||
.authenticated())
|
||||
.cors().disable()
|
||||
.csrf().disable()
|
||||
.formLogin().disable()
|
||||
.httpBasic().disable()
|
||||
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
|
||||
.and()
|
||||
.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt)
|
||||
.exceptionHandling(exceptions -> exceptions
|
||||
.authenticationEntryPoint(new BearerTokenAuthenticationEntryPoint())
|
||||
.accessDeniedHandler(new BearerTokenAccessDeniedHandler())
|
||||
.and())
|
||||
.build();
|
||||
//@formatter:on
|
||||
}
|
||||
|
||||
/**
|
||||
* For demonstration/example, we use the InMemoryUserDetailsManager.
|
||||
*
|
||||
* @return Returns the UserDetailsService with pre-configure credentials.
|
||||
* @see InMemoryUserDetailsManager
|
||||
*/
|
||||
@Bean
|
||||
UserDetailsService allUsers() {
|
||||
// @formatter:off
|
||||
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
|
||||
manager
|
||||
.createUser(User.builder()
|
||||
.passwordEncoder(password -> password)
|
||||
.username("john")
|
||||
.password("password")
|
||||
.authorities("USER")
|
||||
.roles("USER").build());
|
||||
manager
|
||||
.createUser(User.builder()
|
||||
.passwordEncoder(password -> password)
|
||||
.username("jane")
|
||||
.password("password")
|
||||
.authorities("USER")
|
||||
.roles("USER").build());
|
||||
return manager;
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
/**
|
||||
* This bean is used to decode the JWT token.
|
||||
*
|
||||
* @return Returns the JwtDecoder bean to decode JWT tokens.
|
||||
*/
|
||||
@Bean
|
||||
JwtDecoder jwtDecoder() {
|
||||
return NimbusJwtDecoder.withPublicKey(this.publicKey).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* This bean is used to encode the JWT token.
|
||||
*
|
||||
* @return Returns the JwtEncoder bean to encode JWT tokens.
|
||||
*/
|
||||
@Bean
|
||||
JwtEncoder jwtEncoder() {
|
||||
JWK jwk = new RSAKey.Builder(this.publicKey).privateKey(this.privateKey).build();
|
||||
return new NimbusJwtEncoder(new ImmutableJWKSet<>(new JWKSet(jwk)));
|
||||
}
|
||||
}
|
||||
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
package com.baeldung.jwt;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
@SpringBootApplication
|
||||
public class SecurityTokenApplication {
|
||||
|
||||
/**
|
||||
* The bootstrap method
|
||||
* @param args
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(SecurityTokenApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package com.baeldung.jwt;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
public class User implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 3317686311392412458L;
|
||||
private String username;
|
||||
private String password;
|
||||
private String role;
|
||||
private String email;
|
||||
|
||||
public User(String username, String password, String role) {
|
||||
this.username = username;
|
||||
this.password = password;
|
||||
this.role = role;
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public void setUsername(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
public void setPassword(String password) {
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
public String getRole() {
|
||||
return role;
|
||||
}
|
||||
|
||||
public void setRole(String role) {
|
||||
this.role = role;
|
||||
}
|
||||
|
||||
public String getEmail() {
|
||||
return email;
|
||||
}
|
||||
|
||||
public void setEmail(String email) {
|
||||
this.email = email;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "User [username=" + username + ", password=" + password + ", role=" + role + "]";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package com.baeldung.jwt;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.text.MessageFormat;
|
||||
|
||||
/**
|
||||
* REST APIs that contain all the operations that can be performed on a User
|
||||
*/
|
||||
@RequestMapping("/api/user")
|
||||
@RestController
|
||||
@Tag(name = "User", description = "The User API. Contains all the operations that can be performed on a user.")
|
||||
public class UserApi {
|
||||
|
||||
private final UserDetailsService userDetailsService;
|
||||
|
||||
public UserApi(UserDetailsService userDetailsService) {
|
||||
this.userDetailsService = userDetailsService;
|
||||
}
|
||||
|
||||
/**
|
||||
* API to get the current user. Returns the user details for the provided JWT token
|
||||
* @param authentication The authentication object that contains the JWT token
|
||||
* @return Returns the user details for the provided JWT token
|
||||
*/
|
||||
@Operation(summary = "Get user details", description = "Get the user details. The operation returns the details of the user that is associated " + "with the provided JWT token.")
|
||||
@GetMapping
|
||||
public UserDetails getUser(Authentication authentication) {
|
||||
return userDetailsService.loadUserByUsername(authentication.getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* API to delete the current user.
|
||||
* @param authentication The authentication object that contains the JWT token
|
||||
* @return Returns a success message on deletion of the user
|
||||
*/
|
||||
@Operation(summary = "Delete user details", description = "Delete user details. The operation deletes the details of the user that is " + "associated with the provided JWT token.")
|
||||
@DeleteMapping
|
||||
public String deleteUser(Authentication authentication) {
|
||||
return MessageFormat.format("User {0} deleted successfully", authentication.getName());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user