rbdwllr/reallysimplejwt

一个用于生成用户认证JSON Web Tokens的简单库。

5.0.0 2022-04-16 14:00 UTC

README

Actions Status codecov Infection MSI StyleCI Latest Stable Version Packagist PHP Version Support Total Downloads

这是一个简单的PHP库,用于创建使用HMAC SHA256进行签名的JSON Web Tokens。对于基本使用,库公开了一个静态接口,允许开发者创建一个存储用户标识和过期时间的令牌。

该库也支持扩展,开发者可以定义自己的编码和密钥标准,设置所有RFC标准 JWT声明,并设置自己的私有声明。

你可以轻松地将ReallySimpleJWT与PSR-7 / PSR-15兼容的框架,如Slim PHP,通过PSR-JWT中间件库进行集成。请阅读框架集成文档以了解更多信息。

如果你需要在浏览器中读取令牌,请查看我们的JavaScript / TypeScript库RS-JWT

内容

什么是JSON Web Token?

JSON Web Tokens是一种创建关于用户或系统的声明的URL友好访问令牌的标准。

令牌分为三部分;头部、有效载荷和签名;各部分之间由点分隔。每一部分都使用base64URL标准进行编码,请参阅RFC

一个JWT示例

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

头部和有效载荷都是包含多个声明的编码JSON字符串

// Example Header
{
  "alg": "HS256",
  "typ": "JWT"
}

// Example Payload
{
  "sub": "1234567890",
  "name": "John Doe",
  "iat": 1516239022
}

声明是一个键值对,例如"typ": "JWT",请阅读RFC 7519以了解更多关于JSON Web Token声明的信息。

通过签名实现令牌安全,签名由头部、有效载荷和只有令牌作者知道的密钥组成。这些信息被散列然后进行base64URL编码。

如果恶意用户尝试编辑头部或有效载荷声明,只要使用强大的密钥,他们将无法复制签名。请参阅令牌安全以了解更多信息。

设置

要安装此包,您需要安装Composer,然后运行composer init。完成此操作后,您可以通过命令行或通过编辑由composer init命令创建的composer.json文件来安装包。

最后,您需要在PHP代码中引用Composer自动加载器,require 'vendor/autoload.php';。自动加载文件的位置取决于您的代码在哪里运行,如果您使用的是框架,可能不需要它。

通过命令行使用Composer安装

composer require rbdwllr/reallysimplejwt

通过composer.json文件安装

将以下内容添加到您的composer.json文件中

"require": {
    "rbdwllr/reallysimplejwt": "^5.0"
}

然后运行

composer update

基本使用

对于基本使用,该库通过ReallySimpleJWT\Token类公开了一组静态方法,允许开发人员创建和验证基本的JSON Web Tokens。

创建令牌

调用create()静态方法,传入用户标识符、密钥、过期日期时间数字和令牌发行者。

如果成功,这将返回一个令牌字符串,如果失败,将抛出ReallySimpleJWT\Exception\BuildException

use ReallySimpleJWT\Token;

$userId = 12;
$secret = 'sec!ReT423*&';
$expiration = time() + 3600;
$issuer = 'localhost';

$token = Token::create($userId, $secret, $expiration, $issuer);

要创建更自定义的令牌,开发者可以使用customPayload()方法。这允许根据表示有效载荷声明的键值对数组创建令牌。

use ReallySimpleJWT\Token;

$payload = [
    'iat' => time(),
    'uid' => 1,
    'exp' => time() + 10,
    'iss' => 'localhost'
];

$secret = 'Hello&MikeFooBar123';

$token = Token::customPayload($payload, $secret);

如果成功,customPayload()方法将返回JWT令牌字符串,如果失败将抛出异常。

验证令牌

要验证JSON Web Token,调用validate()静态方法,传入令牌字符串和密钥。验证方法检查令牌结构是否正确,签名是否有效。

如果成功返回true,如果失败返回false。

use ReallySimpleJWT\Token;

$token = 'aaa.bbb.ccc';
$secret = 'sec!ReT423*&';

$result = Token::validate($token, $secret);

还有可用于验证令牌的过期声明和不可用声明的其他方法。两者在成功时都返回true,失败时返回false。

use ReallySimpleJWT\Token;

$token = 'aaa.bbb.ccc';

Token::validateExpiration($token);

Token::validateNotBefore($token);

获取头部和有效载荷声明数据

要从头部或有效载荷检索令牌声明数据,请调用getHeader()和/或getPayload()静态方法。

这两种方法在成功时都返回关联数组,在失败时抛出异常。

use ReallySimpleJWT\Token;

$token = 'aaa.bbb.ccc';

// Return the header claims
Token::getHeader($token);

// Return the payload claims
Token::getPayload($token);

构建、解析和验证工厂方法

