Laravel + JWT 實現 API 跨域授權

renfan發表於2018-06-30

簡介配置 Laravel 5.6 + JWT + AngularJS 配置 Api 跨域授權訪問,以及實現 Token 重新整理。
因內容較多,涉及較廣,這裡只能概括的講一下了。

安裝配置

JWT

詳細配置過程,請檢視官方文件。

  • 安裝 tymondesigns/jwt-auth
    composer require tymon/jwt-auth "1.0.0-rc.2"
  • 生成key php artisan jwt:secret
  • User 實現 JWTSubject 介面
  • 修改 auth.php 配置
    'api' => [
            'driver' => 'jwt',
            'provider' => 'users',
        ],
  • 一個【授權 + 重新整理】 的中介軟體
    新增到 app/Http/Kernel.php

    protected $routeMiddleware = [
    ...
    'refresh.token' => \App\Http\Middleware\RefreshToken::class
    ...
    ];

    RefreshToken 中介軟體

    class RefreshToken extends BaseMiddleware
    {
    public function handle($request, Closure $next)
    {
        // 檢查此次請求中是否帶有 token,如果沒有則丟擲異常。
        $this->checkForToken($request);
        Log::debug($request->headers->all());
        // 使用 try 包裹,以捕捉 token 過期所丟擲的 TokenExpiredException  異常
        try {
            // 檢測使用者的登入狀態,如果正常則透過
            if ($this->auth->parseToken()->authenticate()) {
                return $next($request);
            }
            $token = $this->auth->refresh();
            // 使用一次性登入以保證此次請求的成功
    
        } catch (TokenExpiredException $exception) {
            // 此處捕獲到了 token 過期所丟擲的 TokenExpiredException 異常,我們在這裡需要做的是重新整理該使用者的 token 並將它新增到響應頭中
            try {
                // 重新整理使用者的 token
                $token = $this->auth->refresh();
                // 使用一次性登入以保證此次請求的成功
                Auth::guard('api')->onceUsingId($this->auth->manager()->getPayloadFactory()->buildClaimsCollection()->toPlainArray()['sub']);
            } catch (JWTException $exception) {
                // 如果捕獲到此異常,即代表 refresh 也過期了,使用者無法重新整理令牌,需要重新登入。
                throw new UnauthorizedHttpException('jwt-auth', $exception->getMessage());
            }
        }
        // 在響應頭中返回新的 token
        return $this->setAuthenticationHeader($next($request), $token);
    }
    }
    
  • 幾個關鍵的配置
    • JWT_TTL Token的有效時間,單位:分,建議設定 20 min ~ 1 h。
    • JWT_REFRESH_TTL=1440 重新整理後的 Token 有效時間,單位:分,建議設定在 7 ~ 30 day。
    • JWT_BLACKLIST_GRACE_PERIOD 防止併發請求導致某些請求失效,單位:秒,建議設定 10 ~ 60 s。

跨域

詳細配置過程,請檢視官方文件。

  • 安裝 barryvdh/laravel-cors
  • 配置 cors
    在配置檔案 cors.php 中,設定 exposedHeaders,不然無法跨域獲取到 header 中的 Authorization 值來重新整理前端令牌。
    'exposedHeaders' => ['Authorization'],

後端 Laravel

登入

  • 登入 Api 介面
    驗證賬號密碼,返回 Token,我這裡使用的 username 欄位。
    Api返回,自己找一個返回success、error、message 的 Trait 工具就可以了,沒必要用 dingo。

    class LoginController extends ApiController
    {
    use AuthenticatesUsers;
    
    public function login(LoginRequest $request)
    {
        $credentials = $this->credentials($request);
        if ($token = auth('api')->attempt($credentials)) {
            $user = auth('api')->user();
            return $this->success(
                [
                    'user' => $user,
                    'token' => $token,
                ]);
        }
        return $this->failed('賬號或密碼錯誤!');
    }
    
    public function username()
    {
        return 'username';
    }
    }
  • 路由
    Api 介面都要使用 api 中介軟體,控制器最好放在 \Api 名稱空間下。
    Route::middleware('api')->prefix('api/user')->namespace('Modules\Account\Http\Controllers\Api')->group(function () {
            Route::post('login', 'LoginController@login')->name('user.login');
    });
  • 給需要授權的介面新增中介軟體,比如獲取使用者列表 getList
    public function __construct(UserRepository $repository)
    {
        $this->middleware('refresh.token')->only([
            'getList', 'get', 'update'
        ]);
        $this->repository = $repository;
    }

前端 Angular

Token

在登入授權時,將返回的 token 儲存在 TokenService 中進行管理。

this.tokenService.set({
          token: res.data.token
 });

Interceptor

攔截器將每一個請求的頭中放入 Authorization 資訊。

setReq(req: HttpRequest<any>, options: DelonAuthConfig): HttpRequest<any> {
    return req.clone({
      setHeaders: {
        Authorization: `Bearer ${this.model.token}`,
      },
    });
  }

Refresh Token

如果返回的 Response 的 Header 中有 Authorization 資訊,說明請求中帶的 Token 已經過期,伺服器將 重新整理的 Token 放在了 Response 頭中。前端獲取後更新 Token 。

 let newAuth = event.headers.get('Authorization');
      if (newAuth) {
          let token = newAuth.slice(7);
          this.tokenService.set(Object.assign(this.tokenService.get(), {
              token: token,
          }));
      }
本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章