web json动画_具有JWTPHP授权(JSON Web令牌)

web json动画

If you like computer security topics, you will know that one of the most discussed and controversial topics is user authentication. Within its context, you will find a broad range of study areas, from new mechanisms to usability. It is, thus, to my surprise that JSON Web Tokens is a topic not often talked about, and I think it deserves to be in the spotlight today. We will see how easy it is to integrate it in an API authentication mechanism.

如果您喜欢计算机安全性主题,您将知道讨论最多和争议最大的主题之一是用户身份验证。 在它的上下文中,您会发现从新机制到可用性的广泛研究领域。 因此,令我惊讶的是JSON Web令牌是一个不经常谈论的话题,我认为它今天应成为人们关注的焦点。 我们将看到将其集成到API身份验证机制中是多么容易。

Key icon

与会议 (Versus Sessions)

There was a time when the only way to authenticate yourself into an application was by giving out credentials. Later came service APIs and sending out credentials in plain text was unacceptable. The idea of API tokens came up and nowadays, they are common practice.

曾经有一段时间,向自己的应用程序进行身份验证的唯一方法是颁发凭据。 后来出现了服务API,并且以纯文本形式发送凭据是不可接受的。 API令牌的想法应运而生,如今,它们是常见的做法。

Some of the disadvantages of giving out credentials to an application and maintaining a user’s state in relation to the application with session cookies are:

通过会话cookie向应用程序提供凭据并维护与该应用程序相关的用户状态的一些缺点是:

  • Data is stored in plain text on the server Even though the data is usually not stored in a public folder, anyone with access can read the contents of the session files.

    数据以纯文本格式存储在服务器上,即使通常不将数据存储在公用文件夹中,任何具有访问权限的人都可以读取会话文件的内容。

  • Filesystem read/write requests Every time a session starts or its data is modified, the server needs to update the session file. The same goes for every time the application sends a session cookie. You will end up with a slow server if you have a considerable amount of users, unless you use alternative session stores.

    文件系统读/写请求每次启动会话或修改其数据时,服务器都需要更新会话文件。 每次应用程序发送会话cookie时,情况都一样。 如果您拥有大量用户,则最终将导致服务器运行缓慢,除非您使用其他会话存储。

  • Distributed/clustered applications Since the session files are stored in the file system by default, it is hard to have a distributed or clustered infrastructure for high availability applications that require the use of load balancers, clustered servers, etc… Other storage media and special configurations have to be made.

    分布式/集群应用程序由于默认情况下会话文件存储在文件系统中,因此很难为需要使用负载平衡器,集群服务器等的高可用性应用程序提供分布式或集群基础结构。其他存储介质和特殊配置必须制作。

When dealing with service APIs that have restricted service calls, you will need to add your key to every request made (either in the request header, such as Authorization, or in the URL query string). API keys commonly rely on a centralized mechanism to control them. So if you want to mark an API key as invalid, it has to be revoked on the application side.

在处理具有受限服务调用的服务API时,您需要将密钥添加到每个已发出的请求中(在请求标头中,例如Authorization ,或在URL查询字符串中)。 API密钥通常依赖于集中式机制来控制它们。 因此,如果要将API密钥标记为无效,则必须在应用程序端将其吊销。

智威汤逊 (JWT)

Since October 2010, there have been several proposals to use JSON based tokens. JWT or JSON Web Token was proposed on December 2010, having the following characteristics:

自2010年10月以来,已经有一些建议使用基于JSON的令牌 。 JWT或JSON Web令牌于2010年12月提出,具有以下特征:

  • Intended for space constrained environments, such as HTTP Authorization headers or query string parameters.

    适用于空间受限的环境,例如HTTP授权标头或查询字符串参数。
  • Data to be transmitted in Javascript Object Notation format (JSON)

    以Javascript对象符号格式(JSON)传输的数据
  • The data has to be the payload of a JSON Web Signature (JWS)

    数据必须是JSON Web签名(JWS)的有效负载
  • Represented using Base64 URL encoding

    使用Base64 URL编码表示

