Jhipster 微服务:Zuul 代理访问控制:过滤端点上的未授权访问

Jhipster Microservice: Zuul Proxy Access Control: filtered unauthorized access on endpoint

我有一组 Jhipster 微服务、Consul 和一个使用 Keycloak/OAuth2SSO 进行身份验证的网关(我们称之为 Gateway1)。 我想启用多个网关(每个网关服务于单独的应用程序)并让其他网关(Gateway2、Gateway3 等)将身份验证请求代理到现有网关 (Gateway1)。

我尝试将以下路由添加到 Gateway1 来处理这个问题;

zuul: # those values must be configured depending on the application specific needs
sensitive-headers: Cookie,Set-Cookie #see https://github.com/spring-cloud/spring-cloud-netflix/issues/3126
host:
    max-total-connections: 1000
    max-per-route-connections: 100
semaphore:
    max-semaphores: 500
ignoredServices: 'consul, Gateway1' # Prevent Consul and the "Gateway1" app itself from having a Zuul route automatically created for them
routes:
   Gateway1-uaa:
      path: /api/account/**
      serviceId: Gateway1
      sensitive-headers:

将此路由添加到 Gateway1 后,我在验证后立即收到以下错误:

Access Control: filtered unauthorized access on endpoint /api/account

当我点击 /api/authenticate 时,它 returns 我的用户。所以我怀疑这是通过代理传递令牌的问题?

我试过设置以下但没有成功

关于为什么这可能不起作用的任何想法?

更新

我发现错误是由我项目中的 AccessControl class 引发的(它是 Jhipster 生成的项目,所以不熟悉所有代码)。

路线为api/account/**,呼叫为api/account。 在过滤器中,它检查是否应该从授权列表中过滤端点并具有以下代码;

          // If this route correspond to the current request URI
        // We do a substring to remove the "**" at the end of the route URL
        if (requestUri.startsWith(serviceUrl.substring(0, serviceUrl.length() - 3))) {
            return !isAuthorizedRequest(serviceUrl, serviceName, requestUri);
        }

当字符串是完全匹配时 requestUri.startsWith returns false(api/account vs api/account。我假设它在末尾期待一个 / 。 我将不得不返回并使用 api/account/* 进行测试,看看这段代码是否会按预期工作。

现在我将它修改为 3 个空格,但我现在似乎在进行单个 api/account 调用时重复 DDoSing 服务-

com.netflix.zuul.exception.ZuulException: Filter threw Exception
    at com.netflix.zuul.FilterProcessor.processZuulFilter(FilterProcessor.java:227)
    at com.netflix.zuul.FilterProcessor.runFilters(FilterProcessor.java:157)
    at com.netflix.zuul.FilterProcessor.error(FilterProcessor.java:105)
    at com.netflix.zuul.ZuulRunner.error(ZuulRunner.java:112)
    at com.netflix.zuul.http.ZuulServlet.error(ZuulServlet.java:145)
    at com.netflix.zuul.http.ZuulServlet.service(ZuulServlet.java:83)
    at org.springframework.web.servlet.mvc.ServletWrappingController.handleRequestInternal(ServletWrappingController.java:165)
    at org.springframework.cloud.netflix.zuul.web.ZuulController.handleRequest(ZuulController.java:44)
    at org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter.handle(SimpleControllerHandlerAdapter.java:52)
    at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:991)
    at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:925)
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:981)
    at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:873)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:687)
    at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:858)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:790)
    at io.undertow.servlet.handlers.ServletHandler.handleRequest(ServletHandler.java:74)
    at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:129)
    at org.springframework.web.filter.CorsFilter.doFilterInternal(CorsFilter.java:96)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
    at io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:61)
    at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131)
    at org.springframework.boot.actuate.web.trace.servlet.HttpTraceFilter.doFilterInternal(HttpTraceFilter.java:90)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
    at io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:61)
    at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131)
    at com.st.aperture.gateway.config.SimpleCORSFilter.doFilter(SimpleCORSFilter.java:41)
    at io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:61)
    at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:320)
    at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.invoke(FilterSecurityInterceptor.java:127)
    at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.doFilter(FilterSecurityInterceptor.java:91)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
    at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:119)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
    at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:137)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
    at org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:111)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
    at org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:170)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
    at org.springframework.security.web.savedrequest.RequestCacheAwareFilter.doFilter(RequestCacheAwareFilter.java:63)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
    at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:200)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
    at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:116)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
    at org.springframework.security.web.csrf.CsrfFilter.doFilterInternal(CsrfFilter.java:100)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
    at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:74)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
    at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:105)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
    at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:56)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
    at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:215)
    at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:178)
    at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:357)
    at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:270)
    at io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:61)
    at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131)
    at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:99)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
    at io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:61)
    at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131)
    at org.springframework.security.oauth2.client.filter.OAuth2ClientContextFilter.doFilter(OAuth2ClientContextFilter.java:60)
    at io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:61)
    at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131)
    at org.springframework.web.filter.HttpPutFormContentFilter.doFilterInternal(HttpPutFormContentFilter.java:109)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
    at io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:61)
    at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131)
    at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:93)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
    at io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:61)
    at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131)
    at org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.filterAndRecordMetrics(WebMvcMetricsFilter.java:117)
    at org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.doFilterInternal(WebMvcMetricsFilter.java:106)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
    at io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:61)
    at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131)
    at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:200)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
    at io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:61)
    at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131)
    at com.st.aperture.gateway.config.OAuth2Configuration.doFilterInternal(OAuth2Configuration.java:55)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
    at io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:61)
    at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131)
    at io.undertow.servlet.handlers.FilterHandler.handleRequest(FilterHandler.java:84)
    at io.undertow.servlet.handlers.security.ServletSecurityRoleHandler.handleRequest(ServletSecurityRoleHandler.java:62)
    at io.undertow.servlet.handlers.ServletChain.handleRequest(ServletChain.java:65)
    at io.undertow.servlet.handlers.ServletDispatchingHandler.handleRequest(ServletDispatchingHandler.java:36)
    at io.undertow.servlet.handlers.security.SSLInformationAssociationHandler.handleRequest(SSLInformationAssociationHandler.java:132)
    at io.undertow.servlet.handlers.security.ServletAuthenticationCallHandler.handleRequest(ServletAuthenticationCallHandler.java:57)
    at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
    at io.undertow.security.handlers.AbstractConfidentialityHandler.handleRequest(AbstractConfidentialityHandler.java:46)
    at io.undertow.servlet.handlers.security.ServletConfidentialityConstraintHandler.handleRequest(ServletConfidentialityConstraintHandler.java:64)
    at io.undertow.security.handlers.AuthenticationMechanismsHandler.handleRequest(AuthenticationMechanismsHandler.java:60)
    at io.undertow.servlet.handlers.security.CachedAuthenticatedSessionHandler.handleRequest(CachedAuthenticatedSessionHandler.java:77)
    at io.undertow.security.handlers.AbstractSecurityContextAssociationHandler.handleRequest(AbstractSecurityContextAssociationHandler.java:43)
    at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
    at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
    at io.undertow.servlet.handlers.ServletInitialHandler.handleFirstRequest(ServletInitialHandler.java:292)
    at io.undertow.servlet.handlers.ServletInitialHandler.access0(ServletInitialHandler.java:81)
    at io.undertow.servlet.handlers.ServletInitialHandler.call(ServletInitialHandler.java:138)
    at io.undertow.servlet.handlers.ServletInitialHandler.call(ServletInitialHandler.java:135)
    at io.undertow.servlet.core.ServletRequestContextThreadSetupAction.call(ServletRequestContextThreadSetupAction.java:48)
    at io.undertow.servlet.core.ContextClassLoaderSetupAction.call(ContextClassLoaderSetupAction.java:43)
    at io.undertow.servlet.handlers.ServletInitialHandler.dispatchRequest(ServletInitialHandler.java:272)
    at io.undertow.servlet.handlers.ServletInitialHandler.access[=12=]0(ServletInitialHandler.java:81)
    at io.undertow.servlet.handlers.ServletInitialHandler.handleRequest(ServletInitialHandler.java:104)
    at io.undertow.server.Connectors.executeRootHandler(Connectors.java:336)
    at io.undertow.server.HttpServerExchange.run(HttpServerExchange.java:830)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
    at java.lang.Thread.run(Thread.java:748)
Caused by: java.lang.reflect.UndeclaredThrowableException: null
    at org.springframework.util.ReflectionUtils.rethrowRuntimeException(ReflectionUtils.java:354)
    at org.springframework.cloud.netflix.zuul.filters.post.SendErrorFilter.run(SendErrorFilter.java:99)
    at com.netflix.zuul.ZuulFilter.runFilter(ZuulFilter.java:117)
    at com.netflix.zuul.FilterProcessor.processZuulFilter(FilterProcessor.java:193)
    ... 119 common frames omitted
Caused by: java.io.IOException: An established connection was aborted by the software in your host machine
    at sun.nio.ch.SocketDispatcher.write0(Native Method)
    at sun.nio.ch.SocketDispatcher.write(SocketDispatcher.java:51)
    at sun.nio.ch.IOUtil.writeFromNativeBuffer(IOUtil.java:93)
    at sun.nio.ch.IOUtil.write(IOUtil.java:65)
    at sun.nio.ch.SocketChannelImpl.write(SocketChannelImpl.java:471)
    at org.xnio.nio.NioSocketConduit.write(NioSocketConduit.java:164)
    at io.undertow.server.protocol.http.HttpResponseConduit.write(HttpResponseConduit.java:609)
    at io.undertow.conduits.ChunkedStreamSinkConduit.flush(ChunkedStreamSinkConduit.java:269)
    at org.xnio.conduits.ConduitStreamSinkChannel.flush(ConduitStreamSinkChannel.java:162)
    at io.undertow.channels.DetachableStreamSinkChannel.flush(DetachableStreamSinkChannel.java:119)
    at org.xnio.channels.Channels.flushBlocking(Channels.java:63)
    at io.undertow.servlet.spec.ServletOutputStreamImpl.close(ServletOutputStreamImpl.java:617)
    at org.springframework.security.web.util.OnCommittedResponseWrapper$SaveContextServletOutputStream.close(OnCommittedResponseWrapper.java:520)
    at io.undertow.servlet.spec.RequestDispatcherImpl.forwardImpl(RequestDispatcherImpl.java:236)
    at io.undertow.servlet.spec.RequestDispatcherImpl.forwardImplSetup(RequestDispatcherImpl.java:147)
    at io.undertow.servlet.spec.RequestDispatcherImpl.forward(RequestDispatcherImpl.java:111)
    at org.springframework.security.web.header.HeaderWriterFilter$HeaderWriterRequestDispatcher.forward(HeaderWriterFilter.java:143)
    at org.springframework.cloud.netflix.zuul.filters.post.SendErrorFilter.run(SendErrorFilter.java:94)
    ... 121 common frames omitted

我想知道是否涉及返回到 api/account 或类似路径的重定向...继续搜索!

所以我在这个过程中学到的一些非常困难的教训-

首先 - 我将针对如上所述的 Jhipster accessControl 过滤器提出一个错误,因为它在已经触发的路由上添加了另一层路由验证并过滤了好的路由。如果您正在获取 访问控制:在端点 /api/account 上过滤未经授权的访问,请不要再看 - 只需将其从网关配置中删除即可。

其次 - 我认为我的设置存在问题,或者只是一般情况下 zuul 如果目标 path/resulting 路径不正确 - 你会得到一个 500 错误而不是 404。它可能是 crsf,但这是糟糕的错误处理。 因此,如果您看到 'server terminated connection' 或 'hystrix read timeout' - 它确实可能是一个不正确的目标 url.

第三 - 如果 API 遵循完全相同的命名约定,则很容易建立循环和 DDoS 您自己的代理。 例如我点击 gateway2/api/account 并希望它路由到 gateway1/api/account,所以我设置 路线: 路径:api/account 服务:网关 1 剥离前缀:false

好吧,这很好用...但是请求返回 gateway1/api/account 并再次被重定向.. 又一次.. 又一次,直到您跳闸。

我最终更改了网关 1 上的 API 的名称,它运行良好。

所以 - 很生气我可能会通过重命名 API 破坏某些东西并且 Zuul 不擅长管理前缀,我决定最好的办法是处理自定义过滤器;

  1. 删除前置前缀,即路径之前与路径不匹配的任何内容(例如发送 gw/api/account 到我的路线 /api/account 不应发送 service1/gw/api/account。请注意,我的实现不支持复杂的匹配模式,例如 /api/**/account/)
  2. 不路由 'local' 请求,即如果请求中没有服务名称(例如调用 api/account 来自网关,不应该触发我的 /api/account 网关路由。而且绝对不是 kthx Zuul 的 50 倍。不苦。)
  3. 尽管有上述规则,允许其他请求自由路由

