单独抽取用户服务(请求不通):feign添加拦截器(添加token)

feign和springSecurity

一、拦截器

java 复制代码
@Component
public class FeignRequestInterceptor implements RequestInterceptor {
    @Override
    public void apply(RequestTemplate requestTemplate) {
        String token = KmsPlatTokenUtil.genToken();
        requestTemplate.header("Authorization", "Bearer " + token);
    }
}

二、看工具类KmsPlatTokenUtil

依赖 配置类,实体类PlatAuth,token工具类

java 复制代码
package com.skms.plat.api;

import cn.hutool.core.codec.Base64Decoder;
import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil;
import cn.hutool.crypto.SmUtil;
import cn.hutool.crypto.symmetric.SM4;
import cn.hutool.extra.spring.SpringUtil;
import cn.hutool.json.JSONUtil;
import com.skms.common.exception.CustomException;
import com.skms.common.utils.SM3Utils;
import com.skms.common.utils.SecurityUtils;
import com.skms.plat.config.KmsPlatformConfig;
import lombok.extern.slf4j.Slf4j;

/**
 * @author skms
 */
@Slf4j
public class KmsPlatTokenUtil {

    private final static int EXPIRED_TIME = 5;

    public static String genToken(PlatAuth platAuth, String key) throws Exception {
        // 加密
        String source = JSONUtil.toJsonStr(platAuth);
        SM4 sm4 = SmUtil.sm4(key.getBytes());
        return sm4.encryptHex(source);
    }

    public static String genToken() {
        KmsPlatformConfig config = SpringUtil.getBean(KmsPlatformConfig.class);
        PlatAuth auth = new PlatAuth(SecurityUtils.getUserId(), SecurityUtils.getUsername());
        try {
            return genToken(auth, config.getTokenEncodeKey());
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public static PlatAuth parseToken(String token, String key) throws Exception {
        SM4 sm4 = SmUtil.sm4(key.getBytes());
        String re = sm4.decryptStr(token);
        PlatAuth platAuth = JSONUtil.toBean(re, PlatAuth.class);
        // 校验Hash
        boolean flag = SM3Utils.verify((platAuth.getUserId() + platAuth.getUsername() + platAuth.getTimeStr()).getBytes(), Base64Decoder.decode(platAuth.getHash()));
        if (!flag) {
            throw new CustomException("token校验失败");
        }
        long time = DateUtil.between(DateUtil.parse(platAuth.getTimeStr(), "yyyy-MM-dd HH:mm:ss"), DateUtil.date(), DateUnit.MINUTE, true);
        if (time > EXPIRED_TIME) {
            throw new CustomException("token过期");
        }
        return platAuth;
    }
}

三、工具类的依赖

java 复制代码
/**
 * @author skms
 */
@Data
@Component
@ConfigurationProperties(prefix = "kms")
public class KmsPlatformConfig {
    /**
     * 中心配置
     */
    private String center;
    /**
     * 密钥加密密钥
     */
    private String kek;

    private Integer centerId;

    private Integer appId;

    private String tokenEncodeKey;

    private String uploadTempPath;

    private String updateFilePath;

    private String type;

    private String grpcConfig;

    private Integer grpcPort;

    private Integer sdfPort;
}
java 复制代码
/**
 * @author skms
 */
@Data
@NoArgsConstructor
public class PlatAuth {
    private Long userId;
    private String username;
    private String timeStr;
    /**
     * hash之后base64之后的值
     */
    private String hash;

    public PlatAuth(Long userId, String username) {
        this.userId = userId;
        this.username = username;
        // 获取当前时间字符串
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        this.timeStr = LocalDateTime.now().format(formatter);
        this.hash = Base64Encoder.encode(SM3Utils.hash((userId + username + timeStr).getBytes()));
    }
}
java 复制代码
/**
 * 安全服务工具类
 *
 * @author skms
 */
public class SecurityUtils {
    /**
     * 用户ID
     **/
    public static Long getUserId() {
        try {
            return getLoginUser().getUserId();
        } catch (Exception e) {
            throw new ServiceE("获取用户ID异常", HttpStatus.UNAUTHORIZED);
        }
    }

    /**
     * 获取用户账户
     **/
    public static String getUsername() {
        try {
            return getLoginUser().getUsername();
        } catch (Exception e) {
            throw new ServiceE("获取用户账户异常", HttpStatus.UNAUTHORIZED);
        }
    }

    /**
     * 获取用户
     **/
    public static LoginUser getLoginUser() {
        try {
            return (LoginUser) getAuthentication().getPrincipal();
        } catch (Exception e) {
            throw new ServiceE("获取用户信息异常", HttpStatus.UNAUTHORIZED);
        }
    }

    /**
     * 获取Authentication
     */
    public static Authentication getAuthentication() {
        return SecurityContextHolder.getContext().getAuthentication();
    }

    /**
     * 生成BCryptPasswordEncoder密码
     *
     * @param password 密码
     * @return 加密字符串
     */
    public static String encryptPassword(String password) {
        BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
        return passwordEncoder.encode(password);
    }

    /**
     * 判断密码是否相同
     *
     * @param rawPassword     真实密码
     * @param encodedPassword 加密后字符
     * @return 结果
     */
    public static boolean matchesPassword(String rawPassword, String encodedPassword) {
        BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
        return passwordEncoder.matches(rawPassword, encodedPassword);
    }

    /**
     * 是否为管理员
     *
     * @param userId 用户ID
     * @return 结果
     */
    public static boolean isAdmin(Long userId) {
        return userId != null && 1L == userId;
    }
}

四、feign请求加白名单

由于项目要抽取用户表单独放一个服务,所有请求都是 /system/user 开头,当时怎么都没想到请求不通是拦截器的原因,在Feign里面一步步断点才知道(项目奇怪的地方是项目A和项目B各自保留security,单独抽取的认证服务只有用户表,涉及用户操作都的远程请求,原来项目A和项目B涉及用户表操作都的重新修改)

我直接修改拦截器(简单粗暴)

java 复制代码
/**
 * @author skms
 */
@Component
public class FeignRequestInterceptor implements RequestInterceptor {
    @Override
    public void apply(RequestTemplate requestTemplate) {
        String uri = requestTemplate.url();
        if (uri != null && uri.contains("/system/user")) {
            return;
        }
        String token = KmsPlatTokenUtil.genToken();
        requestTemplate.header("Authorization", "Bearer " + token);
    }
}

正确方式是:放配置文件

方式一:常用方式:在 RequestInterceptor 中通过 URL 判断白名单(最常用)
1. 创建白名单配置类

java 复制代码
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;

@Data
@Component
@ConfigurationProperties(prefix = "feign.whitelist")
public class FeignWhitelistProperties {
    /**
     * 白名单路径列表,支持通配符
     */
    private List<String> urls = new ArrayList<>();
}

2. 配置文件添加白名单(application.yml)

java 复制代码
feign:
  whitelist:
    urls:
      - /api/public/**
      - /health
      - /actuator/**
      - /system/user/login

3. 创建带白名单判断的拦截器

java 复制代码
public class FeignRequestInterceptor implements RequestInterceptor {

    private final FeignWhitelistProperties whitelistProperties;
    private final AntPathMatcher pathMatcher = new AntPathMatcher();

    @Override
    public void apply(RequestTemplate requestTemplate) {
                // 获取请求的 URL 路径(不包含域名和端口)
        String url = template.url();
        String path = extractPath(url);
        
        // 判断是否在白名单中
        if (isWhitelisted(path)) {
            log.debug("白名单路径,跳过鉴权: {}", path);
            return;
        }
        
        // 不在白名单的请求,添加鉴权 Header
        log.debug("非白名单路径,添加鉴权信息: {}", path);
        String token = KmsPlatTokenUtil.genToken();
        requestTemplate.header("Authorization", "Bearer " + token);
    }
  /**
     * 从完整 URL 中提取路径部分
     */
    private String extractPath(String url) {
        if (url == null) return "";
        int queryIndex = url.indexOf('?');
        if (queryIndex > 0) {
            return url.substring(0, queryIndex);
        }
        return url;
    }
    
    /**
     * 判断路径是否在白名单中
     */
    private boolean isWhitelisted(String path) {
        return whitelistProperties.getUrls().stream()
                .anyMatch(pattern -> pathMatcher.match(pattern, path));
    }
}

方式二:通过服务名判断白名单(微服务场景)

在微服务架构中,有时白名单是针对服务级别的,而非接口级别。可以通过判断调用的服务名来决定是否添加鉴权

1.配置文件:

yaml 复制代码
feign:
  whitelist:
    services:
      - public-service
      - auth-service
      - config-service

2... 创建带白名单判断的拦截器

java 复制代码
public class FeignRequestInterceptor implements RequestInterceptor {

