feat(auth): implement JWT authentication interceptor

- Add JwtAuthInterceptor to validate JWT tokens on protected endpoints
- Register interceptor paths via WebConfig for /api/** routes
- Fix Result return type to support nullable values across auth flows
This commit is contained in:
2026-05-21 18:33:11 +08:00
parent aaca30d3c5
commit 5bb836eafc
4 changed files with 71 additions and 3 deletions
@@ -0,0 +1,18 @@
package com.msksbr.bookmgr.config
import com.msksbr.bookmgr.interceptor.JwtAuthInterceptor
import org.springframework.context.annotation.Configuration
import org.springframework.web.servlet.config.annotation.InterceptorRegistry
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer
@Configuration
class WebConfig(
private val jwtAuthInterceptor: JwtAuthInterceptor
): WebMvcConfigurer {
override fun addInterceptors(registry: InterceptorRegistry) {
registry.addInterceptor(jwtAuthInterceptor)
.addPathPatterns("/api/**")
.excludePathPatterns("/api/auth/login")
.excludePathPatterns("/api/auth/logout")
}
}
@@ -30,7 +30,7 @@ class AuthController(
@RequestBody @RequestBody
loginDTO: UserLoginDTO, loginDTO: UserLoginDTO,
request: HttpServletRequest request: HttpServletRequest
): Result<out Any> { ): Result<out Any?> {
log.info("Login from ${ipExtractor.getRealIp(request)} username: ${loginDTO.username}") log.info("Login from ${ipExtractor.getRealIp(request)} username: ${loginDTO.username}")
log.info("UA: ${request.getHeader("User-Agent")}") log.info("UA: ${request.getHeader("User-Agent")}")
// 调用service验证 // 调用service验证
@@ -0,0 +1,50 @@
package com.msksbr.bookmgr.interceptor
import com.fasterxml.jackson.databind.ObjectMapper
import com.msksbr.bookmgr.config.JwtUtils
import com.msksbr.bookmgr.template.Result
import jakarta.servlet.http.HttpServletRequest
import jakarta.servlet.http.HttpServletResponse
import org.springframework.stereotype.Component
import org.springframework.web.servlet.HandlerInterceptor
@Component
class JwtAuthInterceptor(
private val objectMapper: ObjectMapper,
private val jwtUtils: JwtUtils
) : HandlerInterceptor {
override fun preHandle(request: HttpServletRequest, response: HttpServletResponse, handler: Any): Boolean {
val authHeader = request.getHeader("Authorization") ?: run {
writeJson(
response, HttpServletResponse.SC_UNAUTHORIZED,
Result.unauthorized("Missing Authorization header")
)
return false
}
if (!authHeader.startsWith("Bearer ")) {
writeJson(
response, HttpServletResponse.SC_FORBIDDEN,
Result.unauthorized("Invalid token format")
)
return false
}
val token = authHeader.removePrefix("Bearer ")
val claims = jwtUtils.parseToken(token)
if (claims == null) {
writeJson(
response, HttpServletResponse.SC_UNAUTHORIZED,
Result.unauthorized("Token invalid or expired")
)
return false
}
request.setAttribute("username",claims.subject)
request.setAttribute("role",claims.get("role", String::class.java))
return true
}
private fun writeJson(response: HttpServletResponse, status: Int, result: Result<*>) {
response.status = status
response.contentType = "application/json;charset=UTF-8"
objectMapper.writeValue(response.writer, result)
}
}
@@ -15,7 +15,7 @@ data class Result<T>(
) )
} }
// 失败 // 失败
fun <T> error(message: String): Result<T> { fun error(message: String): Result<Any?> {
return Result( return Result(
code = 500, code = 500,
message = message, message = message,
@@ -23,7 +23,7 @@ data class Result<T>(
) )
} }
// 未登录 // 未登录
fun <T> unauthorized(message: String): Result<T> { fun unauthorized(message: String): Result<Any?> {
return Result( return Result(
code = 401, code = 401,
message = message, message = message,