danieldmf/yii2-jwt

基于 sizeg 的 JWT

安装: 1

依赖者: 0

推荐者: 0

安全: 0

星星: 0

关注者: 1

分支: 85

类型:yii2-extension

v2.0.2 2020-08-21 04:46 UTC

README

本扩展为 Yii 框架 2.0(需要 PHP 5.6+)提供了 JWT 集成(基于 JWT),包括基本的 HTTP 身份验证支持。

目录

  1. 安装
  2. 依赖关系
  3. 基本使用
    1. 创建
    2. 从字符串解析
    3. 验证
  4. 令牌签名
    1. Hmac
    2. RSA 和 ECDSA
  5. Yii2 基本模板示例

安装

该软件包可在 Packagist 上获取,您可以使用 Composer 进行安装。

composer require sizeg/yii2-jwt

依赖关系

基本使用

jwt 组件添加到您的配置文件中,

'components' => [
    'jwt' => [
      'class' => \sizeg\jwt\Jwt::class,
      'key'   => 'secret',
    ],
],

按以下方式配置 authenticator 行为。

namespace app\controllers;

class ExampleController extends \yii\rest\Controller
{

    /**
     * @inheritdoc
     */
    public function behaviors()
    {
        $behaviors = parent::behaviors();
        $behaviors['authenticator'] = [
            'class' => \sizeg\jwt\JwtHttpBearerAuth::class,
        ];

        return $behaviors;
    }
}

您还可以与 CompositeAuth 一起使用,请参阅 文档

创建

一些方法被标记为已弃用,并将很快从 lcobucci/jwt 4.x 返回以创建升级路径。

只需使用构建器创建新的 JWT/JWS 令牌

$time = time();
$token = Yii::$app->jwt->getBuilder()
            ->issuedBy('http://example.com') // Configures the issuer (iss claim)
            ->permittedFor('http://example.org') // Configures the audience (aud claim)
            ->identifiedBy('4f1g23a12aa', true) // Configures the id (jti claim), replicating as a header item
            ->issuedAt($time) // Configures the time that the token was issue (iat claim)
            ->canOnlyBeUsedAfter($time + 60) // Configures the time that the token can be used (nbf claim)
            ->expiresAt($time + 3600) // Configures the expiration time of the token (exp claim)
            ->withClaim('uid', 1) // Configures a new claim, called "uid"
            ->getToken(); // Retrieves the generated token


$token->getHeaders(); // Retrieves the token headers
$token->getClaims(); // Retrieves the token claims

echo $token->getHeader('jti'); // will print "4f1g23a12aa"
echo $token->getClaim('iss'); // will print "http://example.com"
echo $token->getClaim('uid'); // will print "1"
echo $token; // The string representation of the object is a JWT string (pretty easy, right?)

从字符串解析

使用解析器从 JWT 字符串创建新的令牌(以之前的令牌为例)

$token = Yii::$app->jwt->getParser()->parse((string) $token); // Parses from a string
$token->getHeaders(); // Retrieves the token header
$token->getClaims(); // Retrieves the token claims

echo $token->getHeader('jti'); // will print "4f1g23a12aa"
echo $token->getClaim('iss'); // will print "http://example.com"
echo $token->getClaim('uid'); // will print "1"

验证

我们可以轻松地验证令牌是否有效(以之前的令牌为例)

$data = Yii::$app->jwt->getValidationData(); // It will use the current time to validate (iat, nbf and exp)
$data->setIssuer('http://example.com');
$data->setAudience('http://example.org');
$data->setId('4f1g23a12aa');

var_dump($token->validate($data)); // false, because we created a token that cannot be used before of `time() + 60`

$data->setCurrentTime(time() + 61); // changing the validation time to future

var_dump($token->validate($data)); // true, because validation information is equals to data contained on the token

$data->setCurrentTime(time() + 4000); // changing the validation time to future

var_dump($token->validate($data)); // false, because token is expired since current time is greater than exp

我们还可以使用 $leeway 参数来处理时钟偏差(请参阅下面的说明)。如果令牌声明的有效时间无效,但该时间与验证时间的差值小于 $leeway,则令牌仍然被视为有效

'components' => [
    'jwt' => [
        'class' => \sizeg\jwt\Jwt:class,
        'key'   => 'secret',
        'jwtValidationData' => [
            'class' => \sizeg\jwt\JwtValidationData::class,
             // configure leeway 
            'leeway' => 20,
        ],
    ],
],
$dataWithLeeway = Yii::$app->jwt->getValidationData();
$dataWithLeeway->setIssuer('http://example.com');
$dataWithLeeway->setAudience('http://example.org');
$dataWithLeeway->setId('4f1g23a12aa');

