1. 程式人生 > >laravel 5.7 安裝 jwt-auth,jwt-auth 文件翻譯

laravel 5.7 安裝 jwt-auth,jwt-auth 文件翻譯

laravel 5.7 安裝 jwt-auth(預設安裝的是 0.5.12 版本)

github 地址:
	https://github.com/tymondesigns/jwt-auth

舊版文件:
	https://github.com/tymondesigns/jwt-auth/wiki

新版文件:
	https://jwt-auth.readthedocs.io/en/develop/(這個是 1.0.0 版本的文件)

安裝(Installation)
	安裝包
		composer require tymon/jwt-auth

	新增服務提供者
		編輯 config/app.php,在 "providers" 新增:
			'Tymon\JWTAuth\Providers\JWTAuthServiceProvider',

	新增 Facades
		編輯 config/app.php,在 "alias" 新增:
		'JWTAuth' => 'Tymon\JWTAuth\Facades\JWTAuth',
		'JWTFactory' => 'Tymon\JWTAuth\Facades\JWTFactory',

	釋出配置檔案:
		php artisan vendor:publish --provider="Tymon\JWTAuth\Providers\JWTAuthServiceProvider"

	生成 secret key
		php artisan jwt:generate

	安裝完成後,執行 php artisan jwt:generate,會報錯:
		1.Method Tymon\JWTAuth\Commands\JWTGenerateCommand::handle() does not exist
			解決方法,這個需要修改原始碼:
				編輯 vendor/tymon/jwt-auth/src/Commands/JWTGenerateCommand.php,新增:
				    /**
				     * Compatiblity with Laravel >= 5.5
				     */
				    public function handle()
				    {
				        $this->fire();
				    }

		2.如果未新增服務提供者,直接執行該命令,可能也會報錯!
			There are no commands defined in the "jwt" namespace
			解決方法:
				就是上面的新增服務提供者

配置(Configuration)
	secret(secret key) - 祕鑰
		用來簽名 token 的祕鑰。作者將祕鑰與 Laravel 的 APP_KEY 分開,以便開發者可以獨立地修改它們。
		提供了一個 artisan 命令,為我們生成一個隨機祕鑰。(php artisan jwt:generate)

	ttl(token time to live) - token 生存時間
		token 的有效時間,以分鐘為單位。建議儘可能設定短點,尤其是當我們也使用 token 重新整理機制。

	refresh_ttl(refresh time to live) - refresh 生存時間
		可以重新整理 token 的有效時間,以分鐘為單位。例如,如果設定為 2周,那麼只能在 2周 內,重新整理對應的 token,否則將會丟擲 TokenExpiredException 異常。如果超過了重新整理的有效時間,必須生成一個全新的 token,這意味著使用者需要重新登入。

	注:ttl 和 refresh_ttl,用於保持使用者的登入狀態

	algo(hashing algorithm) - hash 演算法
		用於簽名 token 的演算法,保留預設值即可

	user(user model path) - 使用者模型路徑
		應該指向我們專案的 User 類的名稱空間路徑

	identifier(user identifier) - 使用者標識
		從 token 的主題宣告中,根據什麼標識來檢索使用者(一般是 id)

	required_claims(required claims)
		這些宣告必須存在於 token 的 payload 中,否則將丟擲 TokenInvalidException 異常(會檢測 token 的 payload 是否存在這些宣告)

	blacklist_enabled(blacklist enabled)
		如果設定為 false,將無法使 token 失效。雖然我們仍然可以重新整理令牌,但是之前的令牌仍舊有效,因此這樣做非常不安全。但對於非常簡單的實現,可能不需要額外的開銷(重新整理 token 等),我們可以配置它。

	providers
		jwt-auth 包已經有一些具體實現,可用來實現各種需求。只要遵循相關介面,我們就可以覆蓋這些具體實現。
	providers.user
		指定基於主題宣告,來查詢使用者的實現。

	providers.jwt
		完成 token 的編碼和解碼的繁重工作

	providers.auth
		通過憑證或 id 來認證使用者

	providers.storage
		用於驅動黑名單,並存儲 token 直到過期。

