JSON Web Token 教程:Laravel 和 AngularJS 中的示例

已發表: 2022-03-11

隨著單頁應用程序、移動應用程序和 RESTful API 服務的日益普及,Web 開發人員編寫後端代碼的方式發生了顯著變化。 借助 AngularJS 和 BackboneJS 等技術,我們不再花費大量時間構建標記,而是構建前端應用程序使用的 API。 我們的後端更多的是關於業務邏輯和數據,而表示邏輯則專門移至前端或移動應用程序。 這些變化導致了在現代應用程序中實現身份驗證的新方法。

身份驗證是任何 Web 應用程序中最重要的部分之一。 幾十年來,cookie 和基於服務器的身份驗證是最簡單的解決方案。 但是,在現代移動和單頁應用程序中處理身份驗證可能很棘手,需要更好的方法。 API 身份驗證問題的最著名解決方案是 OAuth 2.0 和 JSON Web Token (JWT)。

在我們進入這個 JSON Web Token 教程之前,JWT 到底是什麼?

什麼是 JSON Web 令牌?

JSON Web Token 用於發送可以通過數字簽名驗證和信任的信息。 它包含一個緊湊且 URL 安全的 JSON 對象,該對象經過加密簽名以驗證其真實性,如果有效負載包含敏感信息,也可以對其進行加密。

由於其結構緊湊,JWT 通常用於 HTTP Authorization標頭或 URL 查詢參數中。

JSON Web 令牌的結構

JWT 表示為由句點字符分隔的 base64url 編碼值序列。

laravel 和 angularjs 中的 JSON Web 令牌示例

這是一個 JWT 令牌示例:

 eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 . eyJpc3MiOiJ0b3B0YWwuY29tIiwiZXhwIjoxNDI2NDIwODAwLCJodHRwOi8vdG9wdGFsLmNvbS9qd3RfY2xhaW1zL2lzX2FkbWluIjp0cnVlLCJjb21wYW55IjoiVG9wdGFsIiwiYXdlc29tZSI6dHJ1ZX0 . yRQYnWzskCZUxPwaQupWkiUzKELZ49eM7oWxAQK_ZXw

標題

標頭包含令牌的元數據,它至少包含簽名類型和加密算法。 (您可以使用 JSON 格式化工具來美化 JSON 對象。)

示例標題

{ "alg": "HS256", "typ": "JWT" }

此 JWT 示例標頭聲明編碼對像是 JSON Web 令牌,並且使用 HMAC SHA-256 算法對其進行簽名。

一旦這是 base64 編碼,我們就有了 JWT 的第一部分。

 eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9

有效載荷(索賠)

在 JWT 的上下文中,聲明可以定義為關於實體(通常是用戶)的聲明,以及關於令牌本身的附加元數據。 聲明包含我們想要傳輸的信息,服務器可以使用這些信息正確處理 JSON Web Token 身份驗證。 我們可以提供多種索賠; 其中包括已註冊的權利要求名稱、公共權利要求名稱和私人權利要求名稱。

已註冊的 JWT 聲明

這些是在 IANA JSON Web 令牌聲明註冊表中註冊的聲明。 這些 JWT 聲明並不是強制性的,而是為一組有用的、可互操作的聲明提供一個起點。

這些包括:

  • iss : 代幣的發行者
  • sub : 令牌的主題
  • aud : 代幣的受眾
  • exp : 以 Unix 時間定義的 JWT 過期時間
  • nbf :“Not before”時間,標識在該時間之前不得接受 JWT 進行處理
  • iat :“發行時間”,在 Unix 時間中,令牌發行的時間
  • jti :JWT ID 聲明為 JWT 提供唯一標識符

公開索賠

公開聲明需要具有抗衝突的名稱。 通過將名稱設為 URI 或 URN,可以避免發送方和接收方不屬於封閉網絡的 JWT 的命名衝突。

公共聲明名稱的示例可能是: https://www.toptal.com/jwt_claims/is_admin ://www.toptal.com/jwt_claims/is_admin,最佳做法是在該位置放置一個描述聲明的文件,以便可以取消引用它以獲取文檔。