The JSON Web Signature is a cryptographic mechanism designed to secure data with a digital signature unique to the contents of the token in such a way that we are able to determine whether the data of the token has been tampered with or not.

JSON Web签名是一种加密机制,旨在使用令牌内容唯一的数字签名保护数据,以使我们能够确定令牌的数据是否已被篡改。

The use of JWTs has many advantages over a single API key:

与单个API密钥相比,使用JWT具有许多优点:

  • API keys are just random strings, while JWTs contain information and metadata that can describe user identity, authorization data and the validity of the token within a time frame or domain.

    API密钥只是随机字符串,而JWT包含信息和元数据,这些信息和元数据可以描述用户身份,授权数据以及令牌在时间范围或域内的有效性。
  • JWTs do not require a centralized issuing or revoking authority.

    JWT不需要集中的发行或撤消授权。
  • OAUTH2 compatible.

    兼容OAUTH2。
  • JWT data can be inspected.

    可以检查JWT数据。
  • JWTs have expiration controls.

    JWT具有到期控制。

On May 19th 2015, JWT became a published IETF RFC 7519.

2015年5月19日,JWT成为已发布的IETF RFC 7519

它是什么样子的? (What does it look like?)

A JWT would look like the following:

JWT如下所示:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE0MTY5MjkxMDksImp0aSI6ImFhN2Y4ZDBhOTVjIiwic2NvcGVzIjpbInJlcG8iLCJwdWJsaWNfcmVwbyJdfQ.XCEwpBGvOLma4TCoh36FU7XhUbcskygS81HE1uHLf0E

It would appear that the string is just random characters concatenated together, and not very different from an API key. However, if you look closely, there are actually 3 strings, separated by a dot character.

看起来字符串只是串联在一起的随机字符,与API密钥没有太大区别。 但是,如果仔细观察,实际上有3个字符串,由点字符分隔。

The first and second strings are Base64 URL encoded JSON strings, so if we decode those, we will have the following results:

第一个和第二个字符串是Base64 URL编码的JSON字符串,因此,如果对它们进行解码,我们将得到以下结果:

{
  "alg": "HS256",
  "typ": "JWT"
}
{
  "iat": 1416929109,
  "jti": "aa7f8d0a95c",
  "scopes": [
    "repo",
    "public_repo"
  ]
}

The first string is the JWS header, which states which cryptographic algorithm was used to generate the signature and the payload type. The second string is the payload, and passes along some standard fields, any data you wish to send within the token. The third string is the cryptographic signature, and will decode to binary data.

第一个字符串是JWS头,它说明使用哪种加密算法来生成签名和有效载荷类型。 第二个字符串是有效负载,并通过一些标准字段传递您希望在令牌内发送的任何数据。 第三个字符串是密码签名,并将解码为二进制数据。

What is interesting about the signature is that the cryptographic algorithm requires a secret key, a string that only the issuer application has to know and should never be disclosed in any way. This way when the application receives a token, it can verify the signature against the contents of the token using said secret key. If the signature verification fails, we can know for sure that the data within the token has been tampered with and should be discarded.

关于签名的有趣之处在于,密码算法需要一个秘密密钥,这是只有发行者应用程序必须知道的字符串,绝不能以任何方式公开。 这样,当应用程序收到令牌时,它可以使用所述秘密密钥对照令牌的内容来验证签名。 如果签名验证失败,我们可以确定令牌中的数据已被篡改并应丢弃。

You can take a look at jwt.io where you can play around with encoding and decoding JWTs.

您可以看一下jwt.io ,在其中可以进行JWT的编码和解码。

让我们玩 (Let’s Play)

So how do we apply this to a PHP app? Let’s say we have a login mechanism that currently uses session cookies to store information about a user’s login state within the application. Please note that JWT was not designed to substitute session cookies. However, for this example, we will have a couple of services: one that generates a JWT based on the provided username and password, and another that will fetch a protected resource provided we supply a valid JWT.

那么我们如何将其应用于PHP应用程序? 假设我们有一个登录机制,该机制当前使用会话cookie在应用程序中存储有关用户登录状态的信息。 请注意, JWT并非旨在替代会话cookie 。 但是,对于此示例,我们将提供一些服务:一个服务基于提供的用户名和密码生成JWT,另一个服务将在我们提供有效JWT的情况下获取受保护的资源。

