「新轮子」PHP CORS (Cross-origin resource sharing),解决 PHP 项目程序设置跨域需求。

file

是的,可能了解 Laravel 的都知道,在 Laravel 中简单的设置跨域,当然是选择 barryvdh/laravel-cors 这个包。

起因

是的,我确实是在重复造轮子,barryvdh/laravel-cors 包实现很优秀了,但是它也有很多问题。而我的新轮子 medz/cors 也并不是专门解决它的问题的,而是顺便解决而已。medz/cors 包的灵感来自公司项目,公司的项目使用 Laravel 框架进行开发,前端使用完全分离的方式。所以需要设置跨域,而传统 nginx 设置跨域虽然可行,但是程序设置跨域优点不言而喻(可以设置多个允许跨域域名等,动态处理信息)。

概述

medz/cors 这个包,支持在所有 PHP 的项目中使用。你只需要一些简单的配置即可。而这里主要先讲在 Laravel 中的应用,因为目前内置支持暂时对 Laravel 做了很好的专属处理。

打算友好支持如下

  • [x] PHP Native coding.
  • [x] Laravel
  • [x] PSR-7
  • [ ] Symfony
  • [ ] Yii2
  • [ ] Slim Framework

安装

感谢 Composer 为社区带来的改变,现在我们都喜欢它,所以你只需要在你的项目中使用:

composer require medz/cors

好了,你已经安装好了。如果你是 Laravel,那么,现在你已经为你的程序加上了跨域

发布配置文件

运行:

php artisan vendor:publish --provider="Medz\Cors\Laravel\Providers\LaravelServiceProvider" --force

有兴趣看配置的可以看一下 config/cors.php 的配置。但是下面会专门讲配置的。

配置

为了方便一些配置,个别 boolintstring 类型的配置可以使用 .env 进行环境变量设置:

Key 描述
CORS_ALLOW_CREDENTIAILS bool, 设置 Access-Control-Allow-Credentialstrue 还是 false
CORS_ACCESS_CONTROL_MAX_AGE int, 设置 Access-Control-Max-Age 的值,默认是 0,即关闭
CORS_LARAVEL_ALLOW_ROUTE_PERFIX string,路由匹配模式的匹配规则,因为使用 $request->is 进行检查,所以你可以参考 请求,默认是 *,代表所有路由。
CORS_LARAVEL_ROUTE_GROUP_MODE bool, 是否启用「单一路由中间件」或者「路由中间件组」模式,默认是不启用。

然后所有的配置如下(建议看一下 config/cors.php 文件每个配置的注释):

Key 描述
allow-credentiails 参考 CORS_ALLOW_CREDENTIAILS 配置
allow-headers 列出允许的 header 字段列表,默认是 ['*'] 代表全部,你可以设置例如「'[Content-Type', 'X-Requested-With']」只允许上面的两个 header 字段,具体根据你的项目而定。一旦出现 * 成员,代表允许全部。
expose-headers 列出了哪些首部可以作为响应的一部分暴露给外部。默认为 []
origins 列出允许跨域的域名,默认是 ['*'], 代表设置为 *(只要出现 * 在列中都会返回 *),例如设置 ['https://laravel-china.org'],可以设置多个,程序会自动处理。
methods 列出允许跨域请求的方法列表,默认是 ['*'] 代表所有方法。
max-age 预检请求返回的结果是否可以被缓存,默认是 0 即代表不可以被缓存,单位是「秒」你可以设置允许预检请求结果缓存多久。
laravel.allow-route-perfix 参考 CORS_LARAVEL_ALLOW_ROUTE_PERFIX
laravel.route-group-mode 参考 CORS_LARAVEL_ROUTE_GROUP_MODE

使用

其实,你的 Laravel 程序依赖了这个medz/cors 你就不需要在进行任何代码修改,就可以直接使用了。因为 Laravel 有一个神奇的特点吧,就是发起 OPTIONS 预检请求的时候,执行的中间件只有「全局中间件」,即 app/Http/Kernel.phpprotected $middleware 所设置的中间件,所以针对 Laravel medz/cors 包自动在这里添加了 Medz\Cors\Laravel\Middleware\Cors 中间件。你发现,这个中间件如果你进行了一些配置,他是不会执行其他任何处理的。

如果你希望自定义全局中间件的执行顺序,可以手动把 Medz\Cors\Laravel\Middleware\Cors 添加到 $middleware 中。

路由组模式

这是解决 barryvdh/laravel-cors 的痛点之一吧,因为 barryvdh/laravel-cors 会给所有的路由都加上跨域信息设置,这真的不是在实际中需要的,我们想要的是特定的路由组,或者特定的路由才支持跨域,其他的路由无法进行跨域请求。所以针对 Laravel 的开发中,有了这个模式。

而组模式需要用到的中间件叫做 Medz\Cors\Laravel\Middleware\ShouldGroup,为了方便使用,你可以在 app/Http/Kernel.phpprotected $routeMiddleware 中给它取一个简短好记忆的别名,例如:

protected $routeMiddleware = [
    'cors-should' => \Medz\Cors\Laravel\Middleware\ShouldGroup::class,
];

我给它取了一个名字叫做 cors-should,现在,你可以在特定的路由中设置允许跨域:

Route::middleware('cors-should')->get('test-cors', function () {});

当然,你也可以对一个路由组使用,和上面单个路由一样,参考 Laravel 的 Route 文档吧。