私人索賠

私有聲明名稱可用於 JWT 僅在已知系統之間的封閉環境中交換的地方,例如企業內部。 這些是我們可以定義自己的聲明,例如用戶 ID、用戶角色或任何其他信息。

在封閉或私有系統之外使用可能具有衝突語義的聲明名稱可能會發生衝突,因此請謹慎使用。

重要的是要注意,我們希望保持 Web 令牌盡可能小,因此僅在公共和私人聲明中使用必要的數據。

JWT 示例負載

{ "iss": "toptal.com", "exp": 1426420800, "https://www.toptal.com/jwt_claims/is_admin": true, "company": "Toptal", "awesome": true }

此示例有效負載有兩個已註冊的聲明,一個公共聲明和兩個私有聲明。 一旦它被 base64 編碼,我們就有了 JWT 的第二部分。

 eyJpc3MiOiJ0b3B0YWwuY29tIiwiZXhwIjoxNDI2NDIwODAwLCJodHRwOi8vdG9wdGFsLmNvbS9qd3RfY2xhaW1zL2lzX2FkbWluIjp0cnVlLCJjb21wYW55IjoiVG9wdGFsIiwiYXdlc29tZSI6dHJ1ZX0

簽名

JWT 標準遵循 JSON Web 簽名 (JWS) 規範來生成最終的簽名令牌。 它是通過組合編碼的 JWT Header 和編碼的 JWT Payload 生成的,並使用強加密算法(例如 HMAC SHA-256)對其進行簽名。 簽名的密鑰由服務器持有,因此它將能夠驗證現有令牌並簽署新令牌。

 $encodedContent = base64UrlEncode(header) + "." + base64UrlEncode(payload); $signature = hashHmacSHA256($encodedContent);

這給了我們 JWT 的最後一部分。

 yRQYnWzskCZUxPwaQupWkiUzKELZ49eM7oWxAQK_ZXw

JWT 安全和加密

將 TLS/SSL 與 JWT 結合使用以防止中間人攻擊至關重要。 在大多數情況下,如果 JWT 有效負載包含敏感信息,這將足以加密它。 但是,如果我們想添加額外的保護層,我們可以使用 JSON Web 加密 (JWE) 規範對 JWT 有效負載本身進行加密。

當然,如果我們想避免使用 JWE 的額外開銷,另一種選擇是簡單地將敏感信息保存在我們的數據庫中,並在我們需要訪問敏感數據時使用我們的令牌對服務器進行額外的 API 調用。

為什麼需要網絡令牌?

在我們看到使用 JWT 身份驗證的所有好處之前,我們必須看看過去的身份驗證方式。

基於服務器的身份驗證

基於服務器的身份驗證

因為 HTTP 協議是無狀態的,所以需要有一種機制來存儲用戶信息,以及在登錄後的每個後續請求中對用戶進行身份驗證的方法。 大多數網站使用 cookie 來存儲用戶的會話 ID。

這個怎麼運作

瀏覽器向服務器發出包含用戶標識和密碼的 POST 請求。 服務器以設置在用戶瀏覽器上的 cookie 進行響應,其中包括用於識別用戶的會話 ID。

在每個後續請求中,服務器都需要找到該會話並將其反序列化,因為用戶數據存儲在服務器上。

基於服務器的身份驗證的缺點

  • 難以擴展:服務器需要為用戶創建一個會話並將其保存在服務器的某個位置。 這可以在內存或數據庫中完成。 如果我們有一個分佈式系統,我們必須確保我們使用一個不耦合到應用程序服務器的單獨會話存儲。

  • 跨域請求共享(CORS) :當使用 AJAX 調用從另一個域(“跨域”)獲取資源時,我們可能會遇到禁止請求的問題,因為默認情況下,HTTP 請求不包括跨域的 cookie源請求。

  • 與 web 框架耦合:當使用基於服務器的身份驗證時,我們與框架的身份驗證方案相關聯。 在用不同編程語言編寫的不同 Web 框架之間共享會話數據真的很難,甚至是不可能的。

基於令牌的身份驗證

基於令牌的身份驗證