var_dump($token->validate($dataWithLeeway)); // false, because token can't be used before now() + 60, not within leeway

$dataWithLeeway->setCurrentTime($time + 61); // changing the validation time to future

var_dump($token->validate($dataWithLeeway)); // true, because current time plus leeway is between "nbf" and "exp" claims

$dataWithLeeway->setCurrentTime($time + 3610); // changing the validation time to future but within leeway

var_dump($token->validate($dataWithLeeway)); // true, because current time - 20 seconds leeway is less than exp

$dataWithLeeway->setCurrentTime($time + 4000); // changing the validation time to future outside of leeway

var_dump($token->validate($dataWithLeeway)); // false, because token is expired since current time is greater than exp

重要

  • 您必须配置 ValidationData 以告知所有要验证的声明。
  • 如果 ValidationData 包含未在令牌中使用的声明或令牌具有未在 ValidationData 中配置的声明,则它们将由 Token::validate() 忽略。
  • expnbfiat 声明在 ValidationData::__construct() 中默认配置为当前 UNIX 时间(time())。
  • ValidationData 的可选 $leeway 参数将导致我们在验证基于时间的声明时使用该数量的秒数。对于 "Issued At" (iat) 和 "Not Before" (nbf) 声明,我们假装我们将进一步进入未来,而对于 "Expiration Time" (exp) 声明,我们假装我们将进一步进入过去。这允许在发行服务器的时钟与验证服务器的时钟不同的情况下进行操作,如 RFC 7519 的第 4.1 节所述。

令牌签名

我们可以使用签名来验证令牌在生成后是否未被修改。此扩展实现了 Hmac、RSA 和 ECDSA 签名(使用 256、384 和 512)。

重要

不要允许发送给解析器的字符串指定要使用的签名算法,否则您的应用程序将容易受到 关键的 JWT 安全漏洞 的攻击。

下面的示例是安全的,因为 Signer 的选择是硬编码的,不能被恶意用户影响。

Hmac

Hmac 签名非常简单易用

$jwt = Yii::$app->jwt;
$signer = $jwt->getSigner('HS256');
$key = $jwt->getKey();
$time = time();

$token = $jwt->getBuilder()
            ->issuedBy('http://example.com') // Configures the issuer (iss claim)
            ->permittedFor('http://example.org') // Configures the audience (aud claim)
            ->identifiedBy('4f1g23a12aa', true) // Configures the id (jti claim), replicating as a header item
            ->issuedAt($time) // Configures the time that the token was issue (iat claim)
            ->canOnlyBeUsedAfter($time + 60) // Configures the time that the token can be used (nbf claim)
            ->expiresAt($time + 3600) // Configures the expiration time of the token (exp claim)
            ->withClaim('uid', 1) // Configures a new claim, called "uid"
            ->getToken($signer, $key); // Retrieves the generated token

var_dump($token->verify($signer, 'testing 1')); // false, because the key is different
var_dump($token->verify($signer, 'testing')); // true, because the key is the same

RSA 和 ECDSA

RSA 和 ECDSA 签名基于公钥和私钥,因此您必须使用私钥生成签名,使用公钥进行验证。

$jwt = Yii::$app->jwt;
$signer = $jwt->getSigner('RS256'); // you can use 'ES256' if you're using ECDSA keys
$privateKey = $jwt->getKey('file://{path to your private key}');
$time = time();

$token = $jwt->getBuilder()
            ->issuedBy('http://example.com') // Configures the issuer (iss claim)
            ->permittedFor('http://example.org') // Configures the audience (aud claim)
            ->identifiedBy('4f1g23a12aa', true) // Configures the id (jti claim), replicating as a header item
            ->issuedAt($time) // Configures the time that the token was issue (iat claim)
            ->canOnlyBeUsedAfter($time + 60) // Configures the time that the token can be used (nbf claim)
            ->expiresAt($time + 3600) // Configures the expiration time of the token (exp claim)
            ->withClaim('uid', 1) // Configures a new claim, called "uid"
            ->getToken($signer, $privateKey); // Retrieves the generated token
            
$publicKey = $jwt->getKey('file://{path to your public key}');

var_dump($token->verify($signer, $publicKey)); // true when the public key was generated by the private one =)

重要的是要说明,如果您正在使用 RSA 密钥,则不应调用 ECDSA 签名者(反之亦然),否则 sign()verify() 将引发异常!

Yii2 基本模板示例