ReallySimpleJWT\Token类还提供了三个工厂方法,用于访问核心的ReallySimpleJWT\BuildReallySimpleJWT\ParseReallySimpleJWT\Validate类。这些类允许您根据需要构建自定义令牌,并解析和验证令牌。

Token::builder(); // Returns an instance of ReallySimpleJWT\Build

Token::parser($token); // Returns an instance of ReallySimpleJWT\Parse

Token::validator($token, $secret); // Returns an instance of ReallySimpleJWT\Validate

非静态使用

ReallySimpleJWT\Token类是ReallySimpleJWT\Tokens类的包装器,对于那些更喜欢实例化和注入功能的人来说,可以直接使用。

use ReallySimpleJWT\Tokens;

$tokens = new Tokens();

$id = 52;
$secret = 'sec!ReT423*&';
$expiration = time() + 50;
$issuer = 'localhost';

$token = $tokens->create('id', $id, $secret, $expiration, $issuer);
$token->getToken();

请注意,当在Tokens类上调用create()customPayload()方法时,它们将返回Jwt类的一个实例,而Token类将返回一个令牌字符串。

此外,create()方法与Tokens类的签名略有不同,因为必须传入用户标识符键。

create(string $userKey, $userId, string $secret, int $expiration, string $issuer): Jwt

高级使用

要创建自定义的JSON Web Tokens,开发者需要直接访问ReallySimpleJWT\BuildReallySimpleJWT\ParseReallySimpleJWT\Validate类。

创建自定义令牌

ReallySimpleJWT\Build类允许您创建一个完全独特的JSON Web Token。它提供了所有RFC定义的头部和有效载荷声明的辅助方法。例如,setIssuer()方法将添加iss声明到令牌有效载荷中。

该类还允许开发者通过setHeaderClaim()setPayloadClaim()方法设置自定义头部和有效载荷声明。

可以将这些方法链接在一起,当调用build()方法时,将生成并返回一个ReallySimpleJWT\Jwt对象。

use ReallySimpleJWT\Build;
use ReallySimpleJWT\Helper\Validator;
use ReallySimpleJWT\Encoders\EncodeHS256;

$secret = '!secReT$123*';

$build = new Build('JWT', new Validator(), new EncodeHS256($secret));

$token = $build->setContentType('JWT')
    ->setHeaderClaim('info', 'foo')
    ->setIssuer('localhost')
    ->setSubject('admins')
    ->setAudience('https://google.com')
    ->setExpiration(time() + 30)
    ->setNotBefore(time() - 30)
    ->setIssuedAt(time())
    ->setJwtId('123ABC')
    ->setPayloadClaim('uid', 12)
    ->build();

访问令牌

当开发者在ReallySimpleJWT\Build类上调用build()方法时,将返回一个ReallySimpleJWT\Jwt对象。Jwt类提供了一个getToken()方法,用于返回令牌字符串。

要通过ReallySimpleJWT\Parse类解析JSON Web Token,开发者必须首先通过实例化时注入令牌字符串来创建一个新的ReallySimpleJWT\Jwt对象。Jwt类将在实例化时验证令牌结构以确保完整性。

use ReallySimpleJWT\Jwt;

$token = 'aaa.bbb.ccc';

$jwt = new Jwt($token);

// Return the token
$jwt->getToken();

解析令牌

ReallySimpleJWT\Parse类允许开发者解析JWT,parse()方法将解码JSON Web Token,并将结果作为ReallySimpleJWT\Parsed对象返回。这将提供对令牌持有的头部和有效载荷声明数据的访问。

use ReallySimpleJWT\Parse;
use ReallySimpleJWT\Jwt;
use ReallySimpleJWT\Decode;

$token = 'aaa.bbb.ccc';

$jwt = new Jwt($token);

$parse = new Parse($jwt, new Decode());

$parsed = $parse->parse();

// Return the token header claims as an associative array.
$parsed->getHeader();

// Return the token payload claims as an associative array.
$parsed->getPayload();

访问令牌声明数据

当开发者调用 ReallySimpleJWT\Parse 类的 parse() 方法时,将返回 ReallySimpleJWT\Parsed 类。

它提供了一些辅助方法来访问令牌声明数据。开发者可以通过调用 getHeader()getPayload() 方法来获取相应的声明数据作为关联数组。

开发者还可以调用RFC 兼容的声明方法之一

头部

  • getAlgorithm()
  • getType()
  • getContentType()

载荷

  • getIssuer()
  • getSubject()
  • getAudience()
  • getExpiration()
  • getNotBefore()
  • getIssuedAt()
  • getJwtId()
  • getExpiresIn()
  • getUsableIn()

令牌验证方法

要验证 JSON Web Token,开发者可以使用 ReallySimpleJWT\Validate 类。要使用验证类,需要创建并注入一个 ReallySimpleJWT\Parsed 对象。这样验证类就可以访问令牌中的信息。