Token based/JWT 認證是無狀態的,所以不需要在 session 中存儲用戶信息。 這使我們能夠擴展我們的應用程序而無需擔心用戶登錄的位置。我們可以輕鬆地使用相同的令牌從我們登錄的域以外的域中獲取安全資源。

JSON Web 令牌的工作原理

瀏覽器或移動客戶端向包含用戶登錄信息的身份驗證服務器發出請求。 身份驗證服務器生成一個新的 JWT 訪問令牌並將其返回給客戶端。 在對受限資源的每個請求中,客戶端都會在查詢字符串或Authorization標頭中發送訪問令牌。 然後,服務器驗證令牌,如果有效,則將安全資源返回給客戶端。

身份驗證服務器可以使用任何安全簽名方法對令牌進行簽名。 例如,如果有一個安全通道可以在各方之間共享密鑰,則可以使用諸如 HMAC SHA-256 之類的對稱密鑰算法。 或者,也可以使用 RSA 等非對稱公鑰系統,從而無需進一步共享密鑰。

基於令牌的身份驗證的優勢

無狀態,更易於擴展:令牌包含識別用戶的所有信息,無需會話狀態。 如果我們使用負載均衡器,我們可以將用戶傳遞到任何服務器,而不是綁定到我們登錄的同一台服務器。

可重用性:我們可以有許多獨立的服務器,在多個平台和域上運行,重用相同的令牌來驗證用戶。 構建與另一個應用程序共享權限的應用程序很容易。

JWT 安全性:由於我們不使用 cookie,因此我們不必防範跨站請求偽造 (CSRF) 攻擊。 如果我們必須在其中放入任何敏感信息,我們仍然應該使用 JWE 加密我們的令牌,並通過 HTTPS 傳輸我們的令牌以防止中間人攻擊。

性能:沒有服務器端查找來查找和反序列化每個請求的會話。 我們唯一要做的就是計算 HMAC SHA-256 來驗證令牌並解析其內容。

使用 Laravel 5 和 AngularJS 的 JSON Web 令牌示例

在本 JWT 教程中,我將演示如何在兩種流行的 Web 技術中使用 JSON Web 令牌實現基本身份驗證:Laravel 5 用於後端代碼,AngularJS 用於前端單頁應用程序 (SPA) 示例。 (您可以在此處找到整個演示,以及此 GitHub 存儲庫中的源代碼,以便您可以按照教程進行操作。)

此 JSON Web 令牌示例將不使用任何類型的加密來確保聲明中傳輸的信息的機密性。 實際上,這通常沒問題,因為 TLS/SSL 會加密請求。 但是,如果令牌將包含敏感信息,例如用戶的社會安全號碼,則還應使用 JWE 對其進行加密。

Laravel 後端示例

我們將使用 Laravel 來處理用戶註冊,將用戶數據持久化到數據庫中,並提供一些需要身份驗證才能讓 Angular 應用程序使用的受限數據。 我們還將創建一個示例 API 子域來模擬跨域資源共享 (CORS)。

安裝和項目引導

為了使用 Laravel,我們必須在我們的機器上安裝 Composer 包管理器。 在 Laravel 中開發時,我推薦使用 Laravel Homestead 預打包的 Vagrant “盒子”。 無論我們的操作系統如何,它都為我們提供了一個完整的開發環境。

引導 JWT Laravel 應用程序的最簡單方法是使用 Composer 包 Laravel Installer。

 composer global require "laravel/installer=~1.1"

現在我們都準備好通過運行laravel new jwt創建一個新的 Laravel 項目了。

有關此過程的任何問題,請參閱 Laravel 官方文檔。

在我們創建了基本的 Laravel 5 應用程序之後,我們需要設置Homestead.yaml ,它將為我們的本地環境配置文件夾映射和域配置。

Homestead.yaml文件示例:

 --- ip: "192.168.10.10" memory: 2048 cpus: 1 authorize: /Users/ttkalec/.ssh/public.psk keys: - /Users/ttkalec/.ssh/private.ppk folders: - map: /coding/jwt to: /home/vagrant/coding/jwt sites: - map: jwt.dev to: /home/vagrant/coding/jwt/public - map: api.jwt.dev to: /home/vagrant/coding/jwt/public variables: - key: APP_ENV value: local

