Laravel 解决 ajax 跨域问题 2 - 带头(Authorization)跨域
之前整理过 Laravel 通过中间件解决 ajax 跨域问题 教程,但最近开发一个公众号领红包的小功能依然翻了车。
当前的允许跨域中间件
AccessControlAllowOrigin.php
/**
* @param $request
* @param Closure $next
* @return mixed
* @throws ApiException
*/
public function handle($request, Closure $next)
{
$response = $next($request);
$origin = $request->server('HTTP_ORIGIN') ? $request->server('HTTP_ORIGIN') : '';
Log::info('[AccessControlAllowOrigin] [current origin]' . json_encode($request->server()));
$allow_origin = [
'xx.com'
];
if (in_array($origin, $allow_origin)) {
$response->header('Access-Control-Allow-Origin', $origin);
$response->header('Access-Control-Allow-Headers', 'Origin, Content-Type, Cookie, X-CSRF-TOKEN, Accept, Authorization, X-XSRF-TOKEN');
$response->header('Access-Control-Expose-Headers', 'Authorization, authenticated');
$response->header('Access-Control-Allow-Methods', 'GET, POST, PATCH, PUT, OPTIONS');
$response->header('Access-Control-Allow-Credentials', 'true');
}
return $response;
}
之前的版本里允许所有域名访问,明显不安全,这次增加了 Origin
头部来源域名判断。其他增加的部分属于景上添花,header
设置主要关键点还是在 headers
和 methods
部分。
回到问题本身,经测试普通的接口在添加了跨域头 cors
(别称)后是可以访问了,但携带了 Authorization
头部的接口浏览器会有两次请求,一次是 options
预检请求,另一次是正式请求。因为需要验证身份,所以还使用了 api
中间件,路由定义如下:
Route::group([
'middleware' => ['cors', 'api']
], function(Router $router) {
$router->match(['get'],'/userInfo', 'UserController@UserInfo')->name('api.users.UserInfo');
});
结果就是 options
401 未通过 api
(中间调试多次,也出现过 options
请求过了,但正式请求没过,显示跨域的情况)。并且 options
请求响应头部也没有携带设置的参数,从日志显示分析请求并没有经过 cors
中间件。
这就很奇怪,明明 cors
中间件在前,api
在后。对比两个中间件发现了问题:cors
是在 $next($request)
之后处理请求,而 api
是在 $next($request)
之前处理请求;cors
是后置中间件,api
是前置中间件。也就是说一个请求过来会先执行 api
中间件,再处理请求,最后执行 cors
中间件。所以这两个中间件执行顺序是固定的。
问题就出在这个固定的顺序上,options
请求是不携带参数的,它根本通过不了 api
中间件,然后返回了 401。当时并没有注意到这一点,反复调试了几天都没搞好,搞的整个人都不自信了。
确认了问题的所在,解决方法也就简单了。之前的 Laravel 通过中间件解决 ajax 跨域问题 教程里「需要注意的事项」部分也提到了这一点,我自己给忘了。那就是单独处理 options
请求,设置允许其跨域。因为有多条这样的请求,那么可以在所有的路由请求之前定义一条通用版的 options
路由:
Route::group([
'middleware' => ['cors']
], function(Router $router) {
Route::options('/{all}', function(\Illuminate\Http\Request $request) {
// $origin = $request->header('ORIGIN', '*');
// header("Access-Control-Allow-Origin: $origin");
// header("Access-Control-Allow-Credentials: true");
// header('Access-Control-Allow-Methods: POST, GET, OPTIONS, PUT, DELETE');
// header('Access-Control-Allow-Headers: Origin, Access-Control-Request-Headers, SERVER_NAME, Access-Control-Allow-Headers,
// cache-control, token, X-Requested-With, Content-Type, Accept, Connection, User-Agent, Cookie, Authorization');
})->where(['all' => '([a-zA-Z0-9-]|/)+']);
});
中间注释掉的部分是另外一个老哥写的路由直接处理 options
跨域的 demo,可以在不使用路由组搭配中间件的情况下单独使用。但因为调试过程中前端报错说没有返回 200 ok 状态,就没有采纳,有兴趣的小伙伴可以单独测试看看。
本作品采用 知识共享署名-相同方式共享 4.0 国际许可协议 进行许可。