From 0aeaac17a4ad7bd6a1bd91b78f30ceced320bec7 Mon Sep 17 00:00:00 2001 From: siujamo Date: Mon, 29 Jun 2026 09:38:12 +0800 Subject: [PATCH] feat: custom 401/403 error handling for unauthenticated requests Replace default Spring Security 403 responses with custom 401 (not logged in) and 403 (insufficient permissions) JSON error responses via exception handling and global exception handler. Also bump API version to 1.4.0 and add Swagger annotations for the GitHub webhook controller. --- build.gradle.kts | 1 + .../config/OpenApiConfiguration.java | 4 ++-- .../config/SecurityConfig.java | 16 ++++++++++++++ .../controller/GitHubWebhookController.java | 4 ++++ .../controller/GlobalExceptionHandler.java | 21 +++++++++++++++++++ 5 files changed, 44 insertions(+), 2 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index a98947a..8e62389 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -65,6 +65,7 @@ dependencies { tasks.processResources { filesMatching("application.yaml") { + println("appVersion = ${artefactVersion}, channel = ${buildChannel}, vendor = ${vendor}") expand( "appVersion" to artefactVersion, "channel" to buildChannel, diff --git a/src/main/java/com/onixbyte/deltaforceguide/config/OpenApiConfiguration.java b/src/main/java/com/onixbyte/deltaforceguide/config/OpenApiConfiguration.java index 9f47fe1..e86faaf 100644 --- a/src/main/java/com/onixbyte/deltaforceguide/config/OpenApiConfiguration.java +++ b/src/main/java/com/onixbyte/deltaforceguide/config/OpenApiConfiguration.java @@ -10,14 +10,14 @@ import org.springframework.context.annotation.Configuration; info = @Info( title = "Delta Force Guide Server", description = "API for managing Delta Force game firearm builds", - version = "1.3.4", + version = "1.4.0", contact = @Contact( name = "Zihlu Wang", email = "zihlu.wang@onixbyte.com" ), license = @License( name = "MIT", - url = "https://git.onixbyte.cn/onixbyte/delta-force-guide-server/-/raw/main/LICENCE" + url = "https://onixbyte.dev/onixbyte/delta-force-guide-server/raw/branch/main/LICENCE" ) ) ) diff --git a/src/main/java/com/onixbyte/deltaforceguide/config/SecurityConfig.java b/src/main/java/com/onixbyte/deltaforceguide/config/SecurityConfig.java index b32a691..7d75f6a 100644 --- a/src/main/java/com/onixbyte/deltaforceguide/config/SecurityConfig.java +++ b/src/main/java/com/onixbyte/deltaforceguide/config/SecurityConfig.java @@ -7,6 +7,8 @@ import com.onixbyte.deltaforceguide.filter.TokenAuthenticationFilter; import com.onixbyte.deltaforceguide.properties.CookieProperties; import com.onixbyte.deltaforceguide.properties.TokenProperties; import com.onixbyte.deltaforceguide.security.provider.UsernamePasswordAuthenticationProvider; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -19,6 +21,8 @@ import org.springframework.security.config.annotation.web.configurers.AbstractHt import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; +import com.onixbyte.deltaforceguide.exeption.BizException; +import org.springframework.http.HttpStatus; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.access.ExceptionTranslationFilter; import org.springframework.web.cors.CorsConfigurationSource; @@ -34,6 +38,8 @@ import org.springframework.web.cors.CorsConfigurationSource; @EnableConfigurationProperties({TokenProperties.class, CookieProperties.class}) public class SecurityConfig { + private static final Logger log = LoggerFactory.getLogger(SecurityConfig.class); + /** * Configures the HTTP security filter chain including endpoint authorisation and JWT filter. * @@ -55,6 +61,16 @@ public class SecurityConfig { .authorizeHttpRequests((customiser) -> customiser .anyRequest().permitAll() ) + .exceptionHandling(customiser -> customiser + .authenticationEntryPoint((request, response, authException) -> { + log.error("Unauthenticated request: {}", request, authException); + throw new BizException(HttpStatus.UNAUTHORIZED, "请先登录"); + }) + .accessDeniedHandler((request, response, accessDeniedException) -> { + log.error("Denied request: {}", request, accessDeniedException); + throw new BizException(HttpStatus.FORBIDDEN, "权限不足"); + }) + ) .addFilterAfter(tokenAuthenticationFilter, ExceptionTranslationFilter.class) .build(); } diff --git a/src/main/java/com/onixbyte/deltaforceguide/controller/GitHubWebhookController.java b/src/main/java/com/onixbyte/deltaforceguide/controller/GitHubWebhookController.java index 580e1f9..8b6f3b6 100644 --- a/src/main/java/com/onixbyte/deltaforceguide/controller/GitHubWebhookController.java +++ b/src/main/java/com/onixbyte/deltaforceguide/controller/GitHubWebhookController.java @@ -3,6 +3,8 @@ package com.onixbyte.deltaforceguide.controller; import com.onixbyte.deltaforceguide.domain.dto.GitHubIssueRequest; import com.onixbyte.deltaforceguide.service.WebhookService; import com.onixbyte.deltaforceguide.shared.GitHubWebhookHeader; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.ResponseEntity; @@ -12,6 +14,7 @@ import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +@Tag(name = "GitHub WebHook") @RestController @RequestMapping("/webhooks/github") public class GitHubWebhookController { @@ -24,6 +27,7 @@ public class GitHubWebhookController { this.webhookService = webhookService; } + @Operation(description = "GitHub WebHook 接口") @PostMapping public ResponseEntity handleWebhook( @RequestHeader(GitHubWebhookHeader.EVENT) String event, diff --git a/src/main/java/com/onixbyte/deltaforceguide/controller/GlobalExceptionHandler.java b/src/main/java/com/onixbyte/deltaforceguide/controller/GlobalExceptionHandler.java index 4edd154..fc20ba4 100644 --- a/src/main/java/com/onixbyte/deltaforceguide/controller/GlobalExceptionHandler.java +++ b/src/main/java/com/onixbyte/deltaforceguide/controller/GlobalExceptionHandler.java @@ -2,7 +2,11 @@ package com.onixbyte.deltaforceguide.controller; import com.onixbyte.deltaforceguide.domain.dto.ErrorResponse; import com.onixbyte.deltaforceguide.exeption.BizException; +import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; @@ -20,5 +24,22 @@ public class GlobalExceptionHandler { return ResponseEntity.status(status) .body(new ErrorResponse(exception.getMessage())); } + + @ExceptionHandler(AuthenticationException.class) + public ResponseEntity handleAuthenticationException(AuthenticationException exception) { + return ResponseEntity.status(HttpStatus.UNAUTHORIZED) + .body(new ErrorResponse("请先登录")); + } + + @ExceptionHandler(AccessDeniedException.class) + public ResponseEntity handleAccessDeniedException(AccessDeniedException exception) { + var authentication = SecurityContextHolder.getContext().getAuthentication(); + var httpStatus = authentication == null || !authentication.isAuthenticated() + ? HttpStatus.UNAUTHORIZED + : HttpStatus.FORBIDDEN; + var message = httpStatus == HttpStatus.UNAUTHORIZED ? "请先登录" : "权限不足"; + return ResponseEntity.status(httpStatus) + .body(new ErrorResponse(message)); + } }