P05 | JWT Token 鉴权:AuthInterceptor 完整实现
·
P05 | JWT Token 鉴权:AuthInterceptor 完整实现
💰 付费文章 | 第一阶段:环境与架构
Token 鉴权流程
前端发送请求
│
▼
AuthInterceptor(拦截所有请求)
│
├── 白名单接口?(登录、获取公钥等)→ 直接放行
│
├── 请求头有 Token?
│ ├── 没有 → 返回 401
│ └── 有 → 校验 Token
│ ├── Token 无效/过期 → 返回 401
│ └── Token 有效 → 放行,并把用户信息存入上下文
│
▼
Controller 处理请求
AuthInterceptor 完整代码
@Component
class AuthInterceptor(
private val redisTemplate: StringRedisTemplate
) : HandlerInterceptor {
companion object {
// 不需要 Token 的接口白名单
private val WHITE_LIST = listOf(
"/pw/operator/login",
"/web/user/login",
"/web/user/wxPhone",
"/pw/operator/captcha",
"/plat/publicKey",
"/develop/**"
)
// 线程本地存储:当前请求的用户信息
val currentUser = ThreadLocal<CurrentUser>()
}
override fun preHandle(
request: HttpServletRequest,
response: HttpServletResponse,
handler: Any
): Boolean {
val requestPath = request.requestURI.removePrefix(request.contextPath)
// 1. 白名单放行
if (isWhiteListed(requestPath)) {
return true
}
// 2. 获取 Token
val token = request.getHeader("Authorization")?.removePrefix("Bearer ")
?: run {
writeUnauthorized(response, "请先登录")
return false
}
// 3. 从 Redis 校验 Token
val userJson = redisTemplate.opsForValue().get("token:$token")
?: run {
writeUnauthorized(response, "登录已过期,请重新登录")
return false
}
// 4. 解析用户信息,存入 ThreadLocal
val user = jacksonObjectMapper().readValue(userJson, CurrentUser::class.java)
currentUser.set(user)
// 5. 刷新 Token 过期时间(滑动过期)
redisTemplate.expire("token:$token", 7, TimeUnit.DAYS)
return true
}
override fun afterCompletion(
request: HttpServletRequest,
response: HttpServletResponse,
handler: Any,
ex: Exception?
) {
// 请求结束后清理 ThreadLocal,防止内存泄漏
currentUser.remove()
}
private fun isWhiteListed(path: String): Boolean {
return WHITE_LIST.any { pattern ->
if (pattern.endsWith("/**")) {
path.startsWith(pattern.removeSuffix("/**"))
} else {
path == pattern
}
}
}
private fun writeUnauthorized(response: HttpServletResponse, msg: String) {
response.status = HttpServletResponse.SC_OK // 统一返回 200,业务码标识错误
response.contentType = "application/json;charset=UTF-8"
response.writer.write("""{"code":401,"msg":"$msg","body":null}""")
}
}
// 当前用户信息
data class CurrentUser(
val userId: String,
val userName: String,
val roleIds: List<String>,
val tokenType: String // "operator"(运营员) | "user"(普通用户)
)
在 Controller 中获取当前用户
@RestController
@RequestMapping("/web/favorite")
class FavoriteController(
private val favoriteService: FavoriteService
) {
@PostMapping("/add")
fun addFavorite(@RequestBody params: Map<String, Any>): Result<*> {
// 从 ThreadLocal 获取当前用户
val currentUser = AuthInterceptor.currentUser.get()
?: return Result.error("未登录")
val attractionId = params["attractionId"] as? String
?: return Result.error("参数错误")
return Result.ok(favoriteService.add(currentUser.userId, attractionId))
}
}
注册拦截器
@Configuration
class WebMvcConfig(
private val authInterceptor: AuthInterceptor
) : WebMvcConfigurer {
override fun addInterceptors(registry: InterceptorRegistry) {
registry.addInterceptor(authInterceptor)
.addPathPatterns("/**") // 拦截所有请求
}
// 跨域配置(允许前端访问)
override fun addCorsMappings(registry: CorsRegistry) {
registry.addMapping("/**")
.allowedOriginPatterns("*")
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowedHeaders("*")
.allowCredentials(true)
.maxAge(3600)
}
}
登录接口:生成 Token
@PostMapping("/login")
fun login(@RequestBody params: Map<String, Any>): Result<*> {
val operNo = params["operNo"] as? String ?: return Result.error("用户名不能为空")
val password = params["password"] as? String ?: return Result.error("密码不能为空")
// 查询用户
val operator = operatorService.findByOperNo(operNo)
?: return Result.error("用户名或密码错误")
// 验证密码(MD5(SHA1(pwd).toUpperCase()))
val hashedPwd = MD5Util.encode(SHA1Util.encode(password).uppercase())
if (operator.password != hashedPwd) {
return Result.error("用户名或密码错误")
}
// 生成 Token
val token = UUID.randomUUID().toString().replace("-", "")
// 存入 Redis(7天过期)
val userInfo = CurrentUser(
userId = operator.commId!!,
userName = operator.operName ?: "",
roleIds = operatorService.getRoleIds(operator.commId!!),
tokenType = "operator"
)
redisTemplate.opsForValue().set(
"token:$token",
jacksonObjectMapper().writeValueAsString(userInfo),
7, TimeUnit.DAYS
)
return Result.ok(mapOf(
"token" to token,
"userInfo" to mapOf(
"operName" to operator.operName,
"avatar" to operator.avatar
)
))
}
下一篇
更多推荐


所有评论(0)