为什么我设置 Access-Control-Allow-Origin 后会出现 CORS 错误?

Why do I get a CORS error when I've set Access-Control-Allow-Origin?

问题

响应 header 的 Access-Control-Allow-Origin 与请求 Origin header 匹配,但我仍然收到错误 Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at https://myappname.herokuapp.com/api/v1/products. (Reason: CORS request did not succeed).[Learn More]

Headers

回应Header

HTTP/1.1 308 PERMANENT REDIRECT
Connection: keep-alive
Access-Control-Allow-Credentials: true
Access-Control-Allow-Headers: authorization, content-type
Access-Control-Allow-Methods: DELETE, GET, HEAD, OPTIONS, PATCH, POST, PUT
Access-Control-Allow-Origin: http://localhost
Content-Length: 311
Content-Type: text/html; charset=utf-8
Date: Thu, 04 Apr 2019 10:03:39 GMT
Location: http://mpappname.herokuapp.com/api/v1/products/
Server: waitress
Via: 1.1 vegur

请求Header

Host: mpappname.herokuapp.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:66.0) Gecko/20100101 Firefox/66.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Access-Control-Request-Method: POST
Access-Control-Request-Headers: authorization,content-type
Referer: http://localhost/admin/main/products/create
Origin: http://localhost
DNT: 1
Connection: keep-alive

背景

下面是生成 header 的代码,但我更关心的是理解为什么 CORS 预检拒绝这个。这是针对具有产品 CRUD 设计和基于令牌的身份验证的烧瓶 api。

剪辑版

from flask_cors import CORS

def create_app(config_name):
    ...
    CORS(app, origins="http://localhost",
      allow_headers=["Content-Type", "Authorization", "Access-Control-Allow-Credentials"],
      supports_credentials=True)
    ...
    return app

完整版

from flask import Flask
from config import config
from flask_sqlalchemy import SQLAlchemy
from flask_cors import CORS
db = SQLAlchemy()

def create_app(config_name):
    app = Flask(__name__)
    app.config.from_object(config[config_name])
    config[config_name].init_app(app)

    db.init_app(app)
    CORS(app, origins="http://localhost",
      allow_headers=["Content-Type", "Authorization", "Access-Control-Allow-Credentials"],
      supports_credentials=True)

    from .main import main as main_blueprint
    app.register_blueprint(main_blueprint)

    from .api import api as api_blueprint
    app.register_blueprint(api_blueprint, url_prefix='/api/v1')

    return app

我在请求

http://mpappname.herokuapp.com/api/v1/products

而不是

http://mpappname.herokuapp.com/api/v1/products/

对某些人来说可能很明显,但我需要阅读此博客 post 明确地理解这一点:

https://airbrake.io/blog/http-errors/308-permanent-redirect

The appearance of a 308 Permanent Redirect is usually not something that requires much user intervention. All modern browsers will automatically detect the 308 Permanent Redirect response code and process the redirection action to the new URI automatically. The server sending a 308 code will also include a special Location header as part of the response it sends to the client. This Location header indicates the new URI where the requested resource can be found. For example, if an HTTP POST method request is sent by the client as an attempt to login at the https://airbrake.io URL, the web server may be configured to redirect this POST request to a different URI, such as https://airbrake.io/login. In this scenario, the server may respond with a 308 Permanent Redirect code and include the Location: https://airbrake.io/login header in the response. This informs the user agent (browser) that the POST request data (login info) was received by the server, but the resource has been permanently moved to the Location header URI of https://airbrake.io/login.