Login page

Once we are signed in, we will be able to retrieve a protected resource from the application.

登录后,我们将能够从应用程序中检索受保护的资源。

To begin, we install php-jwt with composer require firebase/php-jwt. In the sample application developed for this tutorial, I’m also using zend-config and zend-http, so if you’d like to follow along, feel free to install those as well:

首先,我们安装PHP,智威汤逊composer require firebase/php-jwt 。 在为本教程开发的示例应用程序中 ,我还使用了zend-config和zend-http,因此,如果您想继续学习,也可以随意安装它们:

composer require firebase/php-jwt:dev-master
composer require zendframework/zend-config:~2.3
composer require zendframework/zend-http:~2.3

There is another PHP library, jose from namshi if you would like to play with it later on.

还有另一个PHP库,来自namshi的 jose ,如果您以后想使用它的话。

Now, let’s assume that the login form submits the data to our JWT issuer service via AJAX, where the credentials are validated against a database, and after determining that the credentials are valid, we have to build our token. Let’s build it as an array first:

现在,假设登录表单通过AJAX将数据提交到我们的JWT发行者服务,在此针对数据库验证了凭据,并且在确定凭据有效之后,我们必须构建令牌。 让我们首先将其构建为数组:

<?php
require_once('vendor/autoload.php');

/*
 * Application setup, database connection, data sanitization and user  
 * validation routines are here.
 */
$config = Factory::fromFile('config/config.php', true); // Create a Zend Config Object

if ($credentialsAreValid) {

    $tokenId    = base64_encode(mcrypt_create_iv(32));
    $issuedAt   = time();
    $notBefore  = $issuedAt + 10;             //Adding 10 seconds
    $expire     = $notBefore + 60;            // Adding 60 seconds
    $serverName = $config->get('serverName'); // Retrieve the server name from config file
    
    /*
     * Create the token as an array
     */
    $data = [
        'iat'  => $issuedAt,         // Issued at: time when the token was generated
        'jti'  => $tokenId,          // Json Token Id: an unique identifier for the token
        'iss'  => $serverName,       // Issuer
        'nbf'  => $notBefore,        // Not before
        'exp'  => $expire,           // Expire
        'data' => [                  // Data related to the signer user
            'userId'   => $rs['id'], // userid from the users table
            'userName' => $username, // User name
        ]
    ];

     /*
      * More code here...
      */
}

Please notice that you can define the data structure however you want, there are however some reserved claims, such as the ones used above:

请注意,您可以根据需要定义数据结构,但是有一些保留的声明,例如上面使用的声明:

  • iat – timestamp of token issuing.

    iat –令牌发行的时间戳。

  • jti – A unique string, could be used to validate a token, but goes against not having a centralized issuer authority.

    jti –唯一的字符串,可用于验证令牌,但不具有集中的发行者权限。

  • iss – A string containing the name or identifier of the issuer application. Can be a domain name and can be used to discard tokens from other applications.

    iss –包含发行者应用程序名称或标识符的字符串。 可以是域名,可以用于丢弃其他应用程序中的令牌。

  • nbf – Timestamp of when the token should start being considered valid. Should be equal to or greater than iat. In this case, the token will begin to be valid 10 seconds

    nbf –令牌应何时开始被视为有效的时间戳。 应该等于或大于iat 。 在这种情况下,令牌将在10秒内开始有效

    after being issued.

    发行后。

  • exp – Timestamp of when the token should cease to be valid. Should be greater than iat and nbf. In this case, the token will expire 60 seconds after being issued.

    exp –令牌应何时失效的时间戳。 应该大于iatnbf 。 在这种情况下,令牌将在发出后60秒后过期。

Those claims are not required, but will help you determine the validity of a token (more on this later). Our application’s payload comes inside the data claim, where we are storing the userId and userName values. Since a JWT can be inspected client side, please remember not to include any sensitive information in it.

