Files
number/后端架构设计/11-通用基础设施-part2-JWT与拦截器.md

175 lines
4.6 KiB
Markdown
Raw Permalink Normal View History

2026-03-17 12:09:43 +08:00
# 通用基础设施开发文档 - Part 2JWT + UserContext + 拦截器)
---
## 一、JwtUtil.java
```java
package com.openclaw.util;
import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.security.Key;
import java.util.Date;
@Component
public class JwtUtil {
private final Key key;
private final long expireMs;
public JwtUtil(
@Value("${jwt.secret}") String secret,
@Value("${jwt.expire-ms}") long expireMs) {
this.key = Keys.hmacShaKeyFor(secret.getBytes());
this.expireMs = expireMs;
}
public String generate(Long userId, String role) {
return Jwts.builder()
.setSubject(userId.toString())
.claim("role", role)
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + expireMs))
.signWith(key, SignatureAlgorithm.HS256)
.compact();
}
public Claims parse(String token) {
return Jwts.parserBuilder()
.setSigningKey(key).build()
.parseClaimsJws(token)
.getBody();
}
public Long getUserId(String token) {
return Long.parseLong(parse(token).getSubject());
}
public String getRole(String token) {
return parse(token).get("role", String.class);
}
}
```
```yaml
# application.yml
jwt:
secret: change-this-to-a-256-bit-random-secret-in-prod
expire-ms: 86400000 # 24 小时
```
---
## 二、UserContext.java
```java
package com.openclaw.util;
public class UserContext {
private static final ThreadLocal<Long> USER_ID = new ThreadLocal<>();
private static final ThreadLocal<String> ROLE = new ThreadLocal<>();
public static void set(Long userId, String role) {
USER_ID.set(userId);
ROLE.set(role);
}
public static Long getUserId() { return USER_ID.get(); }
public static String getRole() { return ROLE.get(); }
public static void clear() {
USER_ID.remove();
ROLE.remove();
}
}
```
---
## 三、AuthInterceptor.java
```java
package com.openclaw.interceptor;
import com.openclaw.constant.ErrorCode;
import com.openclaw.exception.BusinessException;
import com.openclaw.util.JwtUtil;
import com.openclaw.util.UserContext;
import jakarta.servlet.http.*;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
@Component
@RequiredArgsConstructor
public class AuthInterceptor implements HandlerInterceptor {
private final JwtUtil jwtUtil;
@Override
public boolean preHandle(HttpServletRequest req,
HttpServletResponse res,
Object handler) {
String auth = req.getHeader("Authorization");
if (auth == null || !auth.startsWith("Bearer "))
throw new BusinessException(ErrorCode.UNAUTHORIZED);
try {
String token = auth.substring(7);
Long userId = jwtUtil.getUserId(token);
String role = jwtUtil.getRole(token);
UserContext.set(userId, role);
} catch (Exception e) {
throw new BusinessException(ErrorCode.UNAUTHORIZED);
}
return true;
}
@Override
public void afterCompletion(HttpServletRequest req, HttpServletResponse res,
Object handler, Exception ex) {
UserContext.clear(); // 防止 ThreadLocal 内存泄漏
}
}
```
---
## 四、WebMvcConfig.java注册拦截器
```java
package com.openclaw.config;
import com.openclaw.interceptor.AuthInterceptor;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.*;
@Configuration
@RequiredArgsConstructor
public class WebMvcConfig implements WebMvcConfigurer {
private final AuthInterceptor authInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(authInterceptor)
.addPathPatterns("/api/**")
.excludePathPatterns(
"/api/v1/users/register",
"/api/v1/users/login",
"/api/v1/users/sms-code",
"/api/v1/payments/callback/**",
"/api/v1/skills", // 公开浏览
"/api/v1/skills/{id}" // 公开详情
);
}
}
```
---
**文档版本**v1.0 | **创建日期**2026-03-16