nacos配置 服务间调用的认证问题

服务调用一般都需要token,本文章的思路是 服务之间调用时候,设置拦截 自动添加服务token

  1. 你需要设置一个token生成工具类,这个主要是 cliams 中要有 调用方和调用方的服务名称
/**
 * 服务间通信令牌工具类
 * 用于生成和验证服务之间调用的令牌
 * 注意:所有微服务共享此实现
 */
@Component
public class ServiceTokenUtil {

   // 服务名称,用于标识当前服务
    @Value("${spring.application.name:unknown-service}")
    private String serviceName;
    
 /**
     * 生成服务调用令牌
     * @param targetService 目标服务名称
     * @return 生成的JWT令牌
     */
    public String generateToken(String targetService) {
        Map<String, Object> claims = new HashMap<>();
        claims.put("source", serviceName);   // 主要就是 source 调用方服务名称
        claims.put("target", targetService); // 和被调用方服务名称
        claims.put("type", "service");
        
        return Jwts.builder()
                .setClaims(claims)
                .setSubject(serviceName)
                .setIssuedAt(new Date())
                .setExpiration(new Date(System.currentTimeMillis() + expiration))
                .signWith(secretKey)
                .compact();
    }
    
    /**
     * 验证服务令牌
     * @param token 令牌
     * @return 是否有效
     */
    public boolean validateToken(String token) {
        try {
            Jwts.parser()
                .setSigningKey(secretKey)
                .parseClaimsJws(token);
            return true;
        } catch (Exception e) {
            return false;
        }
    }
 /**
     * 从令牌中获取声明信息
     * @param token 令牌
     * @return 声明信息
     */
    public Claims getClaimsFromToken(String token) {
        return Jwts.parser()
                .setSigningKey(secretKey)
                .parseClaimsJws(token)
                .getBody();
    }
  1. 你需要设置个 FeignClientConfig 用于给经过 feign 调用的请求配置 服务token
  /**
 * @Configuration 注解
 * 1、将被修饰的类 创建成一个bean
 * 2、启动CGLIB增强(该类下的所有bean都会被注册,并且没个bean只会创建一个实例也就是单例模式)
 * 3、注册该类下的所有bean
 */
@Configuration
public class FeignClientConfig {

    @Autowired
    private ServiceTokenUtil serviceTokenUtil;

    private static final String SERVICE_AUTH_HEADER = "X-Service-Token";
    
    /**
     * 创建请求拦截器Bean
     * 在每个Feign请求发送前自动添加服务认证令牌
     */
    @Bean
    public RequestInterceptor serviceAuthInterceptor() {
        /**
         * 1、 这里自动判断返回类型是一个 函数式接口
         * 2、接口中只有一个抽象方法 参数类型是RequestTemplate
         * 3、所以lambda表达式 自动判断当前重写的抽象方法是 RequestTemplate类型参数
         */
        return requestTemplate -> {
            // 从请求URL中提取目标服务名称
            String url = requestTemplate.url();
            // 默认假设为edu服务
            String targetService = "service-edu";
            
            // 生成针对目标服务的认证令牌
            String serviceToken = serviceTokenUtil.generateToken(targetService);
            
            // 将令牌添加到请求头
            requestTemplate.header(SERVICE_AUTH_HEADER, serviceToken);
        };
    }
} 
  1. 你需要一个拦截器,用于拦截请求 检查服务token是否正确 和调用方和被调用方是否真实存在
/**
 * 服务认证过滤器
 * 用于验证来自其他服务的请求令牌
 * 继承 OncePerRequestFilter  确保单次请求只执行一次过滤逻辑
 */
@Component
public class ServiceAuthFilter extends OncePerRequestFilter {

    private static final Logger logger = LoggerFactory.getLogger(ServiceAuthFilter.class);
    // 服务间的token请求头
    private static final String SERVICE_AUTH_HEADER = "X-Service-Token";
    
    @Value("${spring.application.name:unknown-service}")
    private String serviceName;
    
    @Autowired
    private ServiceTokenUtil serviceTokenUtil;
    
    @Override
    protected void doFilterInternal(HttpServletRequest request, 
                                   HttpServletResponse response, 
                                   FilterChain filterChain) 
                                   throws ServletException, IOException {
        
        String uri = request.getRequestURI();
        
        // 检查是否是内部服务接口请求
        if (uri.contains("/internal/")) {
            // 获取服务认证令牌
            String serviceToken = request.getHeader(SERVICE_AUTH_HEADER);
            
            // 如果令牌不存在,则拒绝访问
            if (serviceToken == null || serviceToken.isEmpty()) {
                logger.warn("拒绝访问内部接口: 缺少服务认证令牌, URI: {}", uri);
                response.sendError(HttpServletResponse.SC_FORBIDDEN, "缺少服务认证令牌");
                return;
            }
            
            // 验证令牌
            if (!serviceTokenUtil.validateToken(serviceToken)) {
                logger.warn("拒绝访问内部接口: 无效的服务认证令牌, URI: {}", uri);
                response.sendError(HttpServletResponse.SC_FORBIDDEN, "无效的服务认证令牌");
                return;
            }
            
            // 验证目标服务是否正确(可选,增强安全性)
            try {
                Claims claims = serviceTokenUtil.getClaimsFromToken(serviceToken);
                String targetService = (String) claims.get("target");
                
                if (!serviceName.equals(targetService)) {
                    logger.warn("拒绝访问内部接口: 令牌目标服务不匹配, 期望: {}, 实际: {}", serviceName, targetService);
                    response.sendError(HttpServletResponse.SC_FORBIDDEN, "令牌目标服务不匹配");
                    return;
                }
                
                // 记录服务间调用
                String sourceService = (String) claims.get("source");
                logger.info("接收到来自 {} 的内部服务调用: {}", sourceService, uri);
                
            } catch (Exception e) {
                logger.error("验证服务令牌失败", e);
                response.sendError(HttpServletResponse.SC_FORBIDDEN, "验证服务令牌失败");
                return;
            }
        }
        
        // 继续过滤链
        filterChain.doFilter(request, response);
    }
} 
  1. 你需要注册这个拦截器 设置优先级 设置拦截规则
/**
 * 服务认证配置类
 * 自动注册过滤器,所有微服务引入common模块后自动启用
 */
@Configuration
public class ServiceAuthConfig {

    @Autowired
    private ServiceAuthFilter serviceAuthFilter;
    
    /**
     * 注册服务认证过滤器
     * 确保它在Spring Security过滤器链之前执行
     *
     * 1、配置过滤器
     * 2、过滤器优先级
     * 3、和拦截url
     */
    @Bean
    public FilterRegistrationBean<ServiceAuthFilter> serviceAuthFilterRegistration() {
        FilterRegistrationBean<ServiceAuthFilter> registration = new FilterRegistrationBean<>();
        registration.setFilter(serviceAuthFilter);
        // 设置过滤器优先级,确保在Spring Security过滤器之前执行
        registration.setOrder(1);
        // 只拦截包含/internal/的请求
        registration.addUrlPatterns("/**/internal/**");
        return registration;
    }
} 
posted @ 2025-07-06 09:47  我叫李坤朋  阅读(40)  评论(0)    收藏  举报