微服务架构中的 Token 工作机制详解

引言

在微服务架构中,用户身份认证和授权是系统安全的核心挑战。由于服务被拆分成多个独立的微服务,传统的基于 Session 的认证方式无法满足分布式环境的需求。Token 机制(特别是 JWT)成为微服务架构中实现无状态身份认证的主流方案。本文将深入解析 Token 在微服务中的完整生命周期和工作机制。

一、Token 的生成与前端存储

1.1 Token 的生成时机

Token 在用户登录成功后生成,具体流程如下:

  1. 用户提交凭证:用户通过前端提交用户名和密码到认证服务
  2. 凭证验证:认证服务验证用户信息(查询数据库或调用用户服务)
  3. Token 生成:验证通过后,认证服务生成包含用户身份信息的 Token
  4. 返回前端:认证服务将 Token 返回给前端

常见的 Token 类型

  • JWT:自包含令牌,包含用户信息、签名和过期时间
  • UUID令牌:随机字符串,需与后端存储的用户信息关联

1.2 前端 Token 存储机制

前端收到 Token 后,需要妥善存储并在后续请求中自动携带:

// 登录成功后存储 Token
const login = async (credentials) => {
  const response = await fetch('/api/auth/login', {
    method: 'POST',
    body: JSON.stringify(credentials)
  });
  const { token } = await response.json();
  
  // 存储到 localStorage 或 sessionStorage
  localStorage.setItem('auth_token', token);
  
  // 或设置 Cookie(自动携带)
  document.cookie = `auth_token=${token}; path=/; max-age=86400`;
};

// 为所有请求自动添加 Token
axios.interceptors.request.use(config => {
  const token = localStorage.getItem('auth_token');
  if (token) {
    config.headers.Authorization = `Bearer ${token}`;
  }
  return config;
});

二、Token 在微服务间的传递机制

2.1 API 网关:统一入口和认证

微服务架构通常通过 API 网关作为统一入口:

# Spring Cloud Gateway 配置示例
spring:
  cloud:
    gateway:
      routes:
        - id: user-service
          uri: lb://user-service
          predicates:
            - Path=/api/users/**
          filters:
            - name: AuthFilter
              # 网关统一认证,验证 Token 有效性

网关负责:

  • 验证 Token 签名和有效期
  • 路由请求到正确的微服务
  • 传递原始请求头(包括 Token)

2.2 服务间调用的 Token 自动传递

2.2.1 Feign 客户端的 Token 拦截器

@Component
public class FeignTokenInterceptor implements RequestInterceptor {
    
    @Override
    public void apply(RequestTemplate template) {
        // 从当前线程的 UserContext 获取 Token
        UserInfo userInfo = UserContext.get();
        if (userInfo != null && userInfo.getToken() != null) {
            template.header("Authorization", "Bearer " + userInfo.getToken());
        }
    }
}

2.2.2 RestTemplate 的 Token 拦截器

@Configuration
public class RestTemplateConfig {
    
    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
        RestTemplate restTemplate = new RestTemplate();
        
        restTemplate.getInterceptors().add((request, body, execution) -> {
            UserInfo userInfo = UserContext.get();
            if (userInfo != null && userInfo.getToken() != null) {
                request.getHeaders().add("Authorization", 
                    "Bearer " + userInfo.getToken());
            }
            return execution.execute(request, body);
        });
        
        return restTemplate;
    }
}

2.3 Token 传递的完整链条

前端 → 网关 → 服务A → 服务B → 服务C
  ↓      ↓      ↓      ↓      ↓
 Token  Token  Token  Token  Token
  ↓      ↓      ↓      ↓      ↓
携带   验证并  从请求头 从请求头 从请求头
     转发   提取并  提取并  提取并
           存储到   存储到   存储到
           TL     TL     TL

三、微服务内的用户上下文管理

3.1 UserContext 与 ThreadLocal 设计

/**
 * 用户上下文管理类
 */
