mixerapi/jwt-auth

CakePHP 的 JWT 身份验证插件。

安装次数: 1,772

依赖项: 0

建议者: 1

安全性: 0

星星: 1

关注者: 1

分支: 2

类型:cakephp-plugin

v2.0.3 2024-02-17 22:15 UTC

This package is auto-updated.

Last update: 2024-08-29 01:32:29 UTC


README

Latest Version on Packagist Build Coverage Status MixerApi CakePHP Minimum PHP Version

这是一个为 CakePHP 提供支持的 JWT 身份验证库,它支持 HMAC (HS256 或 HS512) 和 RSA (RS256 或 RS512) 加上 JSON Web Keys。在开始之前,您应该确定哪种 签名算法 最适合您的需求。这个库的目标是使两者都变得简单。

有关替代方法,请参阅 admad/cakephp-jwt-auth

安装

您可以使用 composer 将此插件安装到您的 CakePHP 应用程序中。

composer require mixerapi/jwt-auth

然后加载插件

bin/cake plugin load MixerApi/JwtAuth

配置

接下来,创建一个配置文件(例如 config/mixerapi_jwtauth.php),并将其加载到您的应用程序中。

# in config/bootstrap.php
Configure::load('mixerapi_jwtauth');

alg

alg 字符串是必需的,必须是 HS256、HS512、RS256 或 RS512 之一。

secret

当使用 HMAC 时,需要 secret 字符串。密钥不应提交到您的 VCS,且长度至少为 32 个字符。您可以使用 openssl 或 gpg 等工具生成强密码。

openssl rand -base64 24
gpg --armor --gen-random 1 24

keys

当使用 RSA 时,需要 keys 数组。密钥不应提交到您的 VCS,且长度至少为 2048 位。您可以使用 openssl 生成公钥/私钥对。

openssl genrsa -out config/keys/1/private.pem 2048
openssl rsa -in config/keys/1/private.pem -out config/keys/1/public.pem -pubout

服务提供者

建议使用 JwtAuthServiceProvider 来自动注入依赖项。

# in src/Application.php

public function services(ContainerInterface $container): void
{
    /** @var \League\Container\Container $container */
    $container->addServiceProvider(new \MixerApi\JwtAuth\JwtAuthServiceProvider());
}

身份验证

您需要配置 CakePHP 身份验证 以使用此库。有关如何快速启动的文档,请参阅快速入门。有关完整示例,请参阅 mixerapi 示例

确保加载 CakePHP Authentication.Component(通常在您的 AppController 中)。

以下是一个支持 HMAC 和 RSA(基于表单和密码身份验证)的示例。无论您如何实现身份验证,都建议使用 \MixerApi\JwtAuth\Configuration\Configuration 从您的 MixerApi.JwtAuth 配置文件 config/mixerapi_jwtauth.php 中提取值。这将验证您的配置,然后再将其应用到应用程序的身份验证中。

# in src/Application.php

public function getAuthenticationService(ServerRequestInterface $request): \Authentication\AuthenticationServiceInterface
{
    $fields = [
        \Authentication\Identifier\AbstractIdentifier::CREDENTIAL_USERNAME => 'email',
        \Authentication\Identifier\AbstractIdentifier::CREDENTIAL_PASSWORD => 'password',
    ];

    $config = new \MixerApi\JwtAuth\Configuration\Configuration();
    $service = new \Authentication\AuthenticationService();

    $service->loadAuthenticator('Authentication.Form', [
        'fields' => $fields,
        'loginUrl' => '/admin/auth/login'
    ]);

    $service->loadIdentifier('Authentication.JwtSubject');

    if (str_starts_with(haystack: $config->getAlg(), needle: 'HS')) {
        $service->loadAuthenticator('Authentication.Jwt', [
            'secretKey' => $config->getSecret(),
            'algorithm' => $config->getAlg(),
        ]);
    } else if (str_starts_with(haystack: $config->getAlg(), needle: 'RS')) {
        $jsonKeySet = \Cake\Cache\Cache::remember('jwkset', function() {
            return json_encode((new \MixerApi\JwtAuth\Jwk\JwkSet)->getKeySet());
        });

        /*
         * Caching is optional, you may also set the jwks key to the return value of (new JwkSet)->getKeySet()
         */
        $service->loadAuthenticator('Authentication.Jwt', [
            'jwks' => json_decode($jsonKeySet, true),
            'algorithm' => $config->getAlg(),
        ]);
    }

    $service->loadIdentifier('Authentication.Password', ['fields' => $fields]);

    return $service;
}

