diff --git a/library-api-authentication-server/pom.xml b/library-api-authentication-server/pom.xml
index 392b06e..19d0842 100644
--- a/library-api-authentication-server/pom.xml
+++ b/library-api-authentication-server/pom.xml
@@ -66,6 +66,14 @@
${jjwt.version}
+
+
+ org.springdoc
+ springdoc-openapi-starter-webmvc-ui
+ 2.6.0
+
+
+
org.springframework.boot
spring-boot-starter-test
diff --git a/library-api-authentication-server/src/main/java/dev/earlspilner/auth/dto/AuthDto.java b/library-api-authentication-server/src/main/java/dev/earlspilner/auth/dto/AuthDto.java
index 65dc334..fb7ec13 100644
--- a/library-api-authentication-server/src/main/java/dev/earlspilner/auth/dto/AuthDto.java
+++ b/library-api-authentication-server/src/main/java/dev/earlspilner/auth/dto/AuthDto.java
@@ -1,9 +1,18 @@
package dev.earlspilner.auth.dto;
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotNull;
+
/**
* @author Alexander Dudkin
*/
+@Schema(description = "Authentication request data")
public record AuthDto(
+ @NotNull
+ @Schema(description = "User's username", example = "thisdudkin", requiredMode = Schema.RequiredMode.REQUIRED)
String username,
+
+ @NotNull
+ @Schema(description = "User's password", example = "password", requiredMode = Schema.RequiredMode.REQUIRED)
String password
) { }
diff --git a/library-api-authentication-server/src/main/java/dev/earlspilner/auth/dto/Tokens.java b/library-api-authentication-server/src/main/java/dev/earlspilner/auth/dto/Tokens.java
index c3508dc..00a55f3 100644
--- a/library-api-authentication-server/src/main/java/dev/earlspilner/auth/dto/Tokens.java
+++ b/library-api-authentication-server/src/main/java/dev/earlspilner/auth/dto/Tokens.java
@@ -1,11 +1,18 @@
package dev.earlspilner.auth.dto;
+import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
/**
* @author Alexander Dudkin
*/
+@Schema(description = "Access and refresh tokens")
public record Tokens(
- @NotNull String accessToken,
- @NotNull String refreshToken
+ @NotNull
+ @Schema(description = "Access token", example = "eyJhbGciOiJIUzI1...", requiredMode = Schema.RequiredMode.REQUIRED)
+ String accessToken,
+
+ @NotNull
+ @Schema(description = "Refresh token", example = "dXNlci1yZWZyZXNoLXRva2Vu..", requiredMode = Schema.RequiredMode.REQUIRED)
+ String refreshToken
) { }
diff --git a/library-api-authentication-server/src/main/java/dev/earlspilner/auth/rest/controller/AuthenticationServerRestController.java b/library-api-authentication-server/src/main/java/dev/earlspilner/auth/rest/controller/AuthenticationServerRestController.java
index 11349ba..4a16305 100644
--- a/library-api-authentication-server/src/main/java/dev/earlspilner/auth/rest/controller/AuthenticationServerRestController.java
+++ b/library-api-authentication-server/src/main/java/dev/earlspilner/auth/rest/controller/AuthenticationServerRestController.java
@@ -3,6 +3,11 @@
import dev.earlspilner.auth.dto.AuthDto;
import dev.earlspilner.auth.dto.Tokens;
import dev.earlspilner.auth.service.AuthenticationServer;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.media.Content;
+import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
+import io.swagger.v3.oas.annotations.responses.ApiResponses;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
@@ -23,12 +28,32 @@ public AuthenticationServerRestController(AuthenticationServer authenticationSer
}
@Override
+ @Operation(summary = "Login user and get tokens", description = "Authenticates the user and returns access and refresh tokens.")
+ @ApiResponses(value = {
+ @ApiResponse(responseCode = "201", description = "User logged in successfully",
+ content = @Content(mediaType = "application/json",
+ schema = @Schema(implementation = Tokens.class))),
+ @ApiResponse(responseCode = "400", description = "Bad credentials",
+ content = @Content),
+ })
@PostMapping("/login")
- public ResponseEntity login(@RequestBody AuthDto authDto) {
+ public ResponseEntity login(@io.swagger.v3.oas.annotations.parameters.RequestBody(
+ description = "Login credentials",
+ required = true,
+ content = @Content(schema = @Schema(implementation = AuthDto.class)))
+ @RequestBody AuthDto authDto) {
return new ResponseEntity<>(authenticationServer.authenticate(authDto), HttpStatus.CREATED);
}
@Override
+ @Operation(summary = "Refresh tokens", description = "Generates new access and refresh tokens using the provided refresh token.")
+ @ApiResponses(value = {
+ @ApiResponse(responseCode = "201", description = "Tokens refreshed successfully",
+ content = @Content(mediaType = "application/json",
+ schema = @Schema(implementation = Tokens.class))),
+ @ApiResponse(responseCode = "400", description = "Invalid refresh token",
+ content = @Content)
+ })
@PostMapping("/refresh")
public ResponseEntity refresh(@RequestParam String refreshToken) {
return new ResponseEntity<>(authenticationServer.refresh(refreshToken), HttpStatus.CREATED);
diff --git a/library-api-authentication-server/src/main/java/dev/earlspilner/auth/security/JwtTokenProvider.java b/library-api-authentication-server/src/main/java/dev/earlspilner/auth/security/JwtTokenProvider.java
index 123da5e..97f155b 100644
--- a/library-api-authentication-server/src/main/java/dev/earlspilner/auth/security/JwtTokenProvider.java
+++ b/library-api-authentication-server/src/main/java/dev/earlspilner/auth/security/JwtTokenProvider.java
@@ -18,6 +18,7 @@
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Service;
+import javax.crypto.SecretKey;
import java.security.Key;
import java.util.Base64;
import java.util.Date;
@@ -81,7 +82,7 @@ public Authentication getAuthentication(String token) {
}
public String getUsername(String token) {
- return Jwts.parser().setSigningKey(key).build().parseSignedClaims(token).getPayload().getSubject();
+ return Jwts.parser().verifyWith((SecretKey) key).build().parseSignedClaims(token).getPayload().getSubject();
}
public String resolveToken(HttpServletRequest req) {
@@ -94,7 +95,7 @@ public String resolveToken(HttpServletRequest req) {
public boolean validateToken(String token) {
try {
- Jwts.parser().setSigningKey(key).build().parseSignedClaims(token);
+ Jwts.parser().verifyWith((SecretKey) key).build().parseSignedClaims(token);
return true;
} catch (JwtException | IllegalArgumentException e) {
throw new CustomJwtException("Expired or invalid JWT token", HttpStatus.INTERNAL_SERVER_ERROR);
diff --git a/library-api-authentication-server/src/main/java/dev/earlspilner/auth/security/WebSecurityConfig.java b/library-api-authentication-server/src/main/java/dev/earlspilner/auth/security/WebSecurityConfig.java
index 3315adb..780f95b 100644
--- a/library-api-authentication-server/src/main/java/dev/earlspilner/auth/security/WebSecurityConfig.java
+++ b/library-api-authentication-server/src/main/java/dev/earlspilner/auth/security/WebSecurityConfig.java
@@ -40,6 +40,10 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests((authz) -> authz
.requestMatchers("/auth/login").permitAll()
.requestMatchers("/auth/refresh").permitAll()
+ .requestMatchers("/swagger-ui/**", "/v3/api-docs/**").permitAll()
+ .requestMatchers("/api-docs/openapi.yml").permitAll()
+ .requestMatchers("/swagger-resources/**").permitAll()
+ .requestMatchers("/webjars/**").permitAll()
// Disallow everything else...
.anyRequest().authenticated());