跳转到内容

安全性 - JSON Web 令牌 (JWT)


概览

注意

当前仅支持对称算法

Phalcon\Encryption\Security\JWT是一个命名空间,其中包含的组件允许你按照RFC 7915中的描述来发放、解析和验证 JSON Web 令牌。

注意

在以下示例中,为了便于阅读,将输出分成了不同行

使用该组件的一个示例如下:

<?php

use Phalcon\Encryption\Security\JWT\Builder;
use Phalcon\Encryption\Security\JWT\Signer\Hmac;
use Phalcon\Encryption\Security\JWT\Token\Parser;
use Phalcon\Encryption\Security\JWT\Validator;

// Defaults to 'sha512'
$signer  = new Hmac();

// Builder object
$builder = new Builder($signer);

$now        = new DateTimeImmutable();
$issued     = $now->getTimestamp();
$notBefore  = $now->modify('-1 minute')->getTimestamp();
$expires    = $now->modify('+1 day')->getTimestamp();
$passphrase = 'QcMpZ&b&mo3TPsPk668J6QH8JA$&U&m2';

// Setup
$builder
    ->setAudience('https://target.phalcon.io')  // aud
    ->setContentType('application/json')        // cty - header
    ->setExpirationTime($expires)               // exp 
    ->setId('abcd123456789')                    // JTI id 
    ->setIssuedAt($issued)                      // iat 
    ->setIssuer('https://phalcon.io')           // iss 
    ->setNotBefore($notBefore)                  // nbf
    ->setSubject('my subject for this claim')   // sub
    ->setPassphrase($passphrase)                // password 
;

// Phalcon\Encryption\Security\JWT\Token\Token
$tokenObject = $builder->getToken();

echo $tokenObject->getToken();

// eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiIsImN0eSI6ImFwcGxpY2F0aW9uXC9qc29uIn0.
// eyJhdWQiOlsiaHR0cHM6XC9cL3RhcmdldC5waGFsY29uLmlvIl0sImV4cCI6MTYxNDE4NTkxN
// ywianRpIjoiYWJjZDEyMzQ1Njc4OSIsImlhdCI6MTYxNDA5OTUxNywiaXNzIjoiaHR0cHM6XC
// 9cL3BoYWxjb24uaW8iLCJuYmYiOjE2MTQwOTk0NTcsInN1YiI6Im15IHN1YmplY3QgZm9yIHR
// oaXMgY2xhaW0ifQ.
// LdYevRZaQDZ2lul4CCQ5DymeP2ubcapTtgeezOZGIq7Meu7rFF1pv32b-AMWOxCS63CQz_jpm
// BPlPyOeEAkMbg
// $tokenReceived is what we received
$tokenReceived = getMyTokenFromTheApplication();
$audience      = 'https://target.phalcon.io';
$now           = new DateTimeImmutable();
$issued        = $now->getTimestamp();
$notBefore     = $now->modify('-1 minute')->getTimestamp();
$expires       = $now->getTimestamp();
$id            = 'abcd123456789';
$issuer        = 'https://phalcon.io';

// Defaults to 'sha512'
$signer     = new Hmac();
$passphrase = 'QcMpZ&b&mo3TPsPk668J6QH8JA$&U&m2';

// Parse the token
$parser      = new Parser();

// Phalcon\Encryption\Security\JWT\Token\Token
$tokenObject = $parser->parse($tokenReceived);

// Phalcon\Encryption\Security\JWT\Validator
$validator = new Validator($tokenObject, 100); // Allow for a time shift of 100

# Run the validators
$validator
    ->validateAudience($audience)
    ->validateExpiration($expires)
    ->validateId($id)
    ->validateIssuedAt($issued)
    ->validateIssuer($issuer)
    ->validateNotBefore($notBefore)
    ->validateSignature($signer, $passphrase)
;

# Errors printed out (if any)
var_dump($validator->getErrors())

