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 编码值序列。
这是一个 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 和用于前端单页应用程序 (SPA) 示例的 AngularJS。 (您可以在此处找到整个演示,以及此 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: '/' });
在这里我们可以看到我们已经定义了四个由HomeController
或RestrictedController
处理的路由。 每个路由对应一个部分 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
文件中,我们为我们的应用程序定义了两个控制器: HomeController
和RestrictedController
。 HomeController
处理登录、注册和注销功能。 它将登录和注册表单中的用户名和密码数据传递给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
服务上的getRestrictedData
和getApiData
函数来获取数据。
.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、身份验证和自动化测试