建立 tokens(Creating Tokens)
	jwt-auth 包為我們提供了建立 token 的多種方法。有簡單的方法,如果你想更好的控制,也有更進一步的方法。

	開箱即用(out of box),有許多必須的宣告,雖然這些都可以配置:
		sub(Subject) - 包含 token 的識別符號(預設是使用者 ID)
		iat(Issued At) - token 釋出時間(unix 時間戳)
		exp(Expiry) - token 過期日期(unix 時間戳)
		nbf(Not Before) - 可以使用 token 的最早時間點(unix 時間戳)
		iss(Issuer) - token 釋出者(預設為請求的 url)
		jti(JWT Id) - token 的唯一識別符號(sub 和 iat 宣告的 md5 值)
		aud(Audience) - token 的目標受眾(預設不需要)
	也允許自定義宣告。稍後會介紹。

	建立一個基於使用者憑證的 token
		建立 token 的最常用方法是,通過使用者的登入憑證,來認證使用者。如果認證成功,則返回一個與該使用者相關的 token。例如,假設我們有一個 Laravel AuthenticateController

			use JWTAuth;
			use Tymon\JWTAuth\Exceptions\JWTException;

			class AuthenticateController extends Controller
			{
			    public function authenticate(Request $request)
			    {
			        // grab credentials from the request
			        $credentials = $request->only('email', 'password');

			        try {
			            // attempt to verify the credentials and create a token for the user
			            if (! $token = JWTAuth::attempt($credentials)) {
			                return response()->json(['error' => 'invalid_credentials'], 401);
			            }
			        } catch (JWTException $e) {
			            // something went wrong whilst attempting to encode the token
			            return response()->json(['error' => 'could_not_create_token'], 500);
			        }

			        // all good so return the token
			        return response()->json(compact('token'));
			    }
			}

	建立一個基於使用者物件的 token
		我們可以跳過使用者認證,只傳遞一個使用者物件

			$user = User::first();
			$token = JWTAuth::fromUser($user);

		上面的 2 個方法也有第二個引數,可以傳遞一個 '自定義宣告' 的陣列

			$customClaims = ['foo' => 'bar', 'baz' => 'bob'];
			JWTAuth::attempt($credentials, $customClaims);
			JWTAuth::fromUser($user, $customClaims);

		在解碼 token 時,這些自定義宣告,將和其他宣告一起提供。
		注意:新增大量的自定義宣告,將增加 token 的大小

	建立一個基於任意你喜歡的內容的 token
		作者給我們提供了對底層類和方法的訪問,來提供高階的、可自定義的功能。
		示例使用了內建的 'Tymon\JWTAuth\PayloadFactory' 例項(或者使用 JWTFactory 門面):

			$customClaims = ['foo' => 'bar', 'baz' => 'bob'];
			$payload = JWTFactory::make($customClaims);
			$token = JWTAuth::encode($payload);

		也可以在 'Tymon\JWTAuth\PayloadFactory' 例項上鍊式呼叫宣告(或者使用 JWTFactory 門面):

			$payload = JWTFactory::sub(123)->aud('foo')->foo(['bar' => 'baz']);
			$token = JWTAuth::encode($payload);

認證(Authentication)
	一旦使用者使用他們的憑證登入,下一步將使用 token 發起一個後續請求,來檢索使用者詳情,以便我們可以將其顯示為已登入。

	使用內建方法,通過 http 發起認證請求,我們需要設定一個 Authorization 請求頭,如下所示:
		Authorization: Bearer {yourtokenhere}

	Apache 使用者需要注意:
	Apache 好像會丟棄 Authorization 請求頭,如果該請求頭不是 base64 編碼的 user/pass 組合。為了解決此問題,我們可以在 apache 配置檔案中新增一下內容:
		RewriteEngine On
		RewriteCond %{HTTP:Authorization} ^(.*)
		RewriteRule .* - [e=HTTP_AUTHORIZATION:%1]

	或者,我們可以通過在查詢字串中包含 token 來實現:
		http://api.mysite.com/me?token={yourtokenhere}

	為了從請求中獲取 token,我們可以:
		// 會設定 token 到返回的物件中
		JWTAuth::parseToken();
		// 接著,我們可以繼續鏈式呼叫方法
		$user = JWTAuth::parseToken()->authenticate();

	為了獲取 token 的值,我們可以呼叫:
		$token = JWTAuth::getToken();
	如果設定了一個 token,則會返回 token,否則(為方便起見),它將使用上述方法,嘗試從請求中解析 token,如果沒有設定 token 或 沒有 token 可以被解析,最終返回 false。

	當然,如果在我們的程式中有其他入口點,我們也可以根據需要手動設定 token。例如:
		JWTAuth::setToken('foo.bar.baz');

	從 token 中檢索認證過的使用者
		public function getAuthenticatedUser()
		{
			try {
				if(! $user = JWTAuth::parseToken()->authenticate()){
					return response()->json('user_not_found', 404);
				}
			} catch (Tymon\JWTAuth\Exceptions\TokenExpiredException $e) {
				return response()->json(['token_expired'], $e->getStatusCode());
			} catch (Tymon\JWTAuth\Exceptions\TokenInvalidException $e) {
				return response()->json(['token_invalid'], $e->getStatusCode());
			} catch (Tymon\JWTAuth\Exceptions\JWTException $e) {
				return response()->json(['token_absent'], $e->getStatusCode());
			}
			return response()->json(compact('user'));
		}

	如果不喜歡內聯捕獲多個異常的方法,我們可以隨意使用 Laravel 新增全域性異常處理程式。

	在 app/Exceptions/Handler.php 中,將下面程式碼新增到 render() 方法:
		public function render($request, Exception $e)
		{
			if ($e instanceof Tymon\JWTAuth\Exceptions\TokenExpiredException) {
				return response()->json(['token_expired'], $e->getStatusCode());
			} else if ($e instanceof Tymon\JWTAuth\Exceptions\TokenInvalidException) {
				return response()->json(['token_invalid'], $e->getStatusCode());
			}

			return parent::render($request, $e);
		}

	中介軟體和過濾器
		如果我們使用的是 Laravel 5,可以使用內建的 2 箇中間件:
			GetUserFromToken
				檢查請求頭和查詢字串(正如上面解釋過的)是否存在 token,並嘗試解碼 token。如上所述,同樣的事件被觸發。

			RefreshToken
				此中介軟體將再次嘗試從請求中解析 token,然後將重新整理 token(從而使舊 token 失效),並將其作為下一次響應的一部分返回。這實際上產生了單個使用 token 流,如果 token 被洩露,這種方式會減少攻擊,因為它僅對單個請求有效。

		為了使用這 2 箇中間件,我們需要將它們註冊到 app/Http/Kernel.php 裡的 $routeMIddleware 屬性:
			protected $routeMiddleware = [
				...
				'jwt.auth' => 'Tymon\JWTAuth\Middleware\GetUserFromToken',
				'jwt.refresh' => 'Tymon\JWTAuth\Middleware\RefreshToken',
			];