上面的示例总体展示了如何使用该组件生成、解析和验证 JSON Web 令牌。

对象

Phalcon\Encryption\Security\JWT\Token命名空间中有一些实用工具组件,

枚举

Phalcon\Encryption\Security\JWT\Token\Enum是一个包含多个常量的类。这些常量是定义在RFC 7915中的字符串。你可以选择使用它们,也可以使用对应的字符串代替。

<?php

class Enum
{
    /**
     * Headers
     */
    const TYPE         = "typ";
    const ALGO         = "alg";
    const CONTENT_TYPE = "cty";

    /**
     * Claims
     */
    const AUDIENCE        = "aud";
    const EXPIRATION_TIME = "exp";
    const ID              = "jti";
    const ISSUED_AT       = "iat";
    const ISSUER          = "iss";
    const NOT_BEFORE      = "nbf";
    const SUBJECT         = "sub";
}

条目

Phalcon\Encryption\Security\JWT\Token\Item内部用于存储有效载荷及其编码状态。此类有效载荷可以是声明数据或头部数据。通过使用此组件,我们可以轻松提取每个令牌所需的必要信息。

签名

Phalcon\Encryption\Security\JWT\Token\SignaturePhalcon\Encryption\Security\JWT\Token\Item类似,但它仅保存签名哈希及其编码值。

令牌

Phalcon\Encryption\Security\JWT\Token\Token是负责存储并计算 JWT 令牌的组件。其构造函数接收头部、声明(作为Phalcon\Encryption\Security\JWT\Token\Item对象)以及签名对象,并提供如下方法:

public function getClaims(): Item
返回声明集合

public function getHeaders(): Item
返回头部集合

public function getPayload(): string
返回有效载荷。对于一个令牌来说,abcd.efgh.ijkl它将返回abcd.efgh

public function getSignature(): Signature
返回签名

public function getToken(): string
将令牌作为字符串返回。对于一个令牌来说,abcd.efgh.ijkl它将返回abcd.efgh.ijkl.

public function validate(Validator $validator): array
对令牌数据运行所有验证器。返回验证器中的错误数组

public function verify(SignerInterface $signer, string $key): bool
验证 token 的签名

签发者

要创建一个 JWT 令牌,我们需要提供一个签名算法。默认情况下,构造器使用 "none" (Phalcon\Encryption\Security\JWT\Signer\None)。但你可以使用 HMAC 签发者 (Phalcon\Encryption\Security\JWT\Signer\Hmac)。此外,为进一步自定义,你可以使用提供的Phalcon\Encryption\Security\JWT\Signer\SignerInterface接口。

<?php

use Phalcon\Encryption\Security\JWT\Signer\Hmac;

$signer  = new Hmac();

None

此签发者主要用于开发目的。你应该始终对你 JWT 令牌进行签名。

HMAC

HMAC 签发者支持sha512, sha384sha256算法。如果不指定,则会自动选择sha512。如果你指定了不同的算法,则会抛出Phalcon\Encryption\Security\JWT\Exceptions\UnsupportedAlgorithmException异常。算法在构造函数中设置。

<?php

use Phalcon\Encryption\Security\JWT\Signer\Hmac;

$signer  = new Hmac();
$signer  = new Hmac('sha512');
$signer  = new Hmac('sha384');
$signer  = new Hmac('sha256');
$signer  = new Hmac('sha111'); // exception

该组件内部使用 [hmac_equals][hmac_equals] 和 [hash_hmac][hash_hmac] PHP 方法来验证和签署有效载荷。它提供了以下方法:

public function getAlgHeader(): string

返回标识该算法的字符串。对于 HMAC 算法,它将返回:

算法 getAlgHeader
sha512 HS512
sha384 HS384
sha256 HS256
public function sign(string $payload, string $passphrase): string

使用密钥短语返回有效载荷的哈希值

public function verify(string $source, string $payload, string $passphrase): bool