public class UserContext {
    private static final ThreadLocal<UserInfo> currentUser = new ThreadLocal<>();
    
    public static void set(UserInfo user) {
        currentUser.set(user);
    }
    
    public static UserInfo get() {
        return currentUser.get();
    }
    
    public static void clear() {
        currentUser.remove();
    }
    
    // 便捷方法
    public static Long getUserId() {
        UserInfo user = get();
        return user != null ? user.getUserId() : null;
    }
    
    public static String getUsername() {
        UserInfo user = get();
        return user != null ? user.getUsername() : null;
    }
}

/**
 * 用户信息封装类
 */
@Data
public class UserInfo {
    private Long userId;
    private String username;
    private String token;
    private List<String> roles;
    private List<String> permissions;
    // 其他用户相关信息
}

3.2 请求拦截器:Token 解析与上下文设置

@Component
public class AuthenticationInterceptor implements HandlerInterceptor {
    
    @Autowired
    private JwtTokenProvider tokenProvider;
    
    @Override
    public boolean preHandle(HttpServletRequest request, 
                           HttpServletResponse response, Object handler) {
        
        // 从请求头提取 Token
        String token = extractToken(request);
        
        if (token != null && tokenProvider.validateToken(token)) {
            // 解析 Token 获取用户信息
            UserInfo userInfo = tokenProvider.getUserInfoFromToken(token);
            userInfo.setToken(token);
            
            // 设置到当前线程的 UserContext
            UserContext.set(userInfo);
        } else {
            // Token 无效,返回 401
            response.setStatus(HttpStatus.UNAUTHORIZED.value());
            return false;
        }
        
        return true;
    }
    
    @Override
    public void afterCompletion(HttpServletRequest request,
                              HttpServletResponse response, 
                              Object handler, Exception ex) {
        // 请求完成后清理 ThreadLocal,防止内存泄漏
        UserContext.clear();
    }
    
    private String extractToken(HttpServletRequest request) {
        String bearerToken = request.getHeader("Authorization");
        if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
            return bearerToken.substring(7);
        }
        return null;
    }
}

3.3 业务代码中的用户信息获取

@RestController
@RequestMapping("/api/orders")
public class OrderController {
    
    @Autowired
    private OrderService orderService;
    
    @GetMapping("/my-orders")
    public ResponseEntity<List<Order>> getUserOrders() {
        // 直接从 UserContext 获取当前用户信息
        Long userId = UserContext.getUserId();
        String username = UserContext.getUsername();
        
        List<Order> orders = orderService.getOrdersByUserId(userId);
        return ResponseEntity.ok(orders);
    }
}

@Service
public class OrderService {
    
    public List<Order> getOrdersByUserId(Long userId) {
        // 服务层也可以直接获取用户上下文
        String currentUser = UserContext.getUsername();
        log.info("用户 {} 查询订单,目标用户ID: {}", currentUser, userId);
        
        // 业务逻辑...
        return orderRepository.findByUserId(userId);
    }
}

四、特殊场景处理

4.1 异步调用中的 Token 传递

@Service
public class AsyncService {
    
    @Async
    public CompletableFuture<String> asyncProcess() {
        try {
            // 手动传递 Token 到异步线程
            String token = AsyncContext.getToken();
            if (token != null) {
                UserInfo userInfo = parseToken(token); // 解析 Token
                UserContext.set(userInfo);
            }
            
            // 异步业务逻辑
            String result = doHeavyProcessing();
            
            return CompletableFuture.completedFuture(result);
        } finally {
            UserContext.clear(); // 清理上下文
        }
    }
}

/**
 * 异步上下文传递工具类
 */
public class AsyncContext {
    private static final ThreadLocal<String> asyncToken = new ThreadLocal<>();
    
    public static void setToken(String token) {
        asyncToken.set(token);
    }
    
    public static String getToken() {
        return asyncToken.get();
    }
    
    public static void clear() {
        asyncToken.remove();
    }
}

// 在调用异步方法前设置 Token
@Service
public class OrderService {
    