在我們使用vagrant up命令啟動 Vagrant 框並使用vagrant ssh後,我們導航到之前定義的項目目錄。 在上面的示例中,這將是/home/vagrant/coding/jwt 。 我們現在可以運行php artisan migrate命令,以便在我們的數據庫中創建必要的用戶表。

安裝 Composer 依賴項

幸運的是,有一個開發人員社區致力於 Laravel 並維護許多我們可以重用和擴展我們的應用程序的優秀包。 在這個例子中,我們將使用 Sean Tymon tymon/jwt-auth來處理服務器端的令牌,以及 Barry vd barryvdh/laravel-cors 。 Heuvel,用於處理 CORS。

jwt-身份驗證

在我們的composer.json中需要tymon/jwt-auth包並更新我們的依賴項。

 composer require tymon/jwt-auth 0.5.*

JWTAuthServiceProvider添加到我們的app/config/app.php提供程序數組中。

 'Tymon\JWTAuth\Providers\JWTAuthServiceProvider'

接下來,在app/config/app.php文件的aliases數組下,我們添加JWTAuth門面。

 'JWTAuth' => 'Tymon\JWTAuth\Facades\JWTAuth'

最後,我們將要使用以下命令發布包配置: php artisan config:publish tymon/jwt-auth

JSON Web 令牌使用密鑰進行加密。 我們可以使用php artisan jwt:generate命令生成該密鑰。 它將被放置在我們的config/jwt.php文件中。 然而,在生產環境中,我們絕不希望在配置文件中包含我們的密碼或 API 密鑰。 相反,我們應該將它們放在服務器環境變量中,並使用env函數在配置文件中引用它們。 例如:

 'secret' => env('JWT_SECRET')

我們可以在 Github 上找到有關此軟件包及其所有配置設置的更多信息。

laravel-cors

在我們的composer.json中需要barryvdh/laravel-cors包並更新我們的依賴項。

 composer require barryvdh/laravel-cors 0.4.x@dev

CorsServiceProvider添加到我們的app/config/app.php提供程序數組中。

 'Barryvdh\Cors\CorsServiceProvider'

然後將中間件添加到我們的app/Http/Kernel.php中。

 'Barryvdh\Cors\Middleware\HandleCors'

使用php artisan vendor:publish命令將配置發佈到本地config/cors.php文件。

cors.php文件配置示例:

 return [ 'defaults' => [ 'supportsCredentials' => false, 'allowedOrigins' => [], 'allowedHeaders' => [], 'allowedMethods' => [], 'exposedHeaders' => [], 'maxAge' => 0, 'hosts' => [], ], 'paths' => [ 'v1/*' => [ 'allowedOrigins' => ['*'], 'allowedHeaders' => ['*'], 'allowedMethods' => ['*'], 'maxAge' => 3600, ], ], ];

路由和處理 HTTP 請求

為簡潔起見,我將把所有代碼放在 routes.php 文件中,該文件負責 Laravel 路由和將請求委託給控制器。 我們通常會創建專門的控制器來處理我們所有的 HTTP 請求,並保持我們的代碼模塊化和乾淨。

我們將使用加載我們的 AngularJS SPA 視圖

Route::get('/', function () { return view('spa'); });

用戶註冊

當我們使用用戶名和密碼向/signup發出POST請求時,我們將嘗試創建一個新用戶並將其保存到數據庫中。 創建用戶後,會創建一個 JWT,並通過 JSON 響應返回。

 Route::post('/signup', function () { $credentials = Input::only('email', 'password'); try { $user = User::create($credentials); } catch (Exception $e) { return Response::json(['error' => 'User already exists.'], HttpResponse::HTTP_CONFLICT); } $token = JWTAuth::fromUser($user); return Response::json(compact('token')); });

用戶登錄

