Yii2 JWT

Yii2 JWT

这个扩展为Yii framework 2.0提供了JWT集成(需要PHP 5.6+)。它包括基本的HTTP身份验证支持。

目录

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

安装

Package is available on Packagist,
you can install it using 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参数来处理时钟偏差(见下面的注释)。

如果token的声明时间无效,但与验证时间之间的差异小于$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()将忽略这些声明。
  • exp, nbfiat 声明默认在 ValidationData::__construct() 中使用当前 UNIX 时间 (time()).
  • ValidationData的可选$leeway 参数将导致我们在验证基于时间的声明时使用该长度的秒数,
    假装我们在未来的“发出时间”(iat) 和“不在之前”(nbf)索赔,假装我们在过去的更远
    对于“过期时间”(exp)索赔。这允许在发出服务器的时钟与时钟的时间不同的情况下
    验证服务器,如RFC 7519第4.1节所述。

令牌签名

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

重要提示

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

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

Hmac

Hmac signatures are really simple to be used:

$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()
        {
            $request = Yii::$app->getRequest();
            $jwt = Yii::$app->jwt;
            $time = time();
    
            $token = $jwt->getBuilder()
                ->issuedBy($request->getHostInfo())
                ->permittedFor(isset($_SERVER['HTTP_ORIGIN']) ? $_SERVER['HTTP_ORIGIN'] : '')
                ->identifiedBy(Yii::$app->security->generateRandomString(10), true) //生成随机的盐值
                ->issuedAt($time)// 设置生成token的时间
                ->expiresAt($time + 3600)//设置token过期时间
                ->withClaim('uid', 100)//配置一个名为uid的新声明
                ->getToken($jwt->getSigner('HS256'), $jwt->getKey());
    
    
            // 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
                
            // 示例
            $jwt = Yii::$app->jwt;
            $signer = $jwt->getSigner('HS256');
            $key = $jwt->getKey();
            $time = time();
    
            // 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

posted @ 2020-01-10 11:31  牛奔  阅读(3928)  评论(3编辑  收藏  举报