验证源字符串的哈希是否与使用密钥短语对有效载荷进行哈希后的结果一致。

发放令牌

提供了一个 Builder 组件 (Phalcon\Encryption\Security\JWT\Builder),利用链式调用方法,并准备好用于创建 JWT 令牌。你只需实例化 Builder 对象,配置好你的令牌,然后调用getToken()方法即可。这将返回一个Phalcon\Encryption\Security\Token\Token对象,其中包含你的令牌所需的所有必要信息。在实例化 builder 组件时,你必须提供签发者类。在下面的示例中我们使用了Phalcon\Encryption\Security\JWT\Signer\Hmac签发者。

该组件中的所有设置方法都支持链式调用。

<?php

use Phalcon\Encryption\Security\JWT\Builder;
use Phalcon\Encryption\Security\JWT\Signer\Hmac;

// Defaults to 'sha512'
$signer  = new Hmac();

// Builder object
$builder = new Builder($signer);

方法

public function __construct(SignerInterface $signer): Builder
构造函数

public function init(): Builder
初始化对象 - 当你想重复使用同一个 builder 时很有用

public function addClaim(string $name, mixed $value): Builder
向声明集合中添加一个自定义声明

public function getAudience(): array|string
返回aud内容

public function getClaims(): array
返回声明作为一个数组

public function getContentType(): ?string
返回内容类型 (cty- 头部)

public function getExpirationTime(): ?int
返回exp内容

public function getHeaders(): array
返回头部作为一个数组

public function getId(): ?string
返回jti内容 (该 JWT 的 ID)

public function getIssuedAt(): ?int
返回iat内容

public function getIssuer(): ?string
返回iss内容

public function getNotBefore(): ?int
返回nbf内容

public function getSubject(): ?string
返回sub内容

public function getToken(): Token
返回令牌

public function getPassphrase(): string
返回提供的密钥短语

public function setAudience(array|string $audience): Builder
设置受众 (aud)。如果传入的参数不是一个数组或者字符串,将会抛出Phalcon\Encryption\Security\JWT\Exceptions\ValidatorException异常。

public function setContentType(string $contentType): Builder
设置内容类型 (cty- 头部)

public function setExpirationTime(int $timestamp): Builder
设置受众 (exp)。如果当前时间晚于$timestamp,则会抛出Phalcon\Encryption\Security\JWT\Exceptions\ValidatorException异常。

public function setId(string $id): Builder
设置 id (jti)。

public function setIssuedAt(int $timestamp): Builder
设置发布时间 (iat)。

public function setIssuer(string $issuer): Builder
设置签发者 (iss)。

public function setNotBefore(int $timestamp): Builder
设置生效时间 (nbf)。如果当前时间晚于$timestamp,则会抛出Phalcon\Encryption\Security\JWT\Exceptions\ValidatorException异常。

public function setSubject(string $subject): Builder
设置主题 (sub)。

public function setPassphrase(string $passphrase): Builder
设置密钥短语。如果$passphrase过于简单,则会抛出Phalcon\Encryption\Security\JWT\Exceptions\ValidatorException异常。

private function setClaim(string $name, $value): Builder
在内部集合中设置一个声明值。

示例

<?php

use Phalcon\Encryption\Security\JWT\Builder;
use Phalcon\Encryption\Security\JWT\Signer\Hmac;
use Phalcon\Encryption\Security\JWT\Token\Parser;
use Phalcon\Encryption\Security\JWT\Validator;

// 'sha512'
$signer  = new Hmac();

$builder = new Builder($signer);

$now        = new DateTimeImmutable();
$issued     = $now->getTimestamp();
$notBefore  = $now->modify('-1 minute')->getTimestamp();
$expires    = $now->modify('+1 day')->getTimestamp();
$passphrase = 'QcMpZ&b&mo3TPsPk668J6QH8JA$&U&m2';

