# 通用基础设施开发文档 - Part 2(JWT + 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 USER_ID = new ThreadLocal<>(); private static final ThreadLocal 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