【问题标题】:Is it correct to make a call to another Spring microservice inside a filter?在过滤器中调用另一个 Spring 微服务是否正确?
【发布时间】:2019-12-22 19:26:42
【问题描述】:

我构建了一个 Spring 身份验证微服务,负责对每个 REST 请求进行身份验证。身份验证机制已使用JWT 构建。每个请求都应该提供一个Authentication: Bearer 标头。

我还构建了一个网关微服务,它公开了后端微服务的一些 API。对网关的每个请求都应该经过身份验证。我正在考虑实现一个OncePerRequestFilter,我可以从中调用身份验证微服务。

@Component
public class AuthFilter extends OncePerRequestFilter {
    @Autowired
    private RestTemplate restTemplate;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        // User restTemplate to call the authentication microservice and authenticate the request.
    }
}

我的问题可能有点太宽泛了,但我还是想试试。

在 Spring 过滤器中发出 HTTP 请求是否有任何问题?这会导致挂起或在某些边缘情况下发生任何事情,还是仅仅是糟糕的设计?

【问题讨论】:

    标签: spring filter microservices


    【解决方案1】:

    可以在过滤器中调用外部服务。 Spring Security 也经常在各种情况下执行此操作(例如,针对 LDAP 进行身份验证,获取 JWK 以验证 JWT 签名等)。它可能不会直接在过滤器内部调用外部服务,但过滤器会委托给其他对象来调用外部服务。但是思路是一样的,只要确保你处理了外部服务不可用的情况,比如在调用外部服务时对 HTTP 请求设置一个合理的超时时间。如果超时后无法接收到外部服务的响应,则视为失败。

    附:看起来您正在实施自己的授权流程。如果您的身份验证服务支持 OAuth2 ,您可以考虑尝试 Spring Security 5 的 OAuth2 支持,这可能会让您的生活更轻松。

    【讨论】:

      【解决方案2】:

      我同意你的观点,这种方法是矛盾的。 我看到这种方法有两个主要限制:

      • 此过滤器在请求范围内,因此您可以面对连接/读取超时;
      • 更复杂的错误处理(嵌套结构需要更高的准确性)。

      如果您可以避免这些限制,您可以使用这种方法:RestTemplate 是同步和阻塞的,因此您的过滤器将等待 RestTemplate 调用的结果。使用 WebClient,这样的任务可能无法完成。

      显然这个实现与 SOLID 相矛盾,所以最好改变你的设计/架构。

      【讨论】:

      • 感谢您的回复。您能否就违反 SOLID 原则的问题多讨论一下?
      • 单一职责:过滤器类的工作超出了他的能力范围 - 我们可以讨论这一点,但我的主要论点 - 过滤器调用外部资源来检查不是请求/响应,而是我们可以检查的数据得到请求/响应。 Interface Sergregation:你试图在过滤器类中实现比它应该做的更多的功能。依赖倒置:非常间接,但根据原则的定义,违规是可见的 - 对于外部请求,此过滤器变得非常硬编码。我再说一遍 - 这是非常间接的,仅基于理论。
      • 添加一个只负责拨打电话的服务会解决这些问题吗?
      【解决方案3】:

      从一个微服务到另一个微服务进行 HTTP 调用是一种普遍做法。

      在使用 JWT 令牌处理 REST API 中的身份验证时,通常的做法是从众所周知的 URL (https://YOUR_DOMAIN/.well-known/jwks.json) 加载加密密钥作为 JSON Web 密钥集 (JWKS)。

      根据SOLID原则,最好创建一个专门的类(Spring服务)来负责调用另一个服务,而不是直接将RestTemplate注入Filter

      类的职责越少,将来对其进行更改并使用单元测试覆盖代码就越容易(更不容易出错)。当所有逻辑都位于单个类中时,您将拥有大量测试用例和复杂的模拟。当类具有单一职责并调用另一个类时,很容易模拟这些类并仅测试这个单一职责。

      当一个微服务通过 REST API 调用另一个微服务时,有几个方面需要考虑:

      • 故障转移 - 拥有多个微服务实例以容忍单个实例的故障
      • 可扩展性 - 动态启动更多微服务实例以处理不断增长的负载
      • 负载均衡 - 将请求路由到微服务的不同实例
      • 服务发现 - 允许微服务通过逻辑名称而不是硬编码主机和端口值来找到彼此
      • 超时 - 超过超时后将请求丢弃到下游微服务,而不是永远等待响应
      • 重试 - 重试对下游微服务的失败请求
      • 断路器 - 防止网络或服务故障级联到其他服务

      Kubernetes + IstioSpring Cloud 堆栈涵盖了这些方面。

      如果将微服务之间的同步 REST API 调用替换为 异步消息传递,那么几乎所有这些方面都不是那么重要。

      例如,服务 A 不是通过 REST API 调用服务 B,而是订阅服务 B 发出的事件到 Kafka 主题。每次事件发生时,服务 A 都会收到通知,并将所需的数据持久化到自己的 DB 中。

      这种方法可以实现服务之间的最大解耦,但会导致最终的一致性。这意味着,它不适合身份验证和其他高度一致的情况。例如。检查账户余额是高度一致的,应该使用同步调用来完成。

      【讨论】:

        猜你喜欢
        • 2021-05-24
        • 1970-01-01
        • 2018-03-04
        • 2020-11-17
        • 2020-03-18
        • 1970-01-01
        • 2020-06-16
        • 2020-04-27
        • 2022-12-01
        相关资源
        最近更新 更多