$builder
    ->setAudience('https://target.phalcon.io')  // aud
    ->setContentType('application/json')        // cty - header
    ->setExpirationTime($expires)               // exp 
    ->setId('abcd123456789')                    // JTI id 
    ->setIssuedAt($issued)                      // iat 
    ->setIssuer('https://phalcon.io')           // iss 
    ->setNotBefore($notBefore)                  // nbf
    ->setSubject('my subject for this claim')   // sub
    ->setPassphrase($passphrase)                // password 
;

// Phalcon\Encryption\Security\JWT\Token\Token 
$tokenObject = $builder->getToken();

echo $tokenObject->getToken();

// eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiIsImN0eSI6ImFwcGxpY2F0aW9uXC9qc29uIn0.
// eyJhdWQiOlsiaHR0cHM6XC9cL3RhcmdldC5waGFsY29uLmlvIl0sImV4cCI6MTYxNDE4NTkxN
// ywianRpIjoiYWJjZDEyMzQ1Njc4OSIsImlhdCI6MTYxNDA5OTUxNywiaXNzIjoiaHR0cHM6XC
// 9cL3BoYWxjb24uaW8iLCJuYmYiOjE2MTQwOTk0NTcsInN1YiI6Im15IHN1YmplY3QgZm9yIHR
// oaXMgY2xhaW0ifQ.
// LdYevRZaQDZ2lul4CCQ5DymeP2ubcapTtgeezOZGIq7Meu7rFF1pv32b-AMWOxCS63CQz_jpm
// BPlPyOeEAkMbg

验证令牌

要验证一个令牌,你需要创建一个新的Phalcon\Encryption\Security\JWT\Validator对象。该对象可以通过传递一个Phalcon\Encryption\Security\JWT\Token\Token对象和一个时间偏移量,用于处理发送方与接收方计算机之间的时间/时钟偏差。

要解析收到的 JWT 并将其转换为Phalcon\Encryption\Security\JWT\Token\Token对象,你需要使用一个Phalcon\Encryption\Security\JWT\Token\Parser对象并进行解析。

验证器

$parser = new Parser();

$tokenObject = $parser->parse($tokenReceived);

