当前位置: 代码网 > it编程>编程语言>Java > 浅析SpringBoot3.x 如何避免内部服务调用被重复拦截

浅析SpringBoot3.x 如何避免内部服务调用被重复拦截

2025年09月28日 Java 我要评论
前言在微服务架构里,外部请求和内部服务调用的认证逻辑往往不一样。比如外部用户访问接口时,必须用 user_token 来校验身份;而服务之间(比如服务 a 调用服务 b)只需要用 client_tok

前言

在微服务架构里,外部请求和内部服务调用的认证逻辑往往不一样

比如外部用户访问接口时,必须用 user_token 来校验身份;而服务之间(比如服务 a 调用服务 b)只需要用 client_token 来表明这是内部调用,不必再走一遍复杂的用户认证。

但是问题来了:

用户带着 access_token 请求服务 a,a 会先过 spring security 的过滤器。这个过滤器里我们要远程调用服务 b 验证 access_token,结果 feign 请求又被 security 拦了一次,导致认证死循环。

那要怎么破?本文就来聊聊这个场景的实战解法。

需求场景分析

我们先把需求捋清楚:

1.外部请求

  • 必须携带 user_token
  • 通过服务 a 时,spring security 要走用户认证逻辑

2.服务间调用

  • feign 调用必须带 client_token
  • 服务 b 收到请求时,只要验证这个 client_token 就行,不必再走用户认证

3.问题点

当 a 的 security 过滤器里用 feign 调服务 b 验证 access_token 时,这个请求也会被拦截,导致无限套娃

常见坑点

  • 没区分 token 类型:用户 token 和 client token 混在一起,所有请求都走同一套拦截逻辑。
  • feign 默认带全局拦截:spring security 的 onceperrequestfilter 会作用于所有请求,包括 feign 发起的内部请求。
  • 死循环风险:a 调 b 校验 token,b 又拦截成用户请求 → 又要去 a 验证 → 死循环。

所以我们必须设计一套机制,让外部请求和内部调用走不同的认证逻辑

解决思路

1.区分两类请求

  • 用户请求:带 authorization: bearer user_token
  • 内部调用:带 x-client-token: client_token

2.spring security 配置双认证链

  • 用户请求必须走 jwt 校验逻辑
  • 内部调用只校验 client_token,绕过用户认证

3.feign 请求统一加 client_token

requestinterceptor 给 feign 请求自动加 x-client-token

代码实战

下面用 spring boot 3.x 写一个简化版 demo,演示完整流程。

1. feign 请求加上 client_token

import feign.requestinterceptor;
import feign.requesttemplate;
import org.springframework.context.annotation.bean;
import org.springframework.context.annotation.configuration;

@configuration
public class feignconfig {

    @bean
    public requestinterceptor clienttokeninterceptor() {
        return (requesttemplate template) -> {
            // 模拟内部服务调用的 client_token,可以从配置中心或 vault 获取
            template.header("x-client-token", "my-client-token-123");
        };
    }
}

这样每次服务 a 调用服务 b,都会带上 x-client-token

2. 自定义认证过滤器

我们需要两个过滤器,一个处理用户 token,一个处理 client token。

import jakarta.servlet.filterchain;
import jakarta.servlet.servletexception;
import jakarta.servlet.http.httpservletrequest;
import jakarta.servlet.http.httpservletresponse;
import org.springframework.security.authentication.usernamepasswordauthenticationtoken;
import org.springframework.security.core.context.securitycontextholder;
import org.springframework.security.web.authentication.webauthenticationdetailssource;
import org.springframework.web.filter.onceperrequestfilter;

import java.io.ioexception;

public class clienttokenfilter extends onceperrequestfilter {

    private final string client_token = "my-client-token-123";

    @override
    protected void dofilterinternal(httpservletrequest request,
                                    httpservletresponse response,
                                    filterchain filterchain)
            throws servletexception, ioexception {

        string clienttoken = request.getheader("x-client-token");
        if (clienttoken != null && clienttoken.equals(client_token)) {
            // 直接认证为内部服务角色
            usernamepasswordauthenticationtoken authentication =
                    new usernamepasswordauthenticationtoken("internal-service", null, null);
            authentication.setdetails(new webauthenticationdetailssource().builddetails(request));
            securitycontextholder.getcontext().setauthentication(authentication);
        }

        filterchain.dofilter(request, response);
    }
}

这里的逻辑很简单:

  • 如果请求头有 x-client-token 并且正确 → 直接放行,视为内部请求
  • 没有的话就交给后面的用户认证逻辑去处理

3. security 配置双认证逻辑

import org.springframework.context.annotation.bean;
import org.springframework.context.annotation.configuration;
import org.springframework.security.config.annotation.web.builders.httpsecurity;
import org.springframework.security.web.securityfilterchain;
import org.springframework.security.web.authentication.usernamepasswordauthenticationfilter;

@configuration
public class securityconfig {

    @bean
    public securityfilterchain securityfilterchain(httpsecurity http) throws exception {
        http.csrf(csrf -> csrf.disable());

        http.authorizehttprequests(auth -> auth
                .requestmatchers("/public/**").permitall()
                .anyrequest().authenticated()
        );

        // client token filter 优先级要高于 user token filter
        http.addfilterbefore(new clienttokenfilter(), usernamepasswordauthenticationfilter.class);
        http.addfilterbefore(new usertokenfilter(), usernamepasswordauthenticationfilter.class);

        return http.build();
    }
}

这里我们把 clienttokenfilter 放在前面,这样内部调用会优先匹配,不会再进入用户认证逻辑。

4. 用户 token 认证

public class usertokenfilter extends onceperrequestfilter {
    @override
    protected void dofilterinternal(httpservletrequest request,
                                    httpservletresponse response,
                                    filterchain filterchain)
            throws servletexception, ioexception {
        string authheader = request.getheader("authorization");
        if (authheader != null && authheader.startswith("bearer ")) {
            string usertoken = authheader.substring(7);
            // todo: 调用服务 b 验证 user_token,有效的话设置用户上下文
            // 这里省略 jwt 验证逻辑
        }
        filterchain.dofilter(request, response);
    }
}

这样就能保证:

  • 外部请求 → authorization: bearer user_token → 走 usertokenfilter
  • 内部调用 → x-client-token: client_token → 走 clienttokenfilter

实际场景结合

设想一下:

  • 用户小明用 app 登录,请求 /api/orders 带着 user_token
  • 服务 a 收到请求,要调用服务 b 校验 user_token
  • feign 调用时带上了 x-client-token,所以服务 b 不会再拦截成用户请求,而是识别成内部调用。
  • 服务 b 验证成功后返回结果,a 才继续执行业务逻辑。

这样就避免了死循环,既保证了安全性,又把内部调用和外部请求分开了。

总结

这个问题的核心是 区分外部请求和内部调用的认证链路

  • 给 feign 调用统一加 client_token
  • 在 security 里加一个 clienttokenfilter,专门处理内部请求
  • 用户请求继续走 jwt 或 access_token 的逻辑
  • 通过过滤器顺序,避免 feign 调用再触发用户认证,彻底解决死循环

到此这篇关于浅析springboot3.x 如何避免内部服务调用被重复拦截的文章就介绍到这了,更多相关springboot内部服务调用内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com