use ReallySimpleJWT\Jwt;
use ReallySimpleJWT\Parse;
use ReallySimpleJWT\Validate;
use ReallySimpleJwt\Decode;
use ReallySimpleJwt\Encoders\EncodeHS256;
use ReallySimpleJwt\Helper\Validator;

$token = new Jwt('abc.def.ghi');

$parse = new Parse($jwt, new Decode());

$parsed = $parse->parse();

$validate = new Validate(
    $parsed,
    new EncodeHS256(),
    new Validator()
);

$validate->signature();

有六个验证方法可用,都可以串联使用

  • signature() 确认令牌签名是有效的。
  • expiration() 确认令牌过期声明(exp)尚未过期。
  • notBefore() 确认令牌不早于声明(nbf)已过期。
  • audience() 确认令牌受众声明(aud)与预期匹配。
  • algorithm() 确认令牌算法声明(alg)与预期匹配且有效(参见:RFC 7518)。
  • algorithmNotNone() 确认令牌算法声明(alg)未设置为无。

如果提供的令牌有任何问题,每个验证方法都会抛出 ReallySimpleJWT\Exception\ValidateException

自定义编码

默认情况下,该库使用 hash_hmac() 通过 sha256 算法哈希和编码 JWT 签名。如果开发者想使用自定义的编码形式,只需生成一个符合 ReallySimpleJWT\Interfaces\Encode 接口的自定义编码类。然后可以将它注入到 ReallySimpleJWT\BuildReallySimpleJWT\Validate 类中。

interface EncodeInterface
{
    public function getAlgorithm(): string;

    public function encode(string $toEncode): string;

    public function signature(string $header, string $payload): string;
}

错误信息和代码

在创建、解析和验证 JWT 令牌的过程中,ReallySimpleJWT 库会在多种情况下抛出异常来突出显示问题。错误代码、消息及其说明如下表所示。

可能抛出六种异常类型

  • ReallySimpleJWT\Exception\BuildException
  • ReallySimpleJWT\Exception\EncodeException
  • ReallySimpleJWT\Exception\JwtException
  • ReallySimpleJWT\Exception\ParsedException
  • ReallySimpleJWT\Exception\TokensException
  • ReallySimpleJWT\Exception\ValidateException

令牌安全

JWT RFC 7519 允许创建没有签名和没有加密/哈希签名的令牌。然而,ReallySimpleJWT 库默认强制执行安全性,因为没有合理的理由不这样做。所有创建的令牌都必须有一个签名和一个强密钥,但库可以解析和验证没有密钥或强密钥的令牌。库不会验证没有签名的令牌。

默认情况下,ReallySimpleJWT 库提供了两种编码实现,ReallySimpleJWT\Encoders\EncodeHS256ReallySimpleJWT\Encoders\EncodeHS256Strong。后者强制执行严格的密钥安全性,并默认用于通过 TokenTokens 类创建令牌。EncodeHS256 不强制执行严格的密钥安全性,可以在需要时与 Build 类一起使用来创建令牌。此外,还可以通过实现 ReallySimpleJWT\Interfaces\Encode 接口创建自定义编码类。参见自定义编码部分。

密钥强度

此JWT库通过EncodeHS256Strong类强制执行严格的密钥安全性。提供的密钥长度必须至少为12个字符;包含数字;大小写字母;以及以下特殊字符之一:*&!@%^#$

// Bad Secret
secret123

// Good Secret
sec!ReT423*&

原因是存在大量的JWT破解工具,这意味着弱密钥很容易被破解,从而使JWT提供的签名安全性变得无用。

与PSR-JWT中间件的框架集成

您可以使用PSR-JWT库轻松地将ReallySimpleJWT与符合PSR-7 / PSR-15规范的框架(如Slim PHP和Laminas)集成。通过使用PSR-JWT库

例如,与Slim PHP的集成只需几行代码

// Can be added to any routes file in Slim, often index.php.
require '../../vendor/autoload.php';

$app->get('/jwt', function (Request $request, Response $response) {
    $response->getBody()->write("JSON Web Token is Valid!");

    return $response;
})->add(\PsrJwt\Factory\JwtAuth::middleware('Secret123!456$', 'jwt', 'Authentication Failed'));

请阅读PSR-JWT文档,了解关于ReallySimpleJWT的集成选项。

与RS-JWT的浏览器集成

当您创建JSON Web Tokens时,您可能希望阅读浏览器中包含在头和负载声明中的某些信息。

如果您这样做,我们有一个名为RS-JWT的NPM软件包。

安装

npm install --save rs-jwt

用法

import { parseJwt } from 'rs-jwt'

const result = parseJwt('json.web.token')

// Return the header claims as an object.
const header = result.getHeader()

// Access the type claim.
console.log(header.typ)

// Return the payload claims as an object.
const payload = result.getPayload()

// Access the expiry claim.
console.log(payload.exp)

有关更多信息,请参阅项目的README或访问NPM页面

许可证

MIT

作者

Rob Waller

Twitter: @RobDWaller