你还可以直接设置到中间件组,这样,只允许某写中间件组的 URI 允许跨域,例如 Laravel 默认的两个路由中间件组有 webapi 两个组,首先 web 组肯定不是我们想要跨域的,而 api 我们可能是完全的前后端分离开发,前端程序不在当前 API 服务器的域上,产生了跨域,我们可以直接给 api 组设置允许 api 组跨域:

protected $middlewareGroups = [
    /// ...
    'web' => [
        // ...
    ],
    'api' => [
        \Medz\Cors\Laravel\Middleware\ShouldGroup::class,
        // ...
    ],
];

当然,你给 Medz\Cors\Laravel\Middleware\ShouldGroup::class 设置了路由中间件别名的话(例如:chors-should),你可以:

protected $middlewareGroups = [
    /// ...
    'web' => [
        // ...
    ],
    'api' => [
        'cors-should',
        // ...
    ],
];

注意,路由组模式是会和「路由匹配模式」混合使用的

路由匹配模式

在「配置中」讲到了 ENV 常量 CORS_LARAVEL_ALLOW_ROUTE_PERFIX 或者配置文件中的 laravel.allow-route-perfix 是的,这就是路由匹配模式,默认是 * 即匹配所有。

路由匹配模式的匹配方法是使用 Laravel 中 Illumante\Http\Request::is 方法,文档请参考 Laravel 请求文档,所以设置的规则和 Laravel 请求问中 is 方法要求一致。例如,我们希望 api 前缀的路由才开启跨域:那么设置为:

api/*

即可,⚠️注意,路由匹配模式就像在 「路由组模式」提到了,会和「路由组模式」进行一起匹配。以 API 为例,我们使用「路由组模式」只允许 api 中间件组的路由允许跨域,同时我们设置 api/v2/* 的「路由匹配模式」规则,那么,允许跨于的只有 api/v2/* 的路由可以,例如 api/v1/* 也是 api 中间件组的路由,虽然组模式匹配上了,但是「路由匹配模式」的规则没有匹配上,所以 api/v1/* 的 路由是没有跨域允许信息的。

终结

其实 medz/cors 不只是在 Laravel 项目中使用,你可以用于任何 PHP 程序中,只是包只会内置几个主流框架的预设支持代码而已,你可以用 Array 模式来让所有的 PHP 程序都支持。

忘记的部分

因为一些语法原因,这个包只能用在 PHP >= 7.0 的版本之上,并且暂时不会进行语法妥协。

信息

GitHub 地址:https://github.com/medz/cors

嗯,新轮子,求一波 ?Star 。

Seven 的代码太渣,欢迎关注我的新拓展包 medz/cors 解决 PHP 项目程序设置跨域需求。
本帖已被设为精华帖!
本帖由 Summer 于 6年前 加精
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
讨论数量: 22
medz

@Seaony 是利用一个在线生成器,叫做 carbon 做的,可以生产漂亮的代码图片。

6年前 评论

按照楼主的说明来,但是依然不支持跨域,大家提供的几种跨域请求方法都试过了。但是依然不行。如果我直接在中间键写死header头的话,可以跨域成功。但是因为我依赖了easywechat。所以会返回symfony错误。但是按搜索到的方法,对symfony类与request类进行判断,又提示跨域失败。现在只能nginx设置header头了。

5年前 评论

楼主,请教下,我安装之后在公司电脑可以运行,家里电脑就报错,有遇到过这种情况吗?

Request header field access-control-allow-origin is not allowed by Access-Control-Allow-Headers in preflight response.

3年前 评论
ShibaPipi 3年前
devin_feng (作者) 3年前
liunian-zy 2年前

已 Star,好奇封面图是如何制作的

6年前 评论

我安装了没效果,有人遇到过吗 ,并且好像没执行到这个中间件

3年前 评论

想看封面,错过了。。。

3年前 评论

这个单词拼写错误把我搞疯了快,之前用的旧版本的,然后今天升级新版本了,配置文件没有重新发布导致一直跨域失败。 :triumph: :triumph: :triumph: 谁能想到是这种错误!

3年前 评论
medz (楼主) 3年前

@medz 是的,今天我看到了。没仔细看文档。我再添加到中间件,在路由中使用好像多此一举了。安装好包就自动实现处理了。

3年前 评论
medz

安装即自动设置好了的。

3年前 评论

我没有添加到全局会组里面也没有在路由中执行这个中间件。为什么好像自动执行了

3年前 评论

file

好像是有拼写错误 ,不知道有没有影响

4年前 评论
StringKe

疑似单词拼写错误 laravel.allow-route-perfix prefix

4年前 评论

已star。大佬在使用中出现BUG?明明前端提交的是post请求,但是报错The GET method is not supported for this route. Supported methods: POST.
怎么就变成了get请求了?

4年前 评论
medz (楼主) 4年前
carveybunt (作者) 4年前

如果你能讲讲原理,或者实现的核心代码,那就更好了

5年前 评论
medz (楼主) 4年前
medz

@isalone 使用了 easywechat 因为 response 和 request 都不是支持的实例,你是用 array 的方式,然后附加到 response 的 headers 即可

5年前 评论

@medz 找到原因了, ajax 中的 url 使用 Laravel 的 {{ url('/login') }} 就报419错误, 如果 ajax 的 url 写成 '/login' 就没事

5年前 评论
medz

@ainpy 加上中间件才报 419 还是不加也报?

5年前 评论

我为什么一直报 419 错误呢? ajax post 请求, 已经加上了 csrf

5年前 评论
medz

@Summer 哈哈,是有点呢,在 电脑网页上看着却是有点大。

6年前 评论
Summer

封面代码有点太大了,不是很和谐

6年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!