服务调用一般都需要token,本文章的思路是 服务之间调用时候,设置拦截 自动添加服务token
- 你需要设置一个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();
}
- 你需要设置个 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);
};
}
}
- 你需要一个拦截器,用于拦截请求 检查服务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);
}
}
- 你需要注册这个拦截器 设置优先级 设置拦截规则
/**
* 服务认证配置类
* 自动注册过滤器,所有微服务引入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;
}
}