作者都是各自领域经过审查的专家,并撰写他们有经验的主题. 我们所有的内容都经过同行评审,并由同一领域的Toptal专家验证.
蒂诺·特卡莱克的头像

Tino Tkalec

Tino是一名软件工程师,在创建本机Windows和Web应用程序方面拥有10多年的经验. He’s a LAMP stack expert.

Years of Experience

14

Share

随着单页应用程序的日益普及, mobile applications, and RESTful API services, the way web developers 编写后端代码发生了重大变化. 使用AngularJS和BackboneJS等技术, 我们不再花很多时间建立加价, 相反,我们正在构建前端应用程序使用的api. 我们的后端更多的是关于业务逻辑和数据, 而表示逻辑则专门移动到前端或移动应用程序. 这些变化导致了在现代应用程序中实现身份验证的新方法.

身份验证是任何web应用程序中最重要的部分之一. 几十年来,cookie和基于服务器的身份验证是最简单的解决方案. However, 在现代移动和单页应用程序中处理身份验证可能很棘手, 并要求一个更好的方法. 对于api的身份验证问题,最著名的解决方案是 OAuth 2.0 and the JSON Web Token (JWT).

在我们进入这个JSON Web令牌教程之前,了解一下JWT到底是什么?

What is a JSON Web Token?

JSON Web令牌用于发送可以通过数字签名进行验证和信任的信息. 它包含一个紧凑且url安全的JSON对象, 这是加密签名,以验证其真实性, 如果有效载荷包含敏感信息,也可以加密.

由于其结构紧凑,JWT通常用于HTTP Authorization 头或URL查询参数.

JSON Web令牌的结构

JWT表示为一个序列 base64url 以句点字符分隔的编码值.

laravel和angularjs中的JSON web令牌示例

下面是一个JWT令牌示例:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJpc3MiOiJ0b3B0YWwuY29tIiwiZXhwIjoxNDI2NDIwODAwLCJodHRwOi8vdG9wdGFsLmNvbS9qd3RfY2xhaW1zL2lzX2FkbWluIjp0cnVlLCJjb21wYW55IjoiVG9wdGFsIiwiYXdlc29tZSI6dHJ1ZX0.
yRQYnWzskCZUxPwaQupWkiUzKELZ49eM7oWxAQK_ZXw

报头包含令牌的元数据,并且最少包含签名类型和加密算法. (You can use a JSON formatter 工具来美化JSON对象.)

Example Header

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

这个JWT示例头声明编码的对象是一个JSON Web Token, 并且使用HMAC SHA-256算法进行签名.

一旦它被base64编码,我们就有了JWT的第一部分.

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9

Payload (Claims)

In the context of JWT, 权利要求可以定义为关于实体(通常是实体)的陈述, the user), 以及关于令牌本身的附加元数据. 声明包含了我们想要传递的信息, 并且服务器可以使用它来正确处理JSON Web令牌身份验证. There are multiple claims we can provide; these include registered claim names, 公共索赔名称和私人索赔名称.

Registered JWT Claims

这些都是登记在 IANA JSON Web令牌声明注册表. 这些JWT声明并不是强制性的,而是为一组有用的声明提供了一个起点, interoperable claims.

These include:

  • iss:令牌的发行者
  • sub:令牌的主题
  • aud:代币的受众
  • exp: JWT过期时间,以Unix时间定义
  • nbf:“不在此之前”时间,该时间标识在此之前JWT不得被接受处理
  • iat:“发出时间”,在Unix时间中,令牌发出的时间
  • jti: JWT ID声明提供了JWT的唯一标识符

Public Claims

公共声明需要具有抗碰撞的名称. 通过使名称成为URI或URN, 对于发送方和接收方不属于封闭网络的jwt,可以避免命名冲突.

公共索赔名称的示例如下: http://r0jm.wxzjnt.com/jwt_claims/is_admin, 最佳实践是在该位置放置一个描述权利要求的文件,以便可以对其进行文档化引用.

Private Claims

私有声明名称可用于仅在已知系统之间的封闭环境中交换jwt的地方, 比如在企业内部. 这些是我们可以定义自己的声明,如用户id、用户角色或任何其他信息.

在封闭或私有系统之外使用可能具有冲突语义的声明名可能会发生冲突, so use them with caution.