导入静态org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.FORWARD_TO_KEY;

import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import com.netflix.zuul.context.RequestContext;
import com.st.aperture.gateway.gateway.filters.RequestRewriter;
import com.netflix.zuul.ZuulFilter;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.netflix.zuul.filters.ZuulProperties;
import org.springframework.cloud.netflix.zuul.filters.ZuulProperties.ZuulRoute;

public class SimplePreFilter extends ZuulFilter {

    private static Logger log = LoggerFactory.getLogger(SimplePreFilter.class);
    private ZuulProperties properties;
    private Map<String, ZuulRoute> routes;
    private DiscoveryClient discovery;

    public SimplePreFilter(ZuulProperties properties, DiscoveryClient discovery) {
        this.properties = properties;
        this.discovery = discovery;
        this.routes = this.properties.getRoutes();
    }

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

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

    @Override
    public boolean shouldFilter() {

        return true;
    }

    @Override
    public Object run() {

        RequestContext ctx = RequestContext.getCurrentContext();
        HttpServletRequest request = ctx.getRequest();
        String requestUrl = ctx.getRequest().getRequestURI().toString();

        List<String> services = discovery.getServices();

        String modifiedPath = "";
        Boolean internalService = true;

        for (String service : services) {
            if (requestUrl.contains(service + "/")) {
                // if the url contains a service name, then its not local
                internalService = false;
                break;
            }
        }

        // The routes are the statically configured routes
        for (ZuulRoute zr : routes.values()) {
            // This will break support for wildcards in the middle of paths, consider improving
            String simpleRoute = zr.getPath().replace("/**", "");
            if (requestUrl.contains(simpleRoute) && zr.isStripPrefix() == false) {
                modifiedPath = requestUrl.substring(requestUrl.indexOf(simpleRoute.substring(0, 5)));
                break;
            }
        }

        if (internalService) {
            ctx.put(FORWARD_TO_KEY, requestUrl);
            log.debug("ROUTING: Local Request - " + requestUrl);
        } else if (modifiedPath != "") {
            RequestRewriter requestRewriter = new RequestRewriter(request);
            requestRewriter.setRequestURI(modifiedPath);
            ctx.setRequest(requestRewriter);
            log.debug("ROUTING: Foreign Request: " + requestUrl + " route prefix stripped to: " + modifiedPath);
        } else {
            log.debug("ROUTING: Foreign Request: " + requestUrl);
        }

        return null;
    }

}