【问题标题】:Tracking down how to fix CORS issue追踪如何解决 CORS 问题
【发布时间】:2019-12-07 05:11:27
【问题描述】:

我正在处理的应用程序中遇到了 CORS 问题。

它在 Kubernetes 中设置,带有第三方 Java 框架:

http://www.ninjaframework.org/

我收到以下错误:

Preflight response is not successful
XMLHttpRequest cannot load https://api.domain.com/api/v1/url/goes/here? due to access control checks.
Failed to load resource: Preflight response is not successful

我认为问题不在于 Kubernetes,但以防万一 - 这是我的 Kubernetes 设置:

apiVersion: v1
kind: Service
metadata:
  name: domain-server
  annotations:
    dns.alpha.kubernetes.io/external: "api.domain.com"
    service.beta.kubernetes.io/aws-load-balancer-ssl-cert: arn:aws:acm:us-east-2:152660121739:certificate/8efe41c4-9a53-4cf6-b056-5279df82bc5e
    service.beta.kubernetes.io/aws-load-balancer-backend-protocol: http
spec:
  type: LoadBalancer
  selector:
    app: domain-server
  ports:
    - port: 443
      targetPort: 8080
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: domain-server
spec:
  replicas: 2
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 3
  revisionHistoryLimit: 10
  template:
    metadata:
      labels:
        app: domain-server
    spec:
      containers:
        - name: domain-server
          image: "location.aws.etc"
          imagePullPolicy: Always
    ...

我完全迷失了——如何在我的 api 端点上启用 CORS?如果这是一个简单的问题,或者我在这里没有提供足够的信息,我很抱歉,但我不知道如何做到这一点,我已经尝试了几种途径。

注意,为了清楚起见,api.domain.com 是我实际 api 域的替代品,我只是不想透露我正在处理的网站

编辑:

我的猜测是它可能与此有关:

private Result filterProtectedApi(FilterChain chain, Context context, boolean isMerchant, JwtAuthorizer jwtAuthorizer) {
    String authHeader = context.getHeader("Authorization");
    if (authHeader == null || !authHeader.startsWith("Bearer ")) {
        return this.forbiddenApi();
    }
    context.setAttribute("access-token", authHeader.substring("Bearer ".length()));
    return this.filterProtected(chain, context, isMerchant, jwtAuthorizer, parser -> parser.parseAuthHeader(authHeader), this::forbiddenResource);
}

private AuthLevel getAuthLevel(String requestPath) {
    log.info("REQUEST PATH: " + requestPath);
    if (requestPath.equals("/auth") || requestPath.equals("/auth/merchant") || requestPath.equals("/auth/app")
            || requestPath.startsWith("/assets/") || requestPath.equals("/privacy-policy.html")
            || requestPath.equals("/forbidden.html") || requestPath.equals("/favicon.ico")
            || requestPath.startsWith("/invite/ios/") || requestPath.startsWith("/stripe/")
            || requestPath.startsWith("/chat")) {
        return AuthLevel.UNPROTECTED_RESOURCE;
    }
    if (requestPath.startsWith("/merchant/api/")) {
        return AuthLevel.PROTECTED_MERCHANT_API;
    }
    if (requestPath.startsWith("/merchant/")) {
        return AuthLevel.PROTECTED_MERCHANT_RESOURCE;
    }
    if (requestPath.startsWith("/api/")) {
        return AuthLevel.PROTECTED_API;
    }
    return AuthLevel.PROTECTED_RESOURCE;
}

我尝试添加一些内容以忽略 OPTIONS 请求,但我仍然无法通过预检检查

private Result filterProtectedApi(FilterChain chain, Context context, boolean isMerchant,
        JwtAuthorizer jwtAuthorizer) {
    if (context.getMethod().toLowerCase().equals("options")) {
        return chain.next(context);
    }
    String authHeader = context.getHeader("Authorization");
    if (authHeader == null || !authHeader.startsWith("Bearer ")) {
        return this.forbiddenApi();
    }
    context.setAttribute("access-token", authHeader.substring("Bearer ".length()));
    return this.filterProtected(chain, context, isMerchant, jwtAuthorizer,
            parser -> parser.parseAuthHeader(authHeader), this::forbiddenResource);
}

我需要做什么才能使预检检查成功?

编辑 - 根据以下建议将其更改为:

@Override
public Result filter(FilterChain chain, Context context) {
    if (context.getMethod().toLowerCase().equals("options")) {
        return Results.html().addHeader("Access-Control-Allow-Origin", "*")
                .addHeader("Access-Control-Allow-Headers", "Authorization")
                .addHeader("Access-Control-Allow-Methods", "GET, POST, DELETE, OPTIONS").render("OK");
    }
    AuthLevel authLevel = this.getAuthLevel(context.getRequestPath());
    switch (authLevel) {
    case PROTECTED_API: {
        return this.filterProtectedApi(chain, context, false, this.jwtAuthorizer);
    }
    case PROTECTED_MERCHANT_RESOURCE: {
        return this.filterProtectedResource(chain, context, "merchant-access-token", "/auth/merchant", true,
                this.merchantJwtAuthorizer);
    }
    case PROTECTED_MERCHANT_API: {
        return this.filterProtectedApi(chain, context, true, this.merchantJwtAuthorizer);
    }
    case UNPROTECTED_RESOURCE: {
        return this.filterUnprotectedResource(chain, context);
    }
    }
    return this.filterProtectedResource(chain, context, "access-token", "/auth", false, this.jwtAuthorizer);
}