    private final FeignWhitelistProperties whitelistProperties;

    @Override
    public void apply(RequestTemplate requestTemplate) {
        // 获取目标服务名(需要自定义解析,可以通过 ThreadLocal 传递)
        // 在调用 Feign 客户端之前,将目标服务名存入 ThreadLocal,然后在拦截器中取出来。因为 Feign 拦截器和业务代码运行在同一个线程中,所以可以通过 ThreadLocal 传递信息。
        String targetService = getTargetServiceFromContext();
        
        if (whitelistProperties.getServices().contains(targetService)) {
            log.debug("服务 {} 在白名单中,跳过鉴权", targetService);
            return;
        }
        
        // 不在白名单的请求,添加鉴权 Header
        log.debug("非白名单路径,添加鉴权信息: {}", path);
        String token = KmsPlatTokenUtil.genToken();
        requestTemplate.header("Authorization", "Bearer " + token);
    }
}
相关推荐
YL200404261 小时前
035LRU缓存
java·leetcode·缓存
basketball6161 小时前
C++ Lambda 表达式完全指南
开发语言·c++·算法
不知名的老吴1 小时前
C++中emplace函数的不适场景总结(三)
开发语言·c++·算法
Java面试题总结2 小时前
Go 里什么时候可以“panic”?
开发语言·后端·golang
rit84324992 小时前
基于MATLAB平台的指纹识别系统实现
开发语言·matlab
不像程序员的程序媛2 小时前
mysql 0000-00-00 00:00:00零日期问题
java·mysql
霸道流氓气质2 小时前
Spring @Scheduled 单线程陷阱:当设备重连阻塞了整个定时任务体系
java·spring boot·spring
沐知全栈开发2 小时前
TypeScript String
开发语言
DFT计算杂谈2 小时前
AMSET 设置多核并行计算
java·前端·css·html·css3