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());