因为跨域问题导致的无法读取 response header

昨天临下班的时候发现一个问题,公司系统在操作过程中老退出登录。

项目前端是 angular 后端 laravel,认证用的是 JWT,登录失效问题一定出在 token 上,所以先去检查后端验证代码。

try {
    // 检查此次请求中是否带有 token,如果没有则抛出异常。
    $this->checkForToken($request);

    // 检测用户的登录状态,如果正常则通过
    if ($this->auth->parseToken()->authenticate()) {
        return $next($request);
    }
    throw new UnauthorizedHttpException('jwt-auth', '未登录');
} catch (TokenExpiredException $exception) {
    try {
        \Auth::guard('api')->onceUsingId(
            $this->auth->manager()->getPayloadFactory()->buildClaimsCollection()->toPlainArray()['sub']
        );
        $token = $this->auth->refresh();
        return $this->setAuthenticationHeader($next($request), $token);
    } catch (JWTException $exception) {
        /****************************************
         * 如果捕获到此异常,即代表 refresh 也过期了,
         * 用户无法刷新令牌,需要重新登录。
         ****************************************/
        return $this->response->withUnauthorized()->fail(1, $exception->getMessage());
    }
}

看起来没有什么太大的问题,把 .env 中 JWT_TTL 改成 1 分钟后调试,发现接口能够正常返回 token

file
那么继续去检查前端的代码,前端是通过 angular 的拦截器处理的token,当发现 response header 中有带有 token 则用新的 token 替代旧的。

// 如果返回的信息里带有刷新的token则更换现有token
if (event.headers.has('Authorization')) {
  const userToken = event.headers.get('Authorization');
  this._login.refreshToken(userToken);
}

/**
 * 刷新token,在当前token过期时后台允许使用该token换取一个新的token以继续使用,保证用户的连续登录
 *
 * @param {string} newToken
 * @memberof AuthService
 */
public refreshToken(newToken: string): void {
    const [tokenType, token] = newToken.split(' ');
    this._cookies.put('appToken', token);
    this._cookies.put('tokenType', tokenType);
}

查到这里的时候发现在浏览器里明明返回的响应中有 Authorization 这个 header 但代码中就是读取不到。

纠结了一个晚上,最后查 MDN 时发现跨域中需要带上 Access-Control-Expose-Headers, 才能让客户端访问到服务端返回的 header 。

MDN 中是这么描述的:

The Access-Control-Expose-Headers response header indicates which headers can be exposed as part of the response by listing their names.

By default, only the 6 simple response headers are exposed:

  • Cache-Control
  • Content-Language
  • Content-Type
  • Expires
  • Last-Modified
  • Pragma

If you want clients to be able to access other headers, you have to list them using the Access-Control-Expose-Headers header.

这就很尴尬,最终解决是如果使用 barryvdh/laravel-cors 这个包的话,只需要在 cors.php 配置文件中把前端需要读取的 header 暴露出来就可以了。

return [

    /*
    |--------------------------------------------------------------------------
    | Laravel CORS
    |--------------------------------------------------------------------------
    |
    | allowedOrigins, allowedHeaders and allowedMethods can be set to array('*')
    | to accept any value.
    |
    */

    'supportsCredentials' => false,
    'allowedOrigins' => ['*'],
    'allowedOriginsPatterns' => [],
    'allowedHeaders' => ['*'],
    'allowedMethods' => ['*'],
    'exposedHeaders' => ['Authorization'],
    'maxAge' => 0,

];

还是书读得太少。:kissing:

本作品采用《CC 协议》,转载必须注明作者和本文链接
《L04 微信小程序从零到发布》
从小程序个人账户申请开始,带你一步步进行开发一个微信小程序,直到提交微信控制台上线发布。
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
讨论数量: 2
mushu

正好遇到一样的问题 :+1:

4年前 评论

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