基本方案

  1. 客户端发送凭据。例如,登录名 + 密码
  2. 后端验证它们
  3. 如果凭据有效,客户端将收到令牌
  4. 客户端将令牌存储用于未来的请求

逐步使用示例

  1. 创建 Yii2 应用程序

    在此示例中,我们将使用 基本模板,但您也可以以相同的方式使用 高级模板

    composer create-project --prefer-dist --stability=dev yiisoft/yii2-app-basic yii2-jwt-test
  2. 安装组件

    composer require sizeg/yii2-jwt
  3. 将组件添加到 config/web.php 的 components 部分

    $config = [
        'components' => [
            // other default components here..
            'jwt' => [
                'class' => \sizeg\jwt\Jwt::class,
                'key' => 'secret',
                // You have to configure ValidationData informing all claims you want to validate the token.
                'jwtValidationData' => \app\components\JwtValidationData::class,
            ],
        ],
    ];
  4. 创建 JwtValidationData 类。在这里,您需要配置 ValidationData 以告知所有要验证的令牌声明。

    <?php
    
    namespace app\components;
    
    class JwtValidationData extends \sizeg\jwt\JwtValidationData
    {
     
        /**
         * @inheritdoc
         */
        public function init()
        {
            $this->validationData->setIssuer('http://example.com');
            $this->validationData->setAudience('http://example.org');
            $this->validationData->setId('4f1g23a12aa');
    
            parent::init();
        }
    }    
  5. 更改方法 app\models\User::findIdentityByAccessToken()

        /**
         * {@inheritdoc}
         * @param \Lcobucci\JWT\Token $token
         */
        public static function findIdentityByAccessToken($token, $type = null)
        {
            foreach (self::$users as $user) {
                if ($user['id'] === (string) $token->getClaim('uid')) {
                    return new static($user);
                }
            }
    
            return null;
        }
  6. 创建控制器

    <?php
    
    namespace app\controllers;
    
    use sizeg\jwt\Jwt;
    use sizeg\jwt\JwtHttpBearerAuth;
    use Yii;
    use yii\rest\Controller;
    
    class RestController extends Controller
    {
        /**
         * @inheritdoc
         */
        public function behaviors()
        {
            $behaviors = parent::behaviors();
            $behaviors['authenticator'] = [
                'class' => JwtHttpBearerAuth::class,
                'optional' => [
                    'login',
                ],
            ];
    
            return $behaviors;
        }
    
        /**
         * @return \yii\web\Response
         */
        public function actionLogin()
        {
            /** @var Jwt $jwt */
            $jwt = Yii::$app->jwt;
            $signer = $jwt->getSigner('HS256');
            $key = $jwt->getKey();
            $time = time();
    
            // Previous implementation
            /*
            $token = $jwt->getBuilder()
                ->setIssuer('http://example.com')// Configures the issuer (iss claim)
                ->setAudience('http://example.org')// Configures the audience (aud claim)
                ->setId('4f1g23a12aa', true)// Configures the id (jti claim), replicating as a header item
                ->setIssuedAt(time())// Configures the time that the token was issue (iat claim)
                ->setExpiration(time() + 3600)// Configures the expiration time of the token (exp claim)
                ->set('uid', 100)// Configures a new claim, called "uid"
                ->sign($signer, $jwt->key)// creates a signature using [[Jwt::$key]]
                ->getToken(); // Retrieves the generated token
            */
    
            // Adoption for lcobucci/jwt ^4.0 version
            $token = $jwt->getBuilder()
                ->issuedBy('http://example.com')// Configures the issuer (iss claim)
                ->permittedFor('http://example.org')// Configures the audience (aud claim)
                ->identifiedBy('4f1g23a12aa', true)// Configures the id (jti claim), replicating as a header item
                ->issuedAt($time)// Configures the time that the token was issue (iat claim)
                ->expiresAt($time + 3600)// Configures the expiration time of the token (exp claim)
                ->withClaim('uid', 100)// Configures a new claim, called "uid"
                ->getToken($signer, $key); // Retrieves the generated token
    
            return $this->asJson([
                'token' => (string)$token,
            ]);
        }
    
        /**
         * @return \yii\web\Response
         */
        public function actionData()
        {
            return $this->asJson([
                'success' => true,
            ]);
        }
    }
  7. 发送简单的登录请求以获取令牌。在此示例中,我们不发送任何凭据以简化示例。如我们在 authenticator 行为动作 login 中指定,authenticator 将跳过对那个动作的认证检查。 image

  8. 首先,我们尝试发送没有令牌的请求到 rest/data 并得到错误 Unauthorized image

  9. 然后我们重新发送请求,但已添加包含我们的令牌的 Authorization 标头 image