定义您的 JWT

在您的 User 实体中实现 JwtEntityInterface。这将被用来生成 JWT,例如

namespace App\Model\Entity;

use Cake\ORM\Entity;
use MixerApi\JwtAuth\Jwt\Jwt;
use MixerApi\JwtAuth\Jwt\JwtEntityInterface;
use MixerApi\JwtAuth\Jwt\JwtInterface;

class User extends Entity implements JwtEntityInterface
{
    /**
     * @inheritDoc
     */
    public function getJwt(): JwtInterface
    {
        return new Jwt(
            exp: time() + 60 * 60 * 24,
            sub: (string)$this->get('id'),
            iss: 'mixerapi',
            aud: 'mixerapi-client',
            nbf: null,
            iat: time(),
            jti: \Cake\Utility\Text::uuid(),
            claims: [
                'user' => [
                    'email' => $this->get('email')
                ]
            ]
        );
    }
}

JSON Web Keys

使用 RSA 签名令牌使用公钥/私钥对。如果您使用 HMAC,则可以跳过此部分。

构建密钥

我们将密钥存储在 config/keys/1/ 中,但您可以将它们存储在任何位置。密钥不应存储在版本控制中,例如

# in config/mixerapi_jwtauth.php

return [
    'MixerApi.JwtAuth' => [
        'alg' => 'RS256',
        'keys' => [
            [
                'kid' => '1',
                'public' => file_get_contents(CONFIG . 'keys' . DS . '1' . DS . 'public.pem'),
                'private' => file_get_contents(CONFIG . 'keys' . DS . '1' . DS . 'private.pem'),
            ]
        ]
    ]
];

JWK 集控制器

有关 JSON Web Keys 的更多信息,请参阅 JSON Web Keys。让我们创建一个端点来公开您的 JWK 集合。

use Cake\Controller\Controller;
use Cake\Event\EventInterface;
use MixerApi\JwtAuth\Jwk\JwkSetInterface;

class JwksController extends Controller
{
    public function beforeFilter(EventInterface $event)
    {
        parent::beforeFilter($event);
        $this->Authentication->allowUnauthenticated(['index']);
    }

    public function index(JwkSetInterface $jwkSet)
    {
        $this->set('data', $jwkSet->getKeySet());
        $this->viewBuilder()->setOption('serialize', 'data');
    }
}

在您的 config/routes.php 文件中添加一个路由到您的控制器。

示例响应

{
  "keys": [
    {
      "kty": "RSA",
      "use": "sig",
      "alg": "RS256",
      "kid": "1",
      "n": "wk865HbUKadJU-Mh-Iv2Z_30ZOMclkK1cbuiTVkINy_R9oHoAht2DS788q_Sll38dtTB4bzptd0u6k4cJd6Lj6nVQTe1uRyuAU47tqitiJmEXX_2SHIRv6aj4vygIfqr1FtQMHPlBW7r4q840H5mh_Z-E_a7d27QbtJ3eYNEiFow6LLvl17_7bdaenlwccY0j-PY1GzL7UwG8uHBZ78ZOcvu_GgaYC5suRrJrV_6_Qu6lySXObDaajr6Foz0m-z4Aj7KA8KmAiM_Rw_Yqm_KqPT3YBGj83TxeEiMPkrMYry123hFQYm09EO2Az9lGjr-PQc6SR08SDqZ3zbwe9iam55dzVZ-vQF3ASnZpBHyIDhCI7PFShceFI1Sv0RW7-Tl0uM2jQa1RyEpFle1xc0RxSFZium0aGMnFuE2W9JDERPw47wFZx2kSk1nB6PDK6XPLJLi_db0VrP5m5z2HDWeYVmsuAVFm6-l1PjiGH4G1TpuYfPKP2P8K-kveo1Ddm14IJSWfcACeAF_gx644Ua_IJ8wS98dQqE-R-jzfEv7aLBacP5_thCUbHfCRrAgtM5lBAM_1tfQ4XsOLnFWkl4arm3TzN2wCjjuqxipgwpUtY_SN6SXhJW4MW2qHVKtHtXl9haF5gEDBL7twDsFozYZCc5k0d85EgfJ5Jn7ZSAgwXk",
      "e": "AQAB"
    }
  ]
}

