本文概述
- 什么是JSON Web Token?
- JSON Web Token的结构
- JWT安全性和加密
- 为什么需要Web Token?
- 使用Laravel 5和AngularJS的JSON Web Token示例
- 除此JSON Web Token教程之外
随着单页应用程序, 移动应用程序和RESTful API服务的日益普及, Web开发人员编写后端代码的方式已发生了巨大变化。借助AngularJS和BackboneJS之类的技术, 我们不再花太多时间在构建标记上, 而是在构建前端应用程序使用的API。我们的后端更多地与业务逻辑和数据有关, 而表示逻辑则专门移至前端或移动应用程序。这些变化导致了在现代应用程序中实现身份验证的新方法。
身份验证是任何Web应用程序中最重要的部分之一。几十年来, cookie和基于服务器的身份验证是最简单的解决方案。但是, 在现代移动和单页应用程序中处理身份验证可能很棘手, 因此需要更好的方法。 API身份验证问题的最著名解决方案是OAuth 2.0和JSON Web Token(JWT)。
在进入本JSON Web Token教程之前, JWT到底是什么?
什么是JSON Web Token?
JSON Web Token用于发送可以通过数字签名验证和信任的信息。它包含一个紧凑且URL安全的JSON对象, 该对象经过密码签名以验证其真实性, 并且如果有效负载包含敏感信息, 也可以对其进行加密。
由于其结构紧凑, JWT通常用于HTTP授权标头或URL查询参数中。
JSON Web Token的结构
JWT表示为一系列由base64url编码的值, 这些值之间用句点字符分隔。
这是一个JWT令牌示例:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJpc3MiOiJ0b3B0YWwuY29tIiwiZXhwIjoxNDI2NDIwODAwLCJodHRwOi8vdG9wdGFsLmNvbS9qd3RfY2xhaW1zL2lzX2FkbWluIjp0cnVlLCJjb21wYW55IjoiVG9wdGFsIiwiYXdlc29tZSI6dHJ1ZX0.
yRQYnWzskCZUxPwaQupWkiUzKELZ49eM7oWxAQK_ZXw
标头
标头包含令牌的元数据, 并且最少包含签名的类型和加密算法。 (你可以使用JSON格式化程序工具来装饰JSON对象。)
标题示例
{
"alg": "HS256", "typ": "JWT"
}
此JWT示例标头声明编码对象为JSON Web Token, 并使用HMAC SHA-256算法对其进行签名。
一旦这是base64编码的, 我们就拥有了JWT的第一部分。
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
有效载荷(索赔)
在JWT的上下文中, 声明可以定义为关于实体(通常是用户)的声明以及关于令牌本身的其他元数据。声明包含我们要传输的信息, 服务器可以用来正确处理JSON Web Token身份验证。我们可以提供多种索赔;其中包括注册的索赔名称, 公共索赔名称和私人索赔名称。
已注册的JWT索赔
这些是在IANA JSON Web Token声明注册表中注册的声明。这些JWT声明不是强制性的, 而是为一组有用的可互操作的声明提供起点。
这些包括:
- iss:令牌的发行者
- 子:令牌的主题
- aud:令牌的受众
- exp:Unix时间中定义的JWT到期时间
- nbf:”不早于”时间, 该时间表示不得接受JWT进行处理的时间
- iat:在Unix时间的”发行时间”, 在该时间发行令牌
- jti:JWT ID声明为JWT提供了唯一的标识符
公开声明
公开声明需要使用防撞名称。通过将名称命名为URI或URN, 可以避免发送方和接收方不属于封闭网络的JWT的命名冲突。
公开声明名称的示例可以是:https://www.srcmini02.com/jwt_claims/is_admin, 最佳做法是在该位置放置一个描述声明的文件, 以便可以将其取消引用以备参考。
私人索赔
私有声明名称可以用在仅在已知系统之间(例如企业内部)的封闭环境中交换JWT的地方。这些是我们可以定义自己的声明, 例如用户ID, 用户角色或任何其他信息。
在封闭或专用系统之外使用可能具有冲突语义的声明名称可能会发生冲突, 因此请谨慎使用它们。
重要的是要注意, 我们要使Web Token尽可能小, 因此只能在公共和私人声明中使用必要的数据。
JWT示例有效负载
{
"iss": "srcmini02.com", "exp": 1426420800, "https://www.srcmini02.com/jwt_claims/is_admin": true, "company": "srcmini", "awesome": true
}
该示例有效负载具有两个注册的索赔, 一个公共索赔和两个私有索赔。一旦它经过base64编码, 我们便有了JWT的第二部分。
eyJpc3MiOiJ0b3B0YWwuY29tIiwiZXhwIjoxNDI2NDIwODAwLCJodHRwOi8vdG9wdGFsLmNvbS9qd3RfY2xhaW1zL2lzX2FkbWluIjp0cnVlLCJjb21wYW55IjoiVG9wdGFsIiwiYXdlc29tZSI6dHJ1ZX0
签名
JWT标准遵循JSON Web签名(JWS)规范来生成最终的签名令牌。它是通过组合编码的JWT标头和编码的JWT有效负载并使用强大的加密算法(例如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调用。
为什么需要Web Token?
在我们看到使用JWT身份验证的所有好处之前, 我们必须了解一下过去进行身份验证的方式。
基于服务器的身份验证
由于HTTP协议是无状态的, 因此需要一种用于存储用户信息的机制, 以及一种在登录后针对每个后续请求对用户进行身份验证的方法。大多数网站使用Cookie来存储用户的会话ID。
这个怎么运作
浏览器向服务器发出包含用户标识和密码的POST请求。服务器以cookie进行响应, 该cookie在用户的浏览器上设置, 并包含用于标识用户的会话ID。
在每个后续请求中, 服务器都需要找到该会话并将其反序列化, 因为用户数据存储在服务器上。
基于服务器的身份验证的缺点
-
难以扩展:服务器需要为用户创建会话并将其持久保存在服务器上的某个位置。这可以在内存或数据库中完成。如果我们有分布式系统, 则必须确保使用不与应用程序服务器耦合的单独的会话存储。
-
跨域请求共享(CORS):使用AJAX调用从另一个域获取资源(“跨域”)时, 我们可能会遇到禁止请求的问题, 因为默认情况下, HTTP请求在跨域请求中不包含Cookie原始请求。
-
与网络框架耦合:使用基于服务器的身份验证时, 我们将与我们框架的身份验证方案绑定在一起。在以不同编程语言编写的不同Web框架之间共享会话数据确实非常困难, 甚至是不可能的。
基于令牌的身份验证
基于令牌的/ JWT身份验证是无状态的, 因此无需在会话中存储用户信息。这使我们能够扩展应用程序, 而不必担心用户登录的位置。我们可以轻松地使用同一令牌从非我们登录的域中获取安全资源。
JSON Web Token如何工作
浏览器或移动客户端向身份验证服务器发出包含用户登录信息的请求。认证服务器生成一个新的JWT访问令牌, 并将其返回给客户端。在对受限资源的每个请求中, 客户端都会在查询字符串或Authorization标头中发送访问令牌。然后, 服务器验证令牌, 如果令牌有效, 则将安全资源返回给客户端。
身份验证服务器可以使用任何安全签名方法对令牌进行签名。例如, 如果存在在所有各方之间共享秘密密钥的安全通道, 则可以使用诸如HMAC SHA-256之类的对称密钥算法。另外, 也可以使用非对称的公钥系统, 例如RSA, 从而消除了进一步共享密钥的需求。
基于令牌的身份验证的优点
无状态, 易于扩展:令牌包含标识用户的所有信息, 无需会话状态。如果使用负载平衡器, 则可以将用户传递到任何服务器, 而不必绑定到我们登录的同一服务器。
可重用性:我们可以有许多单独的服务器, 它们在多个平台和域上运行, 并重复使用同一令牌来验证用户身份。构建与另一个应用程序共享权限的应用程序很容易。
JWT安全性:由于我们不使用Cookie, 因此不必防御跨站点请求伪造(CSRF)攻击。如果我们必须在令牌中放入任何敏感信息, 我们仍然应该使用JWE加密令牌, 并通过HTTPS传输令牌以防止中间人攻击。
性能:没有服务器端查找来查找和反序列化每个请求的会话。我们唯一要做的就是计算HMAC SHA-256以验证令牌并解析其内容。
使用Laravel 5和AngularJS的JSON Web Token示例
在本JWT教程中, 我将演示如何在两种流行的Web技术中使用JSON Web Token实现基本身份验证:Laravel 5用于后端代码, 而AngularJS用于前端单页应用程序(SPA)示例。 (你可以在此处找到整个演示, 以及此GitHub存储库中的源代码, 以便你可以随本教程一起学习。)
此JSON Web Token示例将不使用任何类型的加密来确保声明中传输的信息的机密性。实际上, 这通常是可以的, 因为TLS / SSL会对请求进行加密。但是, 如果令牌将包含敏感信息, 例如用户的社会保险号, 则也应使用JWE对其进行加密。
Laravel后端示例
我们将使用Laravel来处理用户注册, 将用户数据持久保存到数据库中, 并提供一些需要身份验证的受限数据, 以供Angular应用使用。我们还将创建一个示例API子域, 以模拟跨域资源共享(CORS)。
安装和项目自举
为了使用Laravel, 我们必须在计算机上安装Composer软件包管理器。在Laravel中进行开发时, 我建议使用Vagrant的Laravel Homestead预包装”盒子”。无论我们使用什么操作系统, 它都为我们提供了完整的开发环境。
引导我们的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 migration命令, 以便在数据库中创建必要的用户表。
安装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 provider数组中。
'Tymon\JWTAuth\Providers\JWTAuthServiceProvider'
接下来, 在app / config / app.php文件中的别名数组下, 我们添加JWTAuth门面。
'JWTAuth' => 'Tymon\JWTAuth\Facades\JWTAuth'
最后, 我们将要使用以下命令发布软件包配置:php artisan config:publish tymon / jwt-auth
JSON Web Token使用密钥进行加密。我们可以使用php artisan jwt:generate命令生成该密钥。它将放置在我们的config / jwt.php文件中。但是, 在生产环境中, 我们永远不想在配置文件中包含密码或API密钥。相反, 我们应该将它们放在服务器环境变量中, 并使用env函数在配置文件中引用它们。例如:
'secret' => env('JWT_SECRET')
我们可以在Github上找到有关此软件包及其所有配置设置的更多信息。
幼虫心
在composer.json中需要barryvdh / laravel-cors软件包并更新我们的依赖项。
composer require barryvdh/laravel-cors [email protected]
将CorsServiceProvider添加到我们的app / config / app.php provider数组中。
'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请求
为了简洁起见, 我将所有代码放入route.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网络令牌示例中, 我们将采用另一种方法来进行令牌验证。代替使用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服务器。转到项目首页后, 后端将提供资源/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创建了一个导航栏, 该导航栏将根据用户的登录状态更改相应链接的可见性。登录状态取决于控制器范围内是否存在令牌变量。
<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 Service上的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未经授权错误状态代码。
验证服务
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 Token教程之外
基于令牌的身份验证使我们能够构建不依赖于特定身份验证方案的解耦系统。令牌可以在任何位置生成, 并可以在使用相同密钥进行令牌签名的任何系统上使用。它们可以移动使用, 不需要我们使用cookie。
JSON Web Token可在所有流行的编程语言中使用, 并且正在迅速普及。它们得到了Google, Microsoft和Zendesk等公司的支持。 Internet工程任务组(IETF)制定的标准规范仍处于草案版本中, 将来可能会稍有变化。
关于JWT, 还有很多内容需要介绍, 例如如何处理安全性详细信息以及在令牌到期时刷新令牌, 但是JSON Web Token教程应演示基本用法, 更重要的是, 介绍使用JWT的优点。