使用Zuul作为身份验证网关

28

背景

我想要实现这篇文章中提出的设计。

它可以通过下面的图表进行总结: Security Architecture

  1. 客户端首先通过IDP(OpenID Connect/OAuth2)进行身份验证
  2. IDP返回访问令牌(不带用户信息的不透明令牌)
  3. 客户端使用Authorization标头中的访问令牌通过API网关进行调用
  4. API网关使用Access Token向IDP发出请求
  5. IDP验证Access Token是否有效,并以JSON格式返回用户信息
  6. API网关将用户信息存储在JWT中,并使用私钥对其进行签名。然后将JWT传递给验证JWT使用公钥的下游服务
  7. 如果服务必须调用另一个服务来满足请求,则将JWT一起传递,该JWT用作请求的身份验证和授权

目前进展

我已经使用以下内容完成了大部分工作:

  • Spring cloud 作为全局框架
  • Spring boot 用于启动单个服务
  • Netflix Zuul 作为API网关

我还编写了一个Zuul PRE过滤器,用于检查Access Token、联系IDP并创建JWT。然后将JWT添加到转发给下游服务的请求标头中。

问题

现在我的问题非常特定于Zuul及其过滤器。如果由于任何原因API网关中的身份验证失败,如何可以停止路由并直接响应401,而不继续过滤器链和转发调用?

目前,如果身份验证失败,则过滤器将不会将JWT添加到标头中,并且401将来自下游服务。我希望我的网关可以防止这种不必要的调用。

我试图使用com.netflix.zuul.context.RequestContext来实现这个目标,但是文档相当贫乏,我找不到方法。


1
为什么你不使用Spring Cloud Security来实现这个功能呢?据我所知,它可以提供开箱即用的解决方案。 - M. Deinum
@M.Deinum 我曾经认为我无法掌控实现这个特定的设计。我需要在受保护的网络之外访问令牌,而在网络内则使用JWT。我对Spring Cloud安全性没有太多经验。你认为我可以用它来实现我的设计吗? - phoenix7360
Spring Cloud Security只会将相同的令牌传递给下游服务,它没有像@phoenix7360所期望的那样交换或增强令牌的能力。然而,它是一个合理的基础构件,可以进行进一步的开发。 - Ryan J. McDonough
3个回答

13

您可以尝试在当前上下文中设置setSendZuulResponse(false)。这样不会路由请求。您也可以从上下文中调用removeRouteHost(),这将实现相同的效果。您可以使用setResponseStatusCode来设置401状态码。


5
在您的run方法中添加以下内容,它将解决此问题。
ctx.setSendZuulResponse(false);
ctx.setResponseStatusCode(401);

4

我知道回答晚了,你可以使用Zuul的预过滤器。下面是你需要遵循的步骤。

 //1. create filter with type pre
 //2. Set the order of filter to greater than 5 because we need to run our filter after preDecoration filter of zuul.
 @Component
 public class CustomPreZuulFilter extends ZuulFilter {

  private final Logger logger = LoggerFactory.getLogger(this.getClass());

@Override
public Object run() {
    final RequestContext requestContext = RequestContext.getCurrentContext();
    logger.info("in zuul filter " + requestContext.getRequest().getRequestURI());
    byte[] encoded;
    try {
        encoded = Base64.encode("fooClientIdPassword:secret".getBytes("UTF-8"));
        requestContext.addZuulRequestHeader("Authorization", "Basic " + new String(encoded));

        final HttpServletRequest req = requestContext.getRequest();
        if (requestContext.getRequest().getHeader("Authorization") == null
                && !req.getContextPath().contains("login")) {
            requestContext.unset();
            requestContext.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value());

        } else {
              //next logic
            }
        }

    } catch (final UnsupportedEncodingException e) {
        logger.error("Error occured in pre filter", e);
    }

    return null;
}



@Override
public boolean shouldFilter() {
    return true;
}

@Override
public int filterOrder() {
    return 6;
}

@Override
public String filterType() {
    return "pre";
}

}

requestContext.unset()将重置当前线程活动请求的RequestContext,并且您可以提供响应状态代码。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接