當我們使用用戶名和密碼向/signin發出POST請求時,我們會驗證用戶是否存在並通過 JSON 響應返回 JWT。

 Route::post('/signin', function () { $credentials = Input::only('email', 'password'); if ( ! $token = JWTAuth::attempt($credentials)) { return Response::json(false, HttpResponse::HTTP_UNAUTHORIZED); } return Response::json(compact('token')); });

獲取同一域上的受限資源

一旦用戶登錄,我們就可以獲取受限資源。 我創建了一個路由/restricted來模擬需要經過身份驗證的用戶的資源。 為此,請求Authorization標頭或查詢字符串需要提供 JWT 以供後端驗證。

 Route::get('/restricted', [ 'before' => 'jwt-auth', function () { $token = JWTAuth::getToken(); $user = JWTAuth::toUser($token); return Response::json([ 'data' => [ 'email' => $user->email, 'registered_at' => $user->created_at->toDateTimeString() ] ]); } ]);

在此示例中,我使用jwt-auth包中提供的jwt-auth中間件,使用'before' => 'jwt-auth' 。 該中間件用於過濾請求並驗證 JWT 令牌。 如果令牌無效、不存在或過期,中間件將拋出我們可以捕獲的異常。

在 Laravel 5 中,我們可以使用app/Exceptions/Handler.php文件來捕獲異常。 使用render函數,我們可以根據拋出的異常創建 HTTP 響應。

 public function render($request, Exception $e) { if ($e instanceof \Tymon\JWTAuth\Exceptions\TokenInvalidException) { return response(['Token is invalid'], 401); } if ($e instanceof \Tymon\JWTAuth\Exceptions\TokenExpiredException) { return response(['Token has expired'], 401); } return parent::render($request, $e); }

如果用戶通過身份驗證並且令牌有效,我們可以安全地將受限數據通過 JSON 返回到前端。

從 API 子域獲取受限資源

在下一個 JSON Web 令牌示例中,我們將採用不同的方法進行令牌驗證。 我們將手動處理異常,而不是使用jwt-auth中間件。 當我們向 API 服務器api.jwt.dev/v1/restricted發出POST請求時,我們正在發出跨域請求,並且必須在後端啟用 CORS。 幸運的是,我們已經在config/cors.php文件中配置了 CORS。

 Route::group(['domain' => 'api.jwt.dev', 'prefix' => 'v1'], function () { Route::get('/restricted', function () { try { JWTAuth::parseToken()->toUser(); } catch (Exception $e) { return Response::json(['error' => $e->getMessage()], HttpResponse::HTTP_UNAUTHORIZED); } return ['data' => 'This has come from a dedicated API subdomain with restricted access.']; }); });

AngularJS 前端示例

我們使用 AngularJS 作為前端,依靠對 Laravel 後端身份驗證服務器的 API 調用進行用戶身份驗證和示例數據,以及 API 服務器用於跨域示例數據。 一旦我們進入項目的主頁,後端將提供resources/views/spa.blade.php視圖,該視圖將引導 Angular 應用程序。