您可以根据密钥轮换策略添加/删除 MixerApi.JwtAuth.keys 配置中的密钥。

注意,如果您不使用依赖注入

    public function index()
    {
        $this->set('data', (new JwkSet)->getKeySet());
        $this->viewBuilder()->setOption('serialize', 'data');
    }

登录控制器

以下示例中,我们将进行身份验证,创建之前定义的JWT并返回给请求者。

use Cake\Controller\Controller;
use MixerApi\JwtAuth\JwtAuthenticatorInterface;

public function LoginController extends Controller
{
    public function beforeFilter(EventInterface $event)
    {
        parent::beforeFilter($event);
        $this->Authentication->allowUnauthenticated(['login']);
    }

    public function login(JwtAuthenticatorInterface $jwtAuth)
    {
        try {
            return $this->response->withStringBody($jwtAuth->authenticate($this->Authentication));
        } catch (UnauthenticatedException $e) {
            return $this->response->withStringBody($e->getMessage())->withStatus(401);
        }
    }
}

在您的 config/routes.php 文件中添加路由到控制器。

这将构建在用户实体中定义的JWT。

{
  "iss": "mixerapi",
  "sub": "5e28e9ed-f3e1-4eb2-aa88-8d618f4021ee",
  "aud": "api-client",
  "exp": 1651972707,
  "jti": "a1f6f5ec-748d-4a1c-9d0e-f8e19ec7f9b2",
  "user": {
    "email": "test@example.com"
  }
}

注意,如果您不使用依赖注入

    public function login()
    {
        try {
            return $this->response->withStringBody(
                (new \MixerApi\JwtAuth\JwtAuthenticator)->authenticate($this->Authentication)
            );
        } catch (UnauthenticatedException $e) {
            return $this->response->withStringBody($e->getMessage())->withStatus(401);
        }
    }

或者,如果您想自己处理身份验证,可以传递 JwtInterface 的实例,例如

    public function login(JwtAuthenticatorInterface $jwtAuth)
    {
        try {
            $result = $this->Authentication->getResult();
            if (!$result->isValid()) {
                throw new UnauthenticatedException();
            }
            return $this->response->withStringBody($jwtAuth->authenticate($result->getData()->getJwt()));
        } catch (UnauthenticatedException $e) {
            return $this->response->withStringBody($e->getMessage())->withStatus(401);
        }
    }

安全性

该库内置了一些安全措施

弱HMAC密钥

使用HMAC签名的JWT可以使用像 JWT Tool 这样的工具进行暴力破解。一旦破解,JWT可以被修改。该库通过要求最小密钥长度为32个字符来减轻这一影响,但您可能希望考虑使用64个字符,如果安全性比速度和令牌大小更重要。生成强随机密钥并确保其安全由您负责。

弱RSA密钥

弱密钥同样可以被破解。该库要求最小密钥长度为2048位。根据您的安全要求,您可能希望考虑使用4096位的密钥长度。确保您的密钥安全由您负责。

Alg None Bypass

通过要求单个有效算法来缓解alg=none签名绕过漏洞。在 firebase/php-jwt 库中存在额外的保护措施,该库应保持最新状态。

RS/HS256 公钥不匹配漏洞

通过要求单个有效算法来缓解。在 firebase/php-jwt 库中存在额外的保护措施,该库应保持最新状态。