$validator = new Validator($tokenObject, 100); // allow for a time shift of 100
您可以使用Phalcon\Encryption\Security\JWT\Validator对象来通过调用相应的方法验证每个声明(claim),方法中需要传入必要的参数(这些参数取自validate* methods with the necessary parameters (taken from the Phalcon\Encryption\Security\Token\Token)。内部的errors数组在Phalcon\Encryption\Security\JWT\Validator中会被相应地填充,并通过getErrors()方法设置容器。

方法

public function __construct(Token $token, int $timeShift = 0)
构造函数

public function get(string $claim): mixed | null
返回声明(claim)的值 ——null如果该声明不存在,则返回

public function set(string $claim, mixed $value): Validator
设置声明及其值。

public function setToken(Token $token): Validator
设置 token 对象。

public function validateAudience(array|string $audience): Validator
验证受众(audience)。如果 token 中不包含该aud声明,Phalcon\Encryption\Security\JWT\Exceptions\ValidatorException异常。

public function validateExpiration(int $timestamp): Validator
验证过期时间。如果 token 中存储的exp时间大于当前时间,则抛出Phalcon\Encryption\Security\JWT\Exceptions\ValidatorException异常。

public function validateId(string $id): Validator
验证 ID。如果与 token 中存储的jti不相同,则抛出Phalcon\Encryption\Security\JWT\Exceptions\ValidatorException异常。

public function validateIssuedAt(int $timestamp): Validator
验证issued at时间。如果 token 中存储的iat时间大于当前时间,则抛出Phalcon\Encryption\Security\JWT\Exceptions\ValidatorException异常。

public function validateIssuer(string $issuer): Validator
验证签发者(issuer)。如果与 token 中存储的iss不相同,则抛出Phalcon\Encryption\Security\JWT\Exceptions\ValidatorException异常。

public function validateNotBefore(int $timestamp): Validator
验证生效时间(not before time)。如果 token 中存储的nbf时间大于当前时间,则抛出Phalcon\Encryption\Security\JWT\Exceptions\ValidatorException异常。

public function validateSignature(SignerInterface $signer, string $passphrase): Validator
验证 token 的签名。如果签名无效,则抛出Phalcon\Encryption\Security\JWT\Exceptions\ValidatorException异常。

示例

<?php

use Phalcon\Encryption\Security\JWT\Signer\Hmac;
use Phalcon\Encryption\Security\JWT\Token\Parser;
use Phalcon\Encryption\Security\JWT\Validator;

// eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiIsImN0eSI6ImFwcGxpY2F0aW9uXC9qc29uIn0.
// eyJhdWQiOlsiaHR0cHM6XC9cL3RhcmdldC5waGFsY29uLmlvIl0sImV4cCI6MTYxNDE4NTkxN
// ywianRpIjoiYWJjZDEyMzQ1Njc4OSIsImlhdCI6MTYxNDA5OTUxNywiaXNzIjoiaHR0cHM6XC
// 9cL3BoYWxjb24uaW8iLCJuYmYiOjE2MTQwOTk0NTcsInN1YiI6Im15IHN1YmplY3QgZm9yIHR
// oaXMgY2xhaW0ifQ.
// LdYevRZaQDZ2lul4CCQ5DymeP2ubcapTtgeezOZGIq7Meu7rFF1pv32b-AMWOxCS63CQz_jpm
// BPlPyOeEAkMbg

$tokenReceived = getMyTokenFromTheApplication();
$audience      = 'https://target.phalcon.io';
$now           = new DateTimeImmutable();
$issued        = $now->getTimestamp();
$notBefore     = $now->modify('-1 minute')->getTimestamp();
$expires       = $now->getTimestamp();
$id            = 'abcd123456789';
$issuer        = 'https://phalcon.io';

// 'sha512'
$signer     = new Hmac();
$passphrase = 'QcMpZ&b&mo3TPsPk668J6QH8JA$&U&m2';

$parser      = new Parser();

// Phalcon\Encryption\Security\JWT\Token\Token 
$tokenObject = $parser->parse($tokenReceived);

// Phalcon\Encryption\Security\JWT\Validator 
$validator = new Validator($tokenObject, 100); // allow for a time shift of 100

$validator
    ->validateAudience($audience)
    ->validateExpiration($expires)
    ->validateId($id)
    ->validateIssuedAt($issued)
    ->validateIssuer($issuer)
    ->validateNotBefore($notBefore)
    ->validateSignature($signer, $passphrase)
;

var_dump($validator->getErrors());

令牌

另外,你也可以使用verify()validate() your token using the relevant methods in the Phalcon\Encryption\Security\Token\Token对象中的相关方法手动

方法

public function validate(Validator $validator): array
验证 token 的声明。执行的验证器包括:

  • validateAudience()
  • validateExpiration()
  • validateId()
  • validateIssuedAt()
  • validateIssuer()
  • validateNotBefore()

你可以扩展Phalcon\Encryption\Security\JWT\ValidatorPhalcon\Encryption\Security\Token\Token对象以添加更多的验证器并在其中执行它们(如下所示)。

public function verify(SignerInterface $signer, string $key): bool
验证 token 的签名

示例

<?php

use Phalcon\Encryption\Security\JWT\Enum;
use Phalcon\Encryption\Security\JWT\Signer\Hmac;
use Phalcon\Encryption\Security\JWT\Token\Parser;
use Phalcon\Encryption\Security\JWT\Validator;

// eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiIsImN0eSI6ImFwcGxpY2F0aW9uXC9qc29uIn0.
// eyJhdWQiOlsiaHR0cHM6XC9cL3RhcmdldC5waGFsY29uLmlvIl0sImV4cCI6MTYxNDE4NTkxN
// ywianRpIjoiYWJjZDEyMzQ1Njc4OSIsImlhdCI6MTYxNDA5OTUxNywiaXNzIjoiaHR0cHM6XC
// 9cL3BoYWxjb24uaW8iLCJuYmYiOjE2MTQwOTk0NTcsInN1YiI6Im15IHN1YmplY3QgZm9yIHR
// oaXMgY2xhaW0ifQ.
// LdYevRZaQDZ2lul4CCQ5DymeP2ubcapTtgeezOZGIq7Meu7rFF1pv32b-AMWOxCS63CQz_jpm
// BPlPyOeEAkMbg

$tokenReceived = getMyTokenFromTheApplication();
$subject       = 'Mary had a little lamb';
$audience      = 'https://target.phalcon.io';
$now           = new DateTimeImmutable();
$issued        = $now->getTimestamp();
$notBefore     = $now->modify('-1 minute')->getTimestamp();
$expires       = $now->getTimestamp();
$id            = 'abcd123456789';
$issuer        = 'https://phalcon.io';

// 'sha512'
$signer     = new Hmac();
$passphrase = 'QcMpZ&b&mo3TPsPk668J6QH8JA$&U&m2';

$parser      = new Parser();

// Phalcon\Encryption\Security\JWT\Token\Token 
$tokenObject = $parser->parse($tokenReceived);

// Phalcon\Encryption\Security\JWT\Validator 
$validator = new Validator($tokenObject, 100); // allow for a time shift of 100

$validator
    ->set(Enum::AUDIENCE, $audience)
    ->set(Enum::EXPIRATION_TIME, $expiry)
    ->set(Enum::ISSUER, $issuer)
    ->set(Enum::ISSUED_AT, $issued)
    ->set(Enum::ID, $id)
    ->set(Enum::NOT_BEFORE, $notBefore)
    ->set(Enum::SUBJECT, $subject)
;

$tokenObject->verify($signer, $passphrase);
$errors = $tokenObject->validate($validator);

var_dump($errors);

异常

Security 组件中抛出的任何异常都属于命名空间Phalcon\Encryption\Security\JWT\*。你可以利用这个异常有选择性地捕获仅来自该组件的异常。此处会引发两个异常:第一个异常是在实例化Phalcon\Encryption\Security\JWT\Signer\Hmac组件时传入了错误的算法字符串。此异常是Phalcon\Encryption\Security\JWT\Exceptions\UnsupportedAlgorithmException.

第二个异常在验证 JWT 时抛出。此异常是Phalcon\Encryption\Security\JWT\Exceptions\ValidatorException.

<?php

use Phalcon\Mvc\Controller;
use Phalcon\Encryption\Security\JWT\Builder;
use Phalcon\Encryption\Security\JWT\Exceptions\ValidatorException;
use Phalcon\Encryption\Security\JWT\Signer\Hmac;
use Phalcon\Encryption\Security\JWT\Validator;

class IndexController extends Controller
{
    public function index()
    {
        try {
            $signer     = new Hmac();
            $builder    = new Builder($signer);
            $expiry     = strtotime('+1 day');
            $issued     = strtotime('now') + 100;
            $notBefore  = strtotime('-1 day');
            $passphrase = '&vsJBETaizP3A3VX&TPMJUqi48fJEgN7';

            return $builder
                ->setAudience('my-audience')
                ->setExpirationTime($expiry)
                ->setIssuer('Phalcon JWT')
                ->setIssuedAt($issued)
                ->setId('PH-JWT')
                ->setNotBefore($notBefore)
                ->setSubject('Mary had a little lamb')
                ->setPassphrase($passphrase)
                ->getToken()
            ;

            $validator = new Validator($token);
            $validator->validateAudience("unknown");
        } catch (Exception $ex) {
            echo $ex->getMessage(); // Validation: audience not allowed
        }
    }
}
无噪 Logo
无噪文档
25 年 6 月翻译
版本号 5.9
文档源↗