這是 Angular 應用程序的文件夾結構:

 public/ |-- css/ `-- bootstrap.superhero.min.css |-- lib/ |-- loading-bar.css |-- loading-bar.js `-- ngStorage.js |-- partials/ |-- home.html |-- restricted.html |-- signin.html `-- signup.html `-- scripts/ |-- app.js |-- controllers.js `-- services.js

引導 Angular 應用程序

spa.blade.php包含運行應用程序所需的基本要素。 我們將使用 Twitter Bootstrap 以及來自 Bootswatch 的自定義主題進行樣式設置。 為了在進行 AJAX 調用時獲得一些視覺反饋,我們將使用 angular-loading-bar 腳本,該腳本攔截 XHR 請求並創建一個加載欄。 在標題部分,我們有以下樣式表:

 <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css"> <link rel="stylesheet" href="/css/bootstrap.superhero.min.css"> <link rel="stylesheet" href="/lib/loading-bar.css">

我們標記的頁腳包含對庫的引用,以及我們用於 Angular 模塊、控制器和服務的自定義腳本。

 <script src="http://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script> <script src="http://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/js/bootstrap.min.js"></script> <script src="http://cdnjs.cloudflare.com/ajax/libs/angular.js/1.3.14/angular.min.js"></script> <script src="http://cdnjs.cloudflare.com/ajax/libs/angular.js/1.3.14/angular-route.min.js"></script> <script src="/lib/ngStorage.js"></script> <script src="/lib/loading-bar.js"></script> <script src="/scripts/app.js"></script> <script src="/scripts/controllers.js"></script> <script src="/scripts/services.js"></script> </body>

我們正在使用 AngularJS 的ngStorage庫,將令牌保存到瀏覽器的本地存儲中,以便我們可以通過Authorization標頭在每個請求上發送它。

當然,在生產環境中,我們會縮小並合併所有腳本文件和样式表以提高性能。

我使用 Bootstrap 創建了一個導航欄,它將根據用戶的登錄狀態更改相應鏈接的可見性。 登錄狀態取決於控制器範圍內是否存在token變量。

 <div class="navbar-header"> <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target=".navbar-collapse"> <span class="sr-only">Toggle navigation</span> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </button> <a class="navbar-brand" href="#">JWT Angular example</a> </div> <div class="navbar-collapse collapse"> <ul class="nav navbar-nav navbar-right"> <li data-ng-show="token"><a ng-href="#/restricted">Restricted area</a></li> <li data-ng-hide="token"><a ng-href="#/signin">Sign in</a></li> <li data-ng-hide="token"><a ng-href="#/signup">Sign up</a></li> <li data-ng-show="token"><a ng-click="logout()">Logout</a></li> </ul> </div>

路由

我們有一個名為app.js的文件,它負責配置我們所有的前端路由。

 angular.module('app', [ 'ngStorage', 'ngRoute', 'angular-loading-bar' ]) .constant('urls', { BASE: 'http://jwt.dev:8000', BASE_API: 'http://api.jwt.dev:8000/v1' }) .config(['$routeProvider', '$httpProvider', function ($routeProvider, $httpProvider) { $routeProvider. when('/', { templateUrl: 'partials/home.html', controller: 'HomeController' }). when('/signin', { templateUrl: 'partials/signin.html', controller: 'HomeController' }). when('/signup', { templateUrl: 'partials/signup.html', controller: 'HomeController' }). when('/restricted', { templateUrl: 'partials/restricted.html', controller: 'RestrictedController' }). otherwise({ redirectTo: '/' });

在這裡我們可以看到我們已經定義了四個由HomeControllerRestrictedController處理的路由。 每個路由對應一個部分 HTML 視圖。 我們還定義了兩個常量,其中包含我們對後端的 HTTP 請求的 URL。

請求攔截器

AngularJS 的 $http 服務允許我們與後端通信並發出 HTTP 請求。 在我們的例子中,我們希望攔截每個 HTTP 請求,並在用戶通過身份驗證時將其註入包含 JWT 的Authorization標頭。 我們還可以使用攔截器來創建全局 HTTP 錯誤處理程序。 這是我們的攔截器的一個示例,如果它在瀏覽器的本地存儲中可用,它會注入一個令牌。

 $httpProvider.interceptors.push(['$q', '$location', '$localStorage', function ($q, $location, $localStorage) { return { 'request': function (config) { config.headers = config.headers || {}; if ($localStorage.token) { config.headers.Authorization = 'Bearer ' + $localStorage.token; } return config; }, 'responseError': function (response) { if (response.status === 401 || response.status === 403) { $location.path('/signin'); } return $q.reject(response); } }; }]);

控制器

controllers.js文件中,我們為我們的應用程序定義了兩個控制器: HomeControllerRestrictedControllerHomeController處理登錄、註冊和註銷功能。 它將登錄和註冊表單中的用戶名和密碼數據傳遞給Auth服務,該服務將 HTTP 請求發送到後端。 然後它將令牌保存到本地存儲,或顯示錯誤消息,具體取決於後端的響應。

 angular.module('app') .controller('HomeController', ['$rootScope', '$scope', '$location', '$localStorage', 'Auth', function ($rootScope, $scope, $location, $localStorage, Auth) { function successAuth(res) { $localStorage.token = res.token; window.location = "/"; } $scope.signin = function () { var formData = { email: $scope.email, password: $scope.password }; Auth.signin(formData, successAuth, function () { $rootScope.error = 'Invalid credentials.'; }) }; $scope.signup = function () { var formData = { email: $scope.email, password: $scope.password }; Auth.signup(formData, successAuth, function () { $rootScope.error = 'Failed to signup'; }) }; $scope.logout = function () { Auth.logout(function () { window.location = "/" }); }; $scope.token = $localStorage.token; $scope.tokenClaims = Auth.getTokenClaims(); }])

RestrictedController的行為方式相同,只是它通過使用Data服務上的getRestrictedDatagetApiData函數來獲取數據。

 .controller('RestrictedController', ['$rootScope', '$scope', 'Data', function ($rootScope, $scope, Data) { Data.getRestrictedData(function (res) { $scope.data = res.data; }, function () { $rootScope.error = 'Failed to fetch restricted content.'; }); Data.getApiData(function (res) { $scope.api = res.data; }, function () { $rootScope.error = 'Failed to fetch restricted API content.'; }); }]);

僅當用戶通過身份驗證時,後端才負責提供受限數據。 這意味著為了響應受限數據,對該數據的請求需要在其Authorization標頭或查詢字符串中包含有效的 JWT。 如果不是這種情況,服務器將響應 401 Unauthorized 錯誤狀態代碼。

認證服務

Auth 服務負責向後端發出登錄和註冊 HTTP 請求。 如果請求成功,則響應包含簽名令牌,然後對其進行 base64 解碼,並且將隨附的令牌聲明信息保存到tokenClaims變量中。 這通過getTokenClaims函數傳遞給控制器。

 angular.module('app') .factory('Auth', ['$http', '$localStorage', 'urls', function ($http, $localStorage, urls) { function urlBase64Decode(str) { var output = str.replace('-', '+').replace('_', '/'); switch (output.length % 4) { case 0: break; case 2: output += '=='; break; case 3: output += '='; break; default: throw 'Illegal base64url string!'; } return window.atob(output); } function getClaimsFromToken() { var token = $localStorage.token; var user = {}; if (typeof token !== 'undefined') { var encoded = token.split('.')[1]; user = JSON.parse(urlBase64Decode(encoded)); } return user; } var tokenClaims = getClaimsFromToken(); return { signup: function (data, success, error) { $http.post(urls.BASE + '/signup', data).success(success).error(error) }, signin: function (data, success, error) { $http.post(urls.BASE + '/signin', data).success(success).error(error) }, logout: function (success) { tokenClaims = {}; delete $localStorage.token; success(); }, getTokenClaims: function () { return tokenClaims; } }; } ]);

數據服務

這是一個簡單的服務,它向身份驗證服務器以及 API 服務器發出一些虛擬受限數據的請求。 它發出請求,並將成功和錯誤回調委託給控制器。

 angular.module('app') .factory('Data', ['$http', 'urls', function ($http, urls) { return { getRestrictedData: function (success, error) { $http.get(urls.BASE + '/restricted').success(success).error(error) }, getApiData: function (success, error) { $http.get(urls.BASE_API + '/restricted').success(success).error(error) } }; } ]);

超越這個 JSON Web 令牌教程

基於令牌的身份驗證使我們能夠構建不依賴於特定身份驗證方案的解耦系統。 令牌可以在任何地方生成並在使用相同密鑰簽署令牌的任何系統上使用。 它們可以移動,並且不需要我們使用 cookie。

JSON Web Tokens 適用於所有流行的編程語言,並且正在迅速普及。 他們得到了谷歌、微軟和 Zendesk 等公司的支持。 由 Internet 工程任務組 (IETF) 制定的標準規範仍處於草案版本中,未來可能會略有變化。

關於 JWT 仍有很多內容需要介紹,例如如何處理安全細節,以及在令牌過期時刷新令牌,但 JSON Web 令牌教程應該展示基本用法,更重要的是,展示使用 JWT 的優勢。

進一步閱讀 Toptal 工程博客:

  • 構建 Node.js/TypeScript REST API,第 3 部分:MongoDB、身份驗證和自動化測試