【问题讨论】:

  • 我敢打赌api.name.com/api/v1/url/goes/here 与您的实际 API 端点不对应,对吧?因此您可能错过了 Web 前端的一些配置。 CORS 不是这里的问题
  • 确实如此。 api.name.com 是我的域的替代品。我不想透露实际的域是什么。我会将其更改为域以使其更清晰。
  • 我强烈的猜测是,像 'if (context.getMethod() == "OPTION") { return chain.next(context);}' 这样的东西基本上会让所有预检都亮起绿灯
  • 不,不是这样
  • 请显示实际的浏览器控制台日志输出(firefox 或 chrome)。这与kubernetes无关,但浏览器中的同源策略和CORS检查。

标签: java ninjaframework


【解决方案1】:

您在正确的道路上,尝试在身份验证验证之前忽略 OPTIONS 请求

if (context.getMethod().toLowerCase().equals("options")) {
    return chain.next(context);
}

还需要正确响应预检请求

if (context.getMethod().toLowerCase().equals("options")) {
    return Results.html()
                  .addHeader("Access-Control-Allow-Origin", "*")
                  .addHeader("Access-Control-Allow-Headers", "Authorization")
                  .addHeader("Access-Control-Allow-Methods", "GET, POST, DELETE")
                  .render("OK");
}

简而言之,你需要回应

  • 适当的 http 状态代码,通常为 200 或 204
  • 添加所需的 http 响应标头
    • "Access-Control-Allow-Origin" 带有 "*" 以允许来自所有域的 CORS,或 "http://www.domainA.com" 以仅允许来自特定域的 CORS
    • “Access-Control-Allow-Headers”,允许 http 标头
    • “Access-Control-Allow-Methods”,允许使用 http 方法
  • 响应正文无关紧要,发送“OK”即可。

请注意,可以从任何路由完成预检请求,因此我建议使用上面的代码创建一个新过滤器,并将其先用于所有路由。

所以你在实现filter() method后使用它:

public Result filter(FilterChain chain, Context context) {
     if (context.getMethod().toLowerCase().equals("options")) {
          return Results.html()
                  .addHeader("Access-Control-Allow-Origin", "*")
                  .addHeader("Access-Control-Allow-Headers", "Authorization")
                  .addHeader("Access-Control-Allow-Methods", "GET, POST, DELETE")
                  .render("OK");
     }

Kubernetes Ingress Nginx 上的 CORS

尝试在注释配置中启用 CORS

annotations:
    nginx.ingress.kubernetes.io/enable-cors: "true"
    nginx.ingress.kubernetes.io/cors-allow-methods: "PUT, GET, POST, OPTIONS"
    nginx.ingress.kubernetes.io/cors-allow-origin: "http://localhost:8100"
    nginx.ingress.kubernetes.io/cors-allow-credentials: "true"
    nginx.ingress.kubernetes.io/cors-allow-headers: "authorization"

请注意,字符串“*”不能用于 支持凭据(https://www.w3.org/TR/cors/#resource-requests), 尝试使用您的域列表(逗号分隔)而不是 *

参考资料:

【讨论】:

  • 那我应该不用担心chain.next(context)吗?
  • 不,您不需要继续使用任何其他过滤器。选项响应应该只是“告诉”浏览器允许哪些来源、请求标头和方法。响应正文无关紧要。
  • 我刚刚测试了这个,我仍然遇到 CORS 问题,所以我猜我没有在正确的地方使用它
  • 最好定义一个新的过滤器并将其用于所有路由,不仅是受保护的路由,可以从任何路由发送预检 OPTIONS 请求。这个过滤器也应该是第一个。
  • 添加另一个过滤器是否比在其余请求的任何路由之前先检查选项请求的条件更好?
【解决方案2】:

您实际上在这里混合了两件事。访问控制和跨源请求。

Kubernetes 可以直接处理跨源请求。您需要适当地配置您的入口以正确转发跨源请求。无需在您的应用程序中配置任何内容。有关示例配置,请参阅here

但是,访问控制(身份验证和授权)需要在应用程序级别处理,可以使用此类过滤器。如果您对某些功能使用选项,则只有在那时才需要处理和实现它。我个人的建议是直接过滤这些请求。

如果您同时使用跨域/代理请求和访问控制,您将一直面临一个问题或另一个问题。让各个模块做他们应该做的事情,这样更容易调试和管理。

【讨论】:

    猜你喜欢
    • 2021-02-17
    • 1970-01-01
    • 2010-10-19
    • 2019-12-29
    • 2021-11-14
    • 2012-05-24
    • 1970-01-01
    • 2016-11-24
    • 2015-10-02
    相关资源
    最近更新 更多