因為跨域問題導致的無法讀取 response header

Outlaws發表於2018-12-11

昨天臨下班的時候發現一個問題,公司系統在操作過程中老退出登入。

專案前端是 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:

相關文章