    @Autowired
    private AsyncService asyncService;
    
    public void processOrderAsync(Order order) {
        // 从当前上下文获取 Token 并设置到异步上下文
        String token = UserContext.get().getToken();
        AsyncContext.setToken(token);
        
        // 调用异步方法
        asyncService.asyncProcess();
        
        // 清理异步上下文
        AsyncContext.clear();
    }
}

4.2 网关层面的统一认证与 Token 中继

@Component
public class GatewayAuthFilter implements GlobalFilter, Ordered {
    
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        
        // 提取 Token
        String token = extractToken(request);
        
        if (token != null) {
            // 验证 Token(可选:可转发到认证服务验证)
            if (validateToken(token)) {
                // 将用户信息添加到请求头,传递给下游服务
                ServerHttpRequest mutatedRequest = request.mutate()
                    .header("X-User-Id", extractUserId(token))
                    .header("X-Username", extractUsername(token))
                    .build();
                
                return chain.filter(exchange.mutate().request(mutatedRequest).build());
            }
        }
        
        // Token 无效,返回 401
        ServerHttpResponse response = exchange.getResponse();
        response.setStatusCode(HttpStatus.UNAUTHORIZED);
        return response.setComplete();
    }
    
    // 其他工具方法...
}

五、安全最佳实践

5.1 Token 安全配置

@Configuration
public class JwtConfig {
    
    @Value("${jwt.secret}")
    private String secret;
    
    @Value("${jwt.expiration}")
    private Long expiration;
    
    @Bean
    public JwtTokenProvider jwtTokenProvider() {
        return new JwtTokenProvider(secret, expiration);
    }
}

@Component
public class JwtTokenProvider {
    
    private final String secret;
    private final Long expiration;
    
    public JwtTokenProvider(String secret, Long expiration) {
        this.secret = secret;
        this.expiration = expiration;
    }
    
    public String generateToken(UserDetails userDetails) {
        Map<String, Object> claims = new HashMap<>();
        claims.put("userId", userDetails.getUserId());
        claims.put("roles", userDetails.getRoles());
        
        return Jwts.builder()
                .setClaims(claims)
                .setSubject(userDetails.getUsername())
                .setIssuedAt(new Date())
                .setExpiration(new Date(System.currentTimeMillis() + expiration))
                .signWith(SignatureAlgorithm.HS512, secret)
                .compact();
    }
    
    public boolean validateToken(String token) {
        try {
            Jwts.parser().setSigningKey(secret).parseClaimsJws(token);
            return true;
        } catch (Exception e) {
            return false;
        }
    }
}

5.2 防范常见安全威胁

  1. 使用 HTTPS 防止 Token 被窃听
  2. 设置合理的过期时间 减少 Token 泄露风险
  3. 实现 Token 刷新机制 平衡安全性与用户体验
  4. 使用 HttpOnly Cookie 防范 XSS 攻击

六、完整工作流程总结

  1. 生成阶段:用户登录 → 认证服务生成 Token → 返回前端存储
  2. 传递阶段:前端携带 Token → 网关验证 → 服务间自动传递
  3. 上下文阶段:各服务解析 Token → 设置 ThreadLocal → 业务使用
  4. 清理阶段:请求完成 → 清理 ThreadLocal → 释放资源

结论

微服务架构中的 Token 工作机制通过"传递+解析+上下文管理"的模式,实现了分布式的无状态认证。关键在于:

  1. 标准化传递:通过 HTTP 头跨服务一致传递
  2. 自动化管理:通过拦截器自动处理 Token 传递和解析
  3. 线程级隔离:通过 ThreadLocal 实现请求级别的用户上下文隔离
  4. 安全加固:通过签名、过期时间等措施保证 Token 安全性

这种机制既保证了微服务架构的松散耦合特性,又提供了统一的身份认证方案,是现代分布式系统的重要基础设施。

posted @ 2025-11-15 17:11  谁来着?  阅读(0)  评论(0)    收藏  举报