这些声明不是必需的,但可以帮助您确定令牌的有效性(稍后会对此进行更多介绍)。 我们应用程序的有效负载位于data声明的内部,我们在其中存储userIduserName值。 由于JWT可以在客户端进行检查,因此请记住不要在其中包含任何敏感信息。

Transforming this array into a JWT is super easy:

将该数组转换为JWT非常简单:

<?php

/*
 * Code here...
 */

    /*
     * Extract the key, which is coming from the config file. 
     * 
     * Best suggestion is the key to be a binary string and 
     * store it in encoded in a config file. 
     *
     * Can be generated with base64_encode(openssl_random_pseudo_bytes(64));
     *
     * keep it secure! You'll need the exact key to verify the 
     * token later.
     */
    $secretKey = base64_decode($config->get('jwtKey'));
    
    /*
     * Encode the array to a JWT string.
     * Second parameter is the key to encode the token.
     * 
     * The output string can be validated at http://jwt.io/
     */
    $jwt = JWT::encode(
        $data,      //Data to be encoded in the JWT
        $secretKey, // The signing key
        'HS512'     // Algorithm used to sign the token, see https://tools.ietf.org/html/draft-ietf-jose-json-web-algorithms-40#section-3
        );
        
    $unencodedArray = ['jwt' => $jwt];
    echo json_encode($unencodedArray);

JWT::encode() will take care of everything (transforming the array to JSON, producing the headers, signing the payload and encoding the final string). You will want to make your secret key a long, binary string, encode it in a config file and never disclose it. Having it directly in your code is a bad idea.

JWT::encode()将处理所有事情(将数组转换为JSON,生成标头,对有效负载进行签名并对最终字符串进行编码)。 您将需要使您的密钥成为一个长的二进制字符串,将其编码在配置文件中,并且永远不要泄露它。 直接将其包含在代码中是个坏主意。

Now that the client has the token, you can store it using JS or whichever mechanism you like. Here is an example using jQuery:

现在客户端有了令牌,您可以使用JS或任何您喜欢的机制来存储它。 这是使用jQuery的示例:

$(function(){
    var store = store || {};
    
    /*
     * Sets the jwt to the store object
     */
    store.setJWT = function(data){
        this.JWT = data;
    }
    
    /*
     * Submit the login form via ajax
     */
	$("#frmLogin").submit(function(e){
	        e.preventDefault();
	        $.post('auth/token', $("#frmLogin").serialize(), function(data){
	            store.setJWT(data.JWT);
	        }).fail(function(){
	            alert('error');
	        });
	    });
});

Now let’s retrieve a resource that is protected by our JWT mechanism.

现在,让我们检索受JWT机制保护的资源。

Sample UI

When clicking on the “Get resource >>” button, if everything is alright, you should see an image in the grey area. Let’s use an ajax call to send the request to the resource service:

单击“获取资源>>”按钮时,如果一切正常,则应该在灰色区域中看到一个图像。 让我们使用ajax调用将请求发送到资源服务:

$("#btnGetResource").click(function(e){
        e.preventDefault();
        $.ajax({
            url: 'resource/image',
            beforeSend: function(request){
                request.setRequestHeader('Authorization', 'Bearer ' + store.JWT);
            },
            type: 'GET',
            success: function(data) {
                // Decode and show the returned data nicely.
            },
            error: function() {
                alert('error');
            }
        });
    });

Please notice the beforeSend option. We are telling jQuery that before every request is made through this call, we need to set the Authorization header with the contents of the JWT in the format of Bearer [JWT]. So when we click the button, the following request is made:

请注意beforeSend选项。 我们告诉jQuery,在通过此调用发出每个请求之前,我们需要以Bearer [JWT]格式设置带有JWT内容的Authorization标头。 因此,当我们单击按钮时,将发出以下请求:

