CORS POST请求失败

5
我使用SLIM微框架构建了一个API。我设置了一些中间件,使用以下代码添加CORS头部信息。
class Cors{

    public function __invoke(Request $request, Response $response, $next){

        $response = $next($request, $response);
        return $response
            ->withHeader('Access-Control-Allow-Origin', 'http://mysite')
            ->withHeader('Access-Control-Allow-Headers', 'X-Requested-With, Content-Type, Accept, Origin, Authorization')
            ->withHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
    }

}

我使用了VueJS作为我的前端框架。我设置了VueResource,并创建了一个包含以下代码的函数。

register (context, email, password) {
  Vue.http({
    url: 'api/auth/register',
    method: 'POST',
    data: {
    email: email,
    password: password
  }
}).then(response => {
  context.success = true
}, response => {
  context.response = response.data
  context.error = true
})
}

在Chrome中,控制台会记录以下错误信息。
XMLHttpRequest cannot load http://mysite:9800/api/auth/register. Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://mysite' is therefore not allowed access.

奇怪的是,GET请求完全正常工作。

4个回答

2

你已经解决了一半的问题。

你所缺少的是一个OPTIONS路由,这些头信息也需要添加。

$app->options('/{routes:.+}', function ($request, $response, $args) {
    return $response
        ->withHeader('Access-Control-Allow-Origin', 'http://mysite')
        ->withHeader('Access-Control-Allow-Headers', 'X-Requested-With, Content-Type, Accept, Origin, Authorization')
        ->withHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
});

1
谢谢!太棒了,伙计。现在它完美地运行了。没有你我做不到。 - Jerome Carter
根据我的了解,我发现对于CORS,浏览器客户端必须进行OPTIONS的预检请求,并验证您上述提到的响应头。这是否意味着禁用OPTIONS并不设置这些标头将导致失败?我们需要为这样的请求每次return response.send(200);吗? - xploreraj

1
这是因为预检请求是OPTIONS类型。您需要在请求上设置一个事件监听器,检查该类型并发送带有所需标头的响应。
不幸的是,我不了解Slim框架,但以下是Symfony中的工作示例。
首先,返回要使用的标头示例:
// Headers allowed to be returned.
const ALLOWED_HEADERS = ['Authorization', 'Origin', 'Content-Type', 'Content-Length', 'Accept'];

在请求监听器中,有一个onKernelRequest方法,它监视所有进来的请求:

    /**
     * @param GetResponseEvent $event
     */
    public function onKernelRequest(GetResponseEvent $event)
    {
        // Don't do anything if it's not the master request
        if (!$event->isMasterRequest()) {
            return;
        }

        // Catch all pre-request events
        if ($event->getRequest()->isMethod('OPTIONS')) {
            $router = $this->container->get('router');
            $pathInfo = $event->getRequest()->getPathInfo();

            $response = new Response();
            $response->headers->set('Access-Control-Allow-Origin', $event->getRequest()->headers->get('Origin'));
            $response->headers->set('Access-Control-Allow-Methods', $this->getAllowedMethods($router, $pathInfo));
            $response->headers->set('Access-Control-Allow-Headers', implode(', ', self::ALLOWED_HEADERS));
            $response->headers->set('Access-Control-Expose-Headers', implode(', ', self::ALLOWED_HEADERS));
            $response->headers->set('Access-Control-Allow-Credentials', 'true');
            $response->headers->set('Access-Control-Max-Age', 60 * 60 * 24);
            $response->send();
        }
    }

这里我只是复制了原始代码(所有域都被允许请求资源,您应该将其更改为您的域名)。 希望它能提供一些线索。


谢谢您详细而清晰地解释答案,但是这个解决方案失败了。 - Jerome Carter

1
实际上,CORS是在浏览器层面实现的。即使使用了

,也是如此。
 return $response
            ->withHeader('Access-Control-Allow-Origin', 'http://mysite')
            ->withHeader('Access-Control-Allow-Headers', 'X-Requested-With, Content-Type, Accept, Origin, Authorization')
            ->withHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');

Chrome和Mozilla不会设置允许跨域的标头。因此,您需要强制禁用它。

了解更多有关禁用CORS的信息

在Chrome中禁用同源策略


更多关于这个答案...为了处理预检请求,您需要除了实际的HTTP动词之外还要接受OPTIONS。 - geggleto
可以再详细解释一下吗?@geggleto - Jerome Carter
我想要在生产环境中使用它。 - Jerome Carter
在生产环境中,这将起作用,因为两者都在同一域上。 - Atul Sharma
请查看以下链接:http://www.slimframework.com/docs/cookbook/enable-cors.html @JeromeCarter - geggleto
在浏览器中禁用同源策略会给许多网络攻击者提供执行恶意脚本以读取各种网站上的个人数据的机会,因此您不应该仅仅因为CORS在您的应用程序中无法工作而禁用它。这不是一个好的解决方案,那些遵循它的人将会把他们的浏览器和信息置于风险之中。 - user482594

1
CORS 可能很难配置。关键是您需要在服务器和客户端设置特殊的头文件,我没有看到任何 Vue 头文件设置,而且据我所知 http 不是一个函数。不过这里有一些用于 POST 请求的设置。
const data = {
    email: email,
    password: password
  } 
const options = {
    headers: {
        'Access-Control-Expose-Headers': // all of your headers,
        'Access-Control-Allow-Origin': '*'
    }
}
Vue.http.post('api/auth/register', JSON.stringify(data), options).then(response => {
    // success
}, response => {
    // error
})

请注意,您需要将数据转换为字符串,并且需要公开您的标头,通常包括 Access-Control-Allow-Origin 标头。在我的一个应用程序中所做的是定义拦截器,因此我不必为每个请求设置标头。
Vue.http.headers.common['Access-Control-Expose-Headers'] = 'Origin, X-Requested-With, Content-Type, Accept, x-session-token, timeout, Content-Length, location, *'
Vue.http.headers.common['Access-Control-Allow-Origin'] = '*'

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