附 2 篇優秀文章(沒看,但應該不錯):
	https://laravel-chiona.org/articles/10885/full-use-of-jwt
	https://laravel-china.org/articles/17883


-----------------------------------------------------------------------------
後記:
	發現不指定版本號,安裝的是 0.5.12 版本
	我們專案中,打算使用最新版:1.0.0-rc.3

	composer require tymon/jwt-auth:1.0.0-rc.3

	文件地址:
		https://jwt-auth.readthedocs.io/en/develop/

	翻譯:
		Laravel 安裝:
			通過 composer 安裝:
				composer require tymon/jwt-auth:1.0.0-rc.3

			新增服務提供者:
				編輯 config/app.php,在 "providers" 新增:
					Tymon\JWTAuth\Providers\LaravelServiceProvider::class,

			釋出配置檔案:
				php artisan vendor:publish --provider="Tymon\JWTAuth\Providers\LaravelServiceProvider"

			生成 secret key:
				php artisan jwt:secret
				.env 檔案下,會新增 JWT_SECRET

		快速開始:
			更新使用者模型:
				User 模型,實現 Tymon\JWTAuth\Contracts\JWTSubject,並新增 getJWTIdentifier() 和 getJWTCustomClaims() 2 個方法

				例如:
					<?php

					namespace App;

					use Tymon\JWTAuth\Contracts\JWTSubject;		// 這個
					use Illuminate\Notifications\Notifiable;
					use Illuminate\Foundation\Auth\User as Authenticatable;

					class User extends Authenticatable implements JWTSubject 		// 這個
					{
					    use Notifiable;

					    // Rest omitted for brevity

					    /**
					     * Get the identifier that will be stored in the subject claim of the JWT.
					     *
					     * @return mixed
					     */
					    public function getJWTIdentifier()		// 這個
					    {
					        return $this->getKey();
					    }

					    /**
					     * Return a key value array, containing any custom claims to be added to the JWT.
					     *
					     * @return array
					     */
					    public function getJWTCustomClaims() 	// 這個
					    {
					        return [];
					    }
					}

			配置 Auth guard:
				config/auth.php
				'defaults' => [
				    'guard' => 'api',			// 使用 api guard
				    'passwords' => 'users',
				],

				...

				'guards' => [
				    'api' => [					// 定義 api guard
				        'driver' => 'jwt',		// 使用 jwt
				        'provider' => 'users',
				    ],
				],

			中介軟體:
				auth:api

			控制器內使用:
				<?php

				namespace App\Http\Controllers;

				use Illuminate\Support\Facades\Auth;
				use App\Http\Controllers\Controller;

				class AuthController extends Controller
				{
				    /**
				     * Create a new AuthController instance.
				     *
				     * @return void
				     */
				    public function __construct()
				    {
				        $this->middleware('auth:api', ['except' => ['login']]);		// 中介軟體
				    }

				    /**
				     * Get a JWT via given credentials.
				     *
				     * @return \Illuminate\Http\JsonResponse
				     */
				    public function login()
				    {
				        $credentials = request(['email', 'password']);

				        if (! $token = auth()->attempt($credentials)) {			// api 認證,通過後得到 token
				            return response()->json(['error' => 'Unauthorized'], 401);
				        }

				        return $this->respondWithToken($token);					// 返回 token 給客戶端
				    }

				    /**
				     * Get the authenticated User.
				     *
				     * @return \Illuminate\Http\JsonResponse
				     */
				    public function me()
				    {
				        return response()->json(auth()->user());		// 通過 api 認證得到使用者資訊
				    }

				    /**
				     * Log the user out (Invalidate the token).
				     *
				     * @return \Illuminate\Http\JsonResponse
				     */
				    public function logout()
				    {
				        auth()->logout();		// api 退出

				        return response()->json(['message' => 'Successfully logged out']);
				    }

				    /**
				     * Refresh a token.
				     *
				     * @return \Illuminate\Http\JsonResponse
				     */
				    public function refresh()
				    {
				        return $this->respondWithToken(auth()->refresh());		// 重新整理 token,返回給客戶端
				    }

				    /**
				     * Get the token array structure.
				     *
				     * @param  string $token
				     *
				     * @return \Illuminate\Http\JsonResponse
				     */
				    protected function respondWithToken($token)
				    {
				        return response()->json([
				            'access_token' => $token,
				            'token_type' => 'bearer',
				            'expires_in' => auth()->factory()->getTTL() * 60
				        ]);
				    }
				}

			認證請求:
				通過 HTTP 給伺服器傳送 token,有以下幾種方法:
					認證頭:
						Authorization: Bearer eyJhbGciOiJIUzI1NiI...

					查詢字串引數:
						http://example.dev/me?token=eyJhbGciOiJIUzI1NiI...

					Post 引數:
						暫無

					Cookies:
						暫無

					Laravel 路由引數:
						暫無

		Auth Guard:
			Auth Guard 例項上,可用以下方法:
				attempt() - 通過使用者身份憑證,來認證使用者
					$token = auth()->attempt($credentials);
					成功返回 jwt token,失敗返回 null

				login() - 傳遞一個使用者例項,來登入使用者
					$user = User::first();
					$token = auth()->login($user);

				user() - 獲取當前認證的使用者
					$user = auth()->user();
					如果使用者未認證,返回 null

				userOrFail() - 獲取當前認證的使用者,否則丟擲異常
					try {
					    $user = auth()->userOrFail();
					} catch (\Tymon\JWTAuth\Exceptions\UserNotDefinedException $e) {
					    // do something
					}

				logout() - 使用者退出,會使當前 token 失效,並刪除認證的使用者
					auth()->logout();

					// 第一個引數傳遞 true,強制 token 新增到 "forever" 黑名單
					auth()->logout(true);

				refresh() - 重新整理 token,會令當前 token 失效,生成一個新 token
					$newToken = auth()->refresh();

					// 第一個引數傳遞 true,強制此 token 新增到 "forever" 黑名單
					// 第二個引數傳遞 true,會重置新 token 的宣告(claims)
					$newToken = auth()->refresh(true, true);

				invalidate() - 使當前 token 失效(新增到黑名單)
					auth()->invalidate();

					// 第一個引數傳遞 true,強制 token 新增到 "forever" 黑名單
					auth()->invalidate(true);

				tokenById() - 基於給定使用者 ID,獲取 token
					$token = auth()->tokenById(32451);

				payload() - 獲取原生的 JWT payload
					$payload = auth()->payload();

					// 之後可以直接訪問宣告
					$payload->get('sub');		// 123
					$payload['jti'];			// 'xxx'
					$payload('exp');			// 123456
					$payload->toArray();		// ['sub' => 123, 'exp' => 123456, 'jti' => 'xxx']

				validate() - 認證使用者身份憑證
					if(auth()->validate($credentials)){
						// 認證通過
					}

			進一步使用:
				新增自定義宣告:
					$token = auth()->claims(['foo' => 'bar'])->attempt($credentials);

				顯示設定 token:
					$user = auth()->setToken('xxx')->user();

				顯示設定請求例項:
					$user = auth()->setRequest($request)->user();

				覆蓋 token ttl:
					$token = auth()->setTTL(7200)->attempt($credentials);

		配置:
			和舊版本的配置不同,官方還未提供文件,看 config/jwt.php 檔案註釋即可