重要的是要注意,我们希望保持一个web令牌尽可能小, 因此,在公共和私人声明中只使用必要的数据.

JWT Example Payload

{
  "iss": "wxzjnt.com",
  "exp": 1426420800,
  "http://r0jm.wxzjnt.com/jwt_claims/is_admin”:没错,
  "company": "Toptal",
  "awesome": true
}

此示例有效负载有两个已注册的索赔,一个公共索赔和两个私有索赔. 一旦它被base64编码,我们就有了JWT的第二部分.

eyJpc3MiOiJ0b3B0YWwuY29tIiwiZXhwIjoxNDI2NDIwODAwLCJodHRwOi8vdG9wdGFsLmNvbS9qd3RfY2xhaW1zL2lzX2FkbWluIjp0cnVlLCJjb21wYW55IjoiVG9wdGFsIiwiYXdlc29tZSI6dHJ1ZX0

Signature

JWT标准遵循JSON Web签名(JWS)规范来生成最终的签名令牌. 它是通过组合编码的JWT报头和编码的JWT有效负载生成的, 并使用强大的加密算法进行签名, such as HMAC SHA-256. 签名的秘钥由服务器持有,因此它将能够验证现有令牌并签署新令牌.

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

这就是JWT的最后一部分.

yRQYnWzskCZUxPwaQupWkiUzKELZ49eM7oWxAQK_ZXw

JWT安全性和加密

将TLS/SSL与JWT结合使用是防止中间人攻击的关键. In most cases, 如果JWT有效负载包含敏感信息,这将足以加密它. However, 如果我们想增加额外的保护层, 方法加密JWT有效负载本身 JSON Web Encryption (JWE) specification.

Of course, 如果我们想避免使用JWE的额外开销, 另一种选择是简单地将敏感信息保存在数据库中, 并在需要访问敏感数据时使用我们的令牌对服务器进行额外的API调用.

为什么需要网络令牌?

在我们看到使用JWT身份验证的所有好处之前, 我们必须看看过去进行身份验证的方式.

基于服务器的身份验证

基于服务器的身份验证

因为HTTP协议是无状态的, 需要有一种存储用户信息的机制,以及在登录后的每个后续请求中对用户进行身份验证的方法. 大多数网站使用cookie来存储用户的会话ID.

How it Works

浏览器向包含用户标识和密码的服务器发出POST请求. 服务器响应一个cookie, 在用户的浏览器上设置哪个, 并包括用于标识用户的会话ID.

对于以后的每一次请求, 服务器需要找到该会话并对其进行反序列化, 因为用户数据存储在服务器上.

基于服务器的身份验证的缺点

  • Hard to scale:服务器需要为用户创建一个会话,并将其持久化到服务器的某个地方. 这可以在内存或数据库中完成. 如果我们有一个分布式系统, 我们必须确保使用独立的会话存储,而不是与应用服务器耦合.

  • 跨域请求共享(CORS):当使用AJAX调用从另一个域(“跨域”)获取资源时,我们可能会遇到禁止请求的问题,因为, by default, HTTP请求在跨域请求中不包含cookie.

  • 与web框架的耦合当使用基于服务器的身份验证时,我们被绑定到框架的身份验证方案中. It is really hard, or even impossible, 在用不同编程语言编写的不同web框架之间共享会话数据.

基于令牌的身份验证

基于令牌的身份验证

基于令牌/JWT的身份验证是无状态的, 因此不需要在会话中存储用户信息. 这使我们能够扩展应用程序,而不必担心用户在哪里登录. 我们可以很容易地使用相同的令牌从我们登录的域以外的域获取安全资源.

How JSON Web Tokens Work

浏览器或移动客户端向包含用户登录信息的身份验证服务器发出请求. 身份验证服务器生成一个新的JWT访问令牌并将其返回给客户机. 在对受限制资源的每次请求中,客户端在查询字符串或中发送访问令牌 Authorization header. 然后,服务器验证令牌,如果令牌有效,则将安全资源返回给客户机.

身份验证服务器可以使用任何安全签名方法对令牌进行签名. For example, 如果有一个安全通道在各方之间共享密钥,则可以使用HMAC SHA-256等对称密钥算法. Alternatively, an asymmetric, public-key system, such as RSA, can be used as well, 消除了进一步共享密钥的需要.