GET /resource.php HTTP/1.1
Host: yourhost.com
Connection: keep-alive
Accept: */*
X-Requested-With: XMLHttpRequest
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE0MjU1ODg4MjEsImp0aSI6IjU0ZjhjMjU1NWQyMjMiLCJpc3MiOiJzcC1qd3Qtc2ltcGxlLXRlY25vbTFrMy5jOS5pbyIsIm5iZiI6MTQyNTU4ODgyMSwiZXhwIjoxNDI1NTkyNDIxLCJkYXRhIjp7InVzZXJJZCI6IjEiLCJ1c2VyTmFtZSI6ImFkbWluIn19.HVYBe9xvPD8qt0wh7rXI8bmRJsQavJ8Qs29yfVbY-A0

Now we can see what the protected resource is:

现在我们可以看到受保护的资源是什么:

Kitteh

This is how we validate the token in the resource service.

这就是我们在资源服务中验证令牌的方式。

<?php
chdir(dirname(__DIR__));

require_once('vendor/autoload.php');

use Zend\Config\Config;
use Zend\Config\Factory;
use Zend\Http\PhpEnvironment\Request;

/*
 * Get all headers from the HTTP request
 */
$request = new Request();

if ($request->isGet()) {
    $authHeader = $request->getHeader('authorization');

    /*
     * Look for the 'authorization' header
     */
    if ($authHeader) {
        /*
         * Extract the jwt from the Bearer
         */
        list($jwt) = sscanf( $authHeader->toString(), 'Authorization: Bearer %s');

        if ($jwt) {
            try {
                $config = Factory::fromFile('config/config.php', true);

                /*
                 * decode the jwt using the key from config
                 */
                $secretKey = base64_decode($config->get('jwtKey'));
                
                $token = JWT::decode($jwt, $secretKey, array('HS512'));

                $asset = base64_encode(file_get_contents('https://lorempixel.com/200/300/cats/'));

                /*
                 * return protected asset
                 */
                header('Content-type: application/json');
                echo json_encode([
                    'img'    => $asset
                ]);

            } catch (Exception $e) {
                /*
                 * the token was not able to be decoded.
                 * this is likely because the signature was not able to be verified (tampered token)
                 */
                header('HTTP/1.0 401 Unauthorized');
            }
        } else {
            /*
             * No token was able to be extracted from the authorization header
             */
            header('HTTP/1.0 400 Bad Request');
        }
    } else {
        /*
         * The request lacks the authorization token
         */
        header('HTTP/1.0 400 Bad Request');
        echo 'Token not found in request';
    }
} else {
    header('HTTP/1.0 405 Method Not Allowed');
}

I’m using Zend\Http\PhpEnvironment\Request to make things a little easier to deal with extracting HTTP request types and headers:

我正在使用Zend\Http\PhpEnvironment\Request使事情更容易处理提取HTTP请求类型和标头:

$request = new Request();
if ($request->isGet()) { //Will only process HTTP GET requests.
	$authHeader = $request->getHeader('authorization');
	// ...

Now let’s find out if the authorization header has a JWT string in it:

现在,让我们找出授权标头中是否包含JWT字符串:

/*
 * Look for the 'authorization' header
 */
if ($authHeader) {
    /*
     * Extract the JWT from the Bearer
     */
    list($jwt) = sscanf( $authHeader->toString(), 'Authorization: Bearer %s');
    // MORE CODE
}

This way the variable $jwt will have the contents of a potential JWT.

这样,变量$jwt将具有潜在JWT的内容。

One alternative you might choose if you do not want to deal with HTTP Authorization headers, is to include the token in the request as a URL parameter:

如果您不想处理HTTP授权标头,则可以选择的另一种选择是将令牌作为URL参数包含在请求中:

GET /resource.php?jwt=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE0MjU1ODg4MjEsImp0aSI6IjU0ZjhjMjU1NWQyMjMiLCJpc3MiOiJzcC1qd3Qtc2ltcGxlLXRlY25vbTFrMy5jOS5pbyIsIm5iZiI6MTQyNTU4ODgyMSwiZXhwIjoxNDI1NTkyNDIxLCJkYXRhIjp7InVzZXJJZCI6IjEiLCJ1c2VyTmFtZSI6ImFkbWluIn19.HVYBe9xvPD8qt0wh7rXI8bmRJsQavJ8Qs29yfVbY-A0 HTTP/1.1
Host: yourhost.com
Connection: keep-alive
Accept: */*
X-Requested-With: XMLHttpRequest

Let’s try to decode the JWT now. Remember the secret key we used earlier to generate the token? It is a vital part of the decoding process here:

让我们现在尝试解码JWT。 还记得我们之前用来生成令牌的密钥吗? 这是解码过程中至关重要的部分:

$secretKey = base64_decode($config->get('jwtKey'));

/*
 * decode the JWT using the key from config
 */
$token = JWT::decode($jwt, $secretKey, array('HS512'));

If the process to decode the JWT fails, it could be that:

如果解码JWT的过程失败,则可能是:

  1. The number of segments provided did not match the standard 3 as described earlier.

    提供的段数与前面所述的标准3不符。
  2. The header or the payload is not a valid JSON string

    标头或有效负载不是有效的JSON字符串
  3. The signature is invalid, which means the data was tampered with!

    签名无效,这意味着数据已被篡改!
  4. The nbf claim is set in the JWT with a timestamp, when the current timestamp is less than that.

    当当前时间戳小于nbf声明时,将在JWT中设置时间戳。

  5. The iat claim is set in the JWT with a timestamp, when the current timestamp is less than that.

    当当前时间戳小于该时间戳时,将在JWT中设置iat声明。

  6. The exp claim is set in the JWT with a timestamp, when the current timestamp is more than that.

    当当前时间戳大于该时间戳时, exp声明将在JWT中设置一个时间戳。

As you can see, JWT has a nice set of controls that will mark it as invalid, without the need to manually revoke it or check it against a list of valid tokens.

如您所见,JWT有一组不错的控件,可以将其标记为无效,而无需手动吊销或对照有效令牌列表对其进行检查。

In case you were wondering about the JWT signature and tampered data, this is possible thanks to cryptographic Message authentication codes. In a nutshell, arbitrary data input along with a key will produce a unique ‘fingerprint’ of the data. This fingerprint alone cannot be reversed back to the data input and the slightest change to either the data input or the key will produce a totally different fingerprint.

如果您想知道JWT签名和被篡改的数据,这要归功于加密的消息身份验证代码 。 简而言之,任意数据输入和键将产生数据的唯一“指纹”。 仅此指纹不能逆转回数据输入,对数据输入或密钥的任何细微更改都会产生完全不同的指纹。

At this point we can be sure that the JWT is valid. Additionally, you could check if the user in the token is still valid, if the issuer of the token (from the iss claim) is you, or if your token has embedded permission flags, then check those against the action the user is requesting to perform.

至此,我们可以确定JWT是有效的。 此外,您可以检查令牌中的用户是否仍然有效,令牌的发行者(根据iss声明)或您的令牌中是否嵌入了权限标志,然后对照用户请求的操作检查那些标志表演。

Finally, we request an image from lorempixel.com, base64 encode it and return it in a json response string:

最后,我们从lorempixel.com请求图像,base64对其进行编码,并以json响应字符串返回:

$asset = base64_encode(file_get_contents('https://lorempixel.com/200/300/cats/'));

/*
 * return protected asset
 */
header('Content-type: application/json');
echo json_encode([
    'img'    => $asset
]);

If you want to play with a sample application, you can check out my project’s repo for this article, follow the instructions of the README, and take a closer look at the code.

如果要使用示例应用程序,则可以查看本文的我的项目的仓库 ,按照README的说明进行操作,并仔细阅读代码。

sample app

结论 (Conclusion)

From here on, you can try to implement JWTs in your next API, maybe trying some other signing algorithms that use asymmetric keys like RS256 or integrate it in an existing OAUTH2 authentication server to be the API key. All your constructive feedback is welcome, as well as any questions or comments.

从这里开始,您可以尝试在下一个API中实现JWT,也许可以尝试其他一些使用非对称密钥(如RS256签名算法,或者将其集成到现有的OAUTH2身份验证服务器中作为API密钥。 欢迎您提出所有建设性的意见,以及任何问题或意见。

翻译自: https://www.sitepoint.com/php-authorization-jwt-json-web-tokens/

web json动画

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值