基于令牌的身份验证的优点

无状态,更容易扩展:令牌包含标识用户的所有信息, 消除了对会话状态的需要. 如果我们使用负载均衡器, 我们可以将用户传递到任何服务器, 而不是绑定到我们登录的同一台服务器.

Reusability我们可以有很多独立的服务器, 在多个平台和域上运行, 重用相同的令牌对用户进行身份验证. 构建与另一个应用程序共享权限的应用程序是很容易的.

JWT Security既然我们不使用饼干, 我们不需要防止跨站点请求伪造(CSRF)攻击. 如果我们必须在令牌中放入任何敏感信息,我们仍然应该使用JWE加密令牌, 并通过HTTPS传输我们的令牌以防止中间人攻击.

Performance:服务器端不需要查找和反序列化每个请求的会话. 我们唯一要做的就是计算HMAC SHA-256来验证令牌并解析其内容.

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

在本JWT教程中,我将演示如何在两种流行的Web技术中使用JSON Web令牌实现基本身份验证:后端代码为Laravel 5,前端单页应用程序(SPA)示例为AngularJS. (您可以在 this GitHub repository 这样你就可以跟着教程走了.)

这个JSON web令牌示例不会使用任何类型的加密来确保声明中传输的信息的机密性. 在实践中,这通常是可以的,因为TLS/SSL对请求进行加密. However, 如果令牌包含敏感信息, 例如用户的社会保险号, 它也应该使用JWE加密.

Laravel Backend Example

我们将使用Laravel来处理用户注册, 将用户数据持久化到数据库中,并提供一些受限制的数据,这些数据需要经过认证才能被Angular应用使用. 我们还将创建一个示例API子域来模拟跨域资源共享(CORS).

安装和项目引导

为了使用Laravel,我们必须安装 Composer 我们机器上的包管理器. 当在Laravel中开发时,我建议使用Vagrant的Laravel Homestead预包装“盒子”. 它为我们提供了一个完整的开发环境,而不管我们的操作系统是什么.

引导我们的JWT Laravel应用程序的最简单的方法是使用一个Composer包Laravel Installer.

Composer全局要求“laravel/installer=~1”.1"

现在我们已经准备好通过运行来创建一个新的Laravel项目 laravel new jwt.

有关此流程的任何问题请咨询官方 Laravel documentation.

在创建了基本的Laravel 5应用程序之后,我们需要设置我们的 Homestead.yaml,它将为本地环境配置文件夹映射和域配置.

Example of a Homestead.yaml file:

---
ip: "192.168.10.10"
memory: 2048
cpus: 1

用户授权:/ / ttkalec /.ssh/public.psk

keys:
    - /Users/ttkalec/.ssh/private.ppk
folders:
    - map: /coding/jwt
      : /home/vagrant/coding/jwt
sites:
    - map: jwt.dev
      : /home/vagrant/coding/jwt/public
    - map: api.jwt.dev
      : /home/vagrant/coding/jwt/public
variables:
    - key: APP_ENV
      value: local

等我们用 vagrant up 命令,并使用 vagrant ssh,我们导航到前面定义的项目目录. 在上面的例子中,这将是 /home/vagrant/coding/jwt. We can now run php artisan migrate 命令,以便在数据库中创建必要的用户表.

安装Composer依赖项

Fortunately, 有一个开发Laravel的开发者社区,他们维护着许多很棒的包,我们可以使用这些包来重用和扩展我们的应用程序. 在本例中,我们将使用 tymon/jwt-auth,由Sean Tymon编写,用于在服务器端处理令牌 barryvdh/laravel-cors, by Barry vd. Heuvel用于处理CORS.

jwt-auth

Require the tymon/jwt-auth package in our composer.json 更新我们的依赖项.

编写器需要tymon/jwt-auth 0.5.* 

Add the JWTAuthServiceProvider to our app/config/app.php providers array.

“Tymon \ JWTAuth \ \ JWTAuthServiceProvider提供者”

Next, in app/config/app.php file, under the aliases array, we add the JWTAuth facade.

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

最后,我们将使用以下命令发布包配置: PHP工匠配置:发布tymon/jwt-auth

JSON Web令牌使用秘密密钥进行加密. 我们可以使用 php artisan jwt:generate command. 它将被放置在我们的 config/jwt.php file. 在生产环境中, however, 我们永远不希望将密码或API密钥放在配置文件中. Instead, 我们应该将它们放在服务器环境变量中,并在配置文件中使用 env function. For example:

'secret' => env('JWT_SECRET')

我们可以找到更多关于这个包和它的所有配置设置 on Github.

laravel-cors

Require the barryvdh/laravel-cors package in our composer.json 更新我们的依赖项.

作曲家要求barryvdh/laravel-cors 0.4.x@dev

Add the CorsServiceProvider to our app/config/app.php providers array.

“Barryvdh \歌珥\ CorsServiceProvider”

然后将中间件添加到 app/Http/Kernel.php.

“中间件Barryvdh \歌珥\ \ HandleCors”

将配置发布到本地 config/cors.php file by using the PHP工匠供应商:发布 command.

Example of a cors.php file configuration:

return [
   'defaults' => [
       'supportsCredentials' => false,
       'allowedOrigins' => [],
       'allowedHeaders' => [],
       'allowedMethods' => [],
       'exposedHeaders' => [],
       'maxAge' => 0,
       'hosts' => [],
   ],

   'paths' => [
       'v1/*' => [
           'allowedOrigins' => ['*'],
           'allowedHeaders' => ['*'],
           'allowedMethods' => ['*'],
           'maxAge' => 3600,
       ],
   ],
];

路由和处理HTTP请求

为简洁起见,我将把所有代码放在路由中.该文件负责Laravel路由并将请求委托给控制器. 我们通常会创建专门的控制器来处理所有HTTP请求,并保持代码的模块化和简洁.

加载AngularJS的SPA视图

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

User Registration

When we make a POST request to /signup 使用用户名和密码,我们将尝试创建一个新用户并将其保存到数据库中. 创建用户之后,将创建JWT并通过JSON响应返回.

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);

   返回响应:json(紧凑(令牌));
});

User Sign In

When we make a POST request to /signin 通过用户名和密码,我们验证用户是否存在,并通过JSON响应返回JWT.

post('/signin', function () {
   $credentials = Input::only('email', 'password');

   if ( ! $token = JWTAuth::尝试($credentials)) {
       返回json(false, HttpResponse::HTTP_UNAUTHORIZED);
   }

   返回响应:json(紧凑(令牌));
});

在同一域中获取受限资源

一旦用户登录,我们就可以获取受限制的资源. I’ve created a route /restricted 它模拟需要经过身份验证的用户的资源. 为了做到这一点,请求 Authorization 头或查询字符串需要提供JWT以供后端验证.

路线:get(“/限制”,
   'before' => 'jwt-auth',
   function () {
       $token = JWTAuth::getToken();
       $user = JWTAuth:: tuser ($token);

       return Response::json([
           'data' => [
               'email' => $user->email,
               'registered_at' => $user->created_at->toDateTimeString()
           ]
       ]);
   }
]);

在这个例子中,我使用 jwt-auth 中提供的中间件 jwt-auth package using 'before' => 'jwt-auth'. 该中间件用于过滤请求并验证JWT令牌. If the token is invalid, not present, or expired, 中间件将抛出一个我们可以捕获的异常.

在Laravel 5中,我们可以使用 app/Exceptions/Handler.php file. Using the render 函数,我们可以基于抛出的异常创建HTTP响应.

render($request, Exception $e)
{
  if ($e instanceof \Tymon\JWTAuth\Exceptions\ tokeninvalideexception)
  {
     返回响应(['令牌无效'],401);
  }
  if ($e instanceof \Tymon\JWTAuth\Exceptions\TokenExpiredException)
  {
     返回响应(['令牌已过期'],401);
  }

  返回parent::render($request, $e);
}

如果用户经过身份验证并且令牌有效, 我们可以通过JSON安全地将受限数据返回到前端.

从API子域中获取受限资源

在下一个JSON web令牌示例中,我们将采用一种不同的方法进行令牌验证. Instead of using jwt-auth 中间件,我们将手动处理异常. When we make a POST request to an API server api.jwt.dev/v1/restricted,我们正在发出跨域请求,并且必须在后端启用CORS. 幸运的是,我们已经在 config/cors.php file.

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请求并创建一个加载栏. 在标题部分,我们有以下样式表:




标记的页脚包含对库的引用, 以及Angular模块的自定义脚本, controllers and services.











We are using ngStorage library for AngularJS, 将令牌保存到浏览器的本地存储中, 这样我们就可以通过 Authorization header.

在生产环境中, of course, 为了提高性能,我们将最小化并组合所有脚本文件和样式表.

我使用Bootstrap创建了一个导航栏,它将改变适当链接的可见性, 取决于用户的登录状态. 的存在来确定登录状态 token 变量在控制器的作用域中.



Routing

We have a file named 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'
   })
   .配置('$routeProvider', '$httpProvider', function ($routeProvider, $httpProvider) {
       $routeProvider.
           when('/', {
               templateUrl:“泛音/ home.html',
               控制器:“HomeController”
           }).
           when('/signin', {
               templateUrl:“泛音/ signin.html',
               控制器:“HomeController”
           }).
           when('/signup', {
               templateUrl:“泛音/注册.html',
               控制器:“HomeController”
           }).
           when('/restricted', {
               templateUrl:“泛音/限制.html',
               控制器:“RestrictedController”
           }).
           otherwise({
               redirectTo: '/'
           });

在这里,我们可以看到,我们已经定义了4条路由,这4条路由由任意一条处理 HomeController or RestrictedController. 每条路由都对应一个局部的HTML视图. 我们还定义了两个常量,它们包含对后端HTTP请求的url.

Request Interceptor

AngularJS的$http服务允许我们与后端通信并发出http请求. 在我们的示例中,我们希望拦截每个HTTP请求并向其注入 Authorization 如果用户经过身份验证,则包含JWT的头. 我们还可以使用拦截器来创建一个全局HTTP错误处理程序. 下面是我们的拦截器的一个例子,如果它在浏览器的本地存储中可用,它会注入一个令牌.

$httpProvider.interceptors.push('$q', '$location', '$localStorage', function ($q, $location, $localStorage) {
   return {
       'request': function (config) {
           config.headers = config.headers || {};
           if ($localStorage.token) {
               config.headers.授权= '承载者' + $localStorage.token;
           }
           return config;
       },
       'responseError':函数(response) {
           if (response.Status === 401 ||响应.status === 403) {
               $location.path('/signin');
           }
           return $q.reject(response);
       }
   };
}]);

Controllers

In the controllers.js 文件中,我们已经为应用程序定义了两个控制器: HomeController and RestrictedController. HomeController 处理登录,注册和注销功能. 它将用户名和密码数据从登录和注册表单传递给 Auth 服务,它将HTTP请求发送到后端. 然后将令牌保存到本地存储中, 或显示错误消息, 取决于后端响应.

angular.module('app')
   .` ` $rootScope `, ` $scope `, ` $location `, ` $localStorage `, ` Auth `,
       函数($rootScope, $scope, $location, $localStorage, Auth) {
           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 = '无效凭据.';
               })
           };

           $scope.signup = function () {
               var formData = {
                   email: $scope.email,
                   password: $scope.password
               };

               Auth.注册(formData, successAuth, function () {
                   $rootScope.error = '注册失败';
               })
           };

           $scope.logout = function () {
               Auth.logout(function () {
                   window.location = "/"
               });
           };
           $scope.token = $localStorage.token;
           $scope.tokenClaims = Auth.getTokenClaims();
       }])

RestrictedController 行为方式相同,只是它通过使用 getRestrictedData and getApiData functions on the Data service.

   .控制器(“RestrictedController”, ['$rootScope', '$scope', 'Data', function ($rootScope, $scope, Data) {
       Data.gettrestricteddata(函数(res) {
           $scope.data = res.data;
       }, function () {
           $rootScope.error =“获取受限内容失败”.';
       });
       Data.getApiData(function (res)) {
           $scope.api = res.data;
       }, function () {
           $rootScope.error =“获取受限API内容失败”.';
       });
   }]);

只有当用户经过身份验证时,后端才负责提供受限制的数据. 这意味着,为了响应受限制的数据, 对该数据的请求需要在其属性中包含有效的JWT Authorization header or query string. 如果不是这种情况,服务器将响应401未授权错误状态码.

Auth Service

Auth服务负责将登录和注册HTTP请求发送到后端. 如果请求成功, 响应包含已签名的令牌, 哪个是base64解码的, 所包含的令牌声明信息保存到 tokenClaims variable. 将其传递给控制器 getTokenClaims function.

angular.module('app')
   .factory('Auth', ['$http', '$localStorage', 'urls', function ($http, $localStorage, urls) {
       函数urlBase64Decode(str) {
           var output = str.replace('-', '+').replace('_', '/');
           switch (output.length % 4) {
               case 0:
                   break;
               case 2:
                   output += '==';
                   break;
               case 3:
                   output += '=';
                   break;
               default:
                   非法的base64url字符串!';
           }
           return window.atob(output);
       }

       getClaimsFromToken() {
           var token = $localStorage.token;
           var user = {};
           if (typeof token !== 'undefined') {
               var encoded = token.split('.')[1];
               user = JSON.解析(urlBase64Decode(编码));
           }
           return user;
       }

       var tokenClaims = getClaimsFromToken();

       return {
           注册:函数(数据,成功,错误){
               $http.post(urls.BASE + '/signup', data).success(success).error(error)
           },
           登录:函数(数据,成功,错误){
               $http.post(urls.BASE + '/signin', data).success(success).error(error)
           },
           注销:function (success) {
               tokenClaims = {};
               delete $localStorage.token;
               success();
           },
           getTokenClaims:函数(){
               return tokenClaims;
           }
       };
   }
   ]);

Data Service

这是一个简单的服务,它向身份验证服务器和API服务器发出请求,以获取一些虚拟的受限数据. 它发出请求,并将成功和错误回调委托给控制器.

angular.module('app')
   .factory('Data', ['$http', 'urls', function ($http, urls) {

       return {
           gettrestricteddata:函数(成功,错误){
               $http.get(urls.BASE + '/restricted').success(success).error(error)
           },
           getApiData:函数(成功,错误){
               $http.get(urls.BASE_API + '/restricted').success(success).error(error)
           }
       };
   }
   ]);

超越这个JSON Web令牌教程

基于令牌的身份验证使我们能够构建不绑定于特定身份验证方案的解耦系统. 令牌可以在任何地方生成,并在使用相同密钥对令牌签名的任何系统上使用. 它们是可移动的,不需要我们使用cookie.

JSON Web令牌适用于所有流行的编程语言,并且正在迅速普及. 他们得到了谷歌、微软和Zendesk等公司的支持. Internet工程任务组(IETF)的标准规范是 还在草案中 未来可能会有轻微的变化.

关于jwt还有很多内容要介绍,例如如何处理 security details, 并在令牌到期时刷新它们, 但JSON Web令牌教程应该演示基本用法和, more importantly, 使用jwt的优点.

Understanding the basics

  • What is a bearer token?

    This refers to a JWT, 这是通过HTTP头被称为授权, 以字符串格式“Bearer $your_token_here”.

  • What is JWT?

    JWT代表JSON Web Token,是现代Web应用中常用的身份验证策略.

  • How does JSON work?

    JSON只是一种数据格式,与JavaScript允许的数据文字格式非常相似. 它是一种分层格式,允许嵌套对象和数组,以及字符串和数字文字.

就这一主题咨询作者或专家.
Schedule a call
蒂诺·特卡莱克的头像
Tino Tkalec

Located in Zagreb, Croatia

Member since October 23, 2014

About the author

Tino是一名软件工程师,在创建本机Windows和Web应用程序方面拥有10多年的经验. He’s a LAMP stack expert.

Toptal作者都是各自领域经过审查的专家,并撰写他们有经验的主题. 我们所有的内容都经过同行评审,并由同一领域的Toptal专家验证.

Years of Experience

14

世界级的文章,每周发一次.

订阅意味着同意我们的 privacy policy

世界级的文章,每周发一次.

订阅意味着同意我们的 privacy policy

Toptal Developers

Join the Toptal® community.

\n\n\n\n\n\n\n\n\n\n\n\n

We are using ngStorage library for AngularJS, 将令牌保存到浏览器的本地存储中, 这样我们就可以通过 Authorization header.

\n\n

在生产环境中, of course, 为了提高性能,我们将最小化并组合所有脚本文件和样式表.

\n\n

我使用Bootstrap创建了一个导航栏,它将改变适当链接的可见性, 取决于用户的登录状态. 的存在来确定登录状态 token 变量在控制器的作用域中.

\n\n
\n \n JWT Angular example\n
\n\n
\n\n

Routing

\n\n

We have a file named app.js 谁负责配置我们所有的前端路由.

\n\n
angular.module('app', [\n   'ngStorage',\n   'ngRoute',\n   'angular-loading-bar'\n])\n   .constant('urls', {\n       BASE: 'http://jwt.dev:8000',\n       BASE_API: 'http://api.jwt.dev:8000/v1'\n   })\n   .配置('$routeProvider', '$httpProvider', function ($routeProvider, $httpProvider) {\n       $routeProvider.\n           when('/', {\n               templateUrl:“泛音/ home.html',\n               控制器:“HomeController”\n           }).\n           when('/signin', {\n               templateUrl:“泛音/ signin.html',\n               控制器:“HomeController”\n           }).\n           when('/signup', {\n               templateUrl:“泛音/注册.html',\n               控制器:“HomeController”\n           }).\n           when('/restricted', {\n               templateUrl:“泛音/限制.html',\n               控制器:“RestrictedController”\n           }).\n           otherwise({\n               redirectTo: '/'\n           });\n
\n\n

在这里,我们可以看到,我们已经定义了4条路由,这4条路由由任意一条处理 HomeController or RestrictedController. 每条路由都对应一个局部的HTML视图. 我们还定义了两个常量,它们包含对后端HTTP请求的url.

\n\n

Request Interceptor

\n\n

AngularJS的$http服务允许我们与后端通信并发出http请求. 在我们的示例中,我们希望拦截每个HTTP请求并向其注入 Authorization 如果用户经过身份验证,则包含JWT的头. 我们还可以使用拦截器来创建一个全局HTTP错误处理程序. 下面是我们的拦截器的一个例子,如果它在浏览器的本地存储中可用,它会注入一个令牌.

\n\n
$httpProvider.interceptors.push('$q', '$location', '$localStorage', function ($q, $location, $localStorage) {\n   return {\n       'request': function (config) {\n           config.headers = config.headers || {};\n           if ($localStorage.token) {\n               config.headers.授权= '承载者' + $localStorage.token;\n           }\n           return config;\n       },\n       'responseError':函数(response) {\n           if (response.Status === 401 ||响应.status === 403) {\n               $location.path('/signin');\n           }\n           return $q.reject(response);\n       }\n   };\n}]);\n
\n\n

Controllers

\n\n

In the controllers.js 文件中,我们已经为应用程序定义了两个控制器: HomeController and RestrictedController. HomeController 处理登录,注册和注销功能. 它将用户名和密码数据从登录和注册表单传递给 Auth 服务,它将HTTP请求发送到后端. 然后将令牌保存到本地存储中, 或显示错误消息, 取决于后端响应.

\n\n
angular.module('app')\n   .` ` $rootScope `, ` $scope `, ` $location `, ` $localStorage `, ` Auth `,\n       函数($rootScope, $scope, $location, $localStorage, Auth) {\n           successAuth(res) {\n               $localStorage.token = res.token;\n               window.location = \"/\";\n           }\n\n           $scope.signin = function () {\n               var formData = {\n                   email: $scope.email,\n                   password: $scope.password\n               };\n\n               Auth.signin(formData, successAuth, function () {\n                   $rootScope.error = '无效凭据.';\n               })\n           };\n\n           $scope.signup = function () {\n               var formData = {\n                   email: $scope.email,\n                   password: $scope.password\n               };\n\n               Auth.注册(formData, successAuth, function () {\n                   $rootScope.error = '注册失败';\n               })\n           };\n\n           $scope.logout = function () {\n               Auth.logout(function () {\n                   window.location = \"/\"\n               });\n           };\n           $scope.token = $localStorage.token;\n           $scope.tokenClaims = Auth.getTokenClaims();\n       }])\n
\n\n

RestrictedController 行为方式相同,只是它通过使用 getRestrictedData and getApiData functions on the Data service.

\n\n
   .控制器(“RestrictedController”, ['$rootScope', '$scope', 'Data', function ($rootScope, $scope, Data) {\n       Data.gettrestricteddata(函数(res) {\n           $scope.data = res.data;\n       }, function () {\n           $rootScope.error =“获取受限内容失败”.';\n       });\n       Data.getApiData(function (res)) {\n           $scope.api = res.data;\n       }, function () {\n           $rootScope.error =“获取受限API内容失败”.';\n       });\n   }]);\n
\n\n

只有当用户经过身份验证时,后端才负责提供受限制的数据. 这意味着,为了响应受限制的数据, 对该数据的请求需要在其属性中包含有效的JWT Authorization header or query string. 如果不是这种情况,服务器将响应401未授权错误状态码.

\n\n

Auth Service

\n\n

Auth服务负责将登录和注册HTTP请求发送到后端. 如果请求成功, 响应包含已签名的令牌, 哪个是base64解码的, 所包含的令牌声明信息保存到 tokenClaims variable. 将其传递给控制器 getTokenClaims function.

\n\n
angular.module('app')\n   .factory('Auth', ['$http', '$localStorage', 'urls', function ($http, $localStorage, urls) {\n       函数urlBase64Decode(str) {\n           var output = str.replace('-', '+').replace('_', '/');\n           switch (output.length % 4) {\n               case 0:\n                   break;\n               case 2:\n                   output += '==';\n                   break;\n               case 3:\n                   output += '=';\n                   break;\n               default:\n                   非法的base64url字符串!';\n           }\n           return window.atob(output);\n       }\n\n       getClaimsFromToken() {\n           var token = $localStorage.token;\n           var user = {};\n           if (typeof token !== 'undefined') {\n               var encoded = token.split('.')[1];\n               user = JSON.解析(urlBase64Decode(编码));\n           }\n           return user;\n       }\n\n       var tokenClaims = getClaimsFromToken();\n\n       return {\n           注册:函数(数据,成功,错误){\n               $http.post(urls.BASE + '/signup', data).success(success).error(error)\n           },\n           登录:函数(数据,成功,错误){\n               $http.post(urls.BASE + '/signin', data).success(success).error(error)\n           },\n           注销:function (success) {\n               tokenClaims = {};\n               delete $localStorage.token;\n               success();\n           },\n           getTokenClaims:函数(){\n               return tokenClaims;\n           }\n       };\n   }\n   ]);\n
\n\n

Data Service

\n\n

这是一个简单的服务,它向身份验证服务器和API服务器发出请求,以获取一些虚拟的受限数据. 它发出请求,并将成功和错误回调委托给控制器.

\n\n
angular.module('app')\n   .factory('Data', ['$http', 'urls', function ($http, urls) {\n\n       return {\n           gettrestricteddata:函数(成功,错误){\n               $http.get(urls.BASE + '/restricted').success(success).error(error)\n           },\n           getApiData:函数(成功,错误){\n               $http.get(urls.BASE_API + '/restricted').success(success).error(error)\n           }\n       };\n   }\n   ]);\n
\n\n

超越这个JSON Web令牌教程

\n\n

基于令牌的身份验证使我们能够构建不绑定于特定身份验证方案的解耦系统. 令牌可以在任何地方生成,并在使用相同密钥对令牌签名的任何系统上使用. 它们是可移动的,不需要我们使用cookie.

\n\n

JSON Web令牌适用于所有流行的编程语言,并且正在迅速普及. 他们得到了谷歌、微软和Zendesk等公司的支持. Internet工程任务组(IETF)的标准规范是 还在草案中 未来可能会有轻微的变化.

\n\n

关于jwt还有很多内容要介绍,例如如何处理 security details, 并在令牌到期时刷新它们, 但JSON Web令牌教程应该演示基本用法和, more importantly, 使用jwt的优点.

\n","as":"div","isContentFit":true,"sharingWidget":{"url":"http://r0jm.wxzjnt.com/web/cookie-free-authentication-with-json-web-tokens-an-example-in-laravel-and-angularjs","title":"使用AngularJS的JSON Web令牌教程 & Laravel","text":null,"providers":["linkedin","twitter","facebook"],"gaCategory":null,"domain":{"name":"developers","title":"Engineering","vertical":{"name":"developers","title":"Developers","publicUrl":"http://r0jm.wxzjnt.com/developers"},"publicUrl":"http://r0jm.wxzjnt.com/developers/blog"},"hashtags":"Authentication,JWT,Authorization,NoCookies"}}