farit-slv/yii2-jwt
基于Icobucci的JWT
Requires
- php: ^7.4 || ^8.0
- lcobucci/jwt: ~4.2.1
- phpunit/phpunit: ^9.5
- yiisoft/yii2: ~2.0.0
This package is auto-updated.
Last update: 2024-09-14 07:32:59 UTC
README
此扩展为Yii框架2.0(需要PHP 7.4或^8.0)提供JWT集成(包括基本的HTTP身份验证支持)。
目录
安装
该软件包可在Packagist上找到,您可以使用Composer安装它。
composer require farit-slv/yii2-jwt
依赖关系
- PHP 7.4或8.0+
- OpenSSL扩展
- lcobucci/jwt 4.2
基本使用
将jwt
组件添加到您的配置文件中,
'components' => [ 'jwt' => [ 'class' => \sizeg\jwt\Jwt::class, 'constraints' => [ function () { return new \Lcobucci\JWT\Validation\Constraint\LooseValidAt( \Lcobucci\Clock\SystemClock::fromSystemTimezone() ); }, ], ], ],
按以下方式配置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
,请参阅文档。
创建
只需使用构建器创建新的JWT/JWS令牌
$now = new DateTimeImmutable(); $algorithm = $this->jwt->getSigner(); $key = $this->jwt->getSignerKey(); $token = Yii::$app->jwt->getBuilder() // Configures the issuer (iss claim) ->issuedBy('http://example.com') // Configures the audience (aud claim) ->permittedFor('http://example.org') // Configures the id (jti claim) ->identifiedBy('4f1g23a12aa') // Configures the time that the token was issue (iat claim) ->issuedAt($now) // Configures the time that the token can be used (nbf claim) ->canOnlyBeUsedAfter($now->modify('+1 minute')) // Configures the expiration time of the token (exp claim) ->expiresAt($now->modify('+1 hour')) // Configures a new claim, called "uid" ->withClaim('uid', 1) // Configures a new header, called "foo" ->withHeader('foo', 'bar') // Builds a new token ->getToken($algorithm, $key); $token->headers(); // Retrieves the token headers $token->claims(); // Retrieves the token claims echo $token->headers()->get('foo'); // will print "bar" echo $token->claims()->get('jti'); // will print "4f1g23a12aa" echo $token->claims()->get('iss'); // will print "http://example.com" echo $token->claims()->get('uid'); // will print "1" echo $token->toString(); // The string representation of the object is a JWT string (pretty easy, right?)
从字符串解析
使用解析器从JWT字符串创建新的令牌(使用前面的令牌作为示例)
use Lcobucci\JWT\Encoding\CannotDecodeContent; use Lcobucci\JWT\Token\InvalidTokenStructure; use Lcobucci\JWT\Token\UnsupportedHeaderFound; use Lcobucci\JWT\UnencryptedToken; try { /** @var string $jwt JWT token string */ $token = Yii::$app->jwt->parse($jwt); // Parses from a string } catch (CannotDecodeContent | InvalidTokenStructure | UnsupportedHeaderFound $e) { echo 'Oh no, an error: ' . $e->getMessage(); } assert($token instanceof UnencryptedToken);
验证
我们可以轻松地验证令牌是否有效(使用前面的令牌作为示例)
use \Lcobucci\JWT\Validation\Constraint\IssuedBy; if (!Yii::$app->jwt->validate($token, new IssuedBy('http://example.com'))) { echo 'Invalid token (1)!', PHP_EOL; // will not print this } if (!Yii::$app->jwt->validate($token, new IssuedBy('http://example.org'))) { echo 'Invalid token (1)!', PHP_EOL; // will print this }
可用约束
\Lcobucci\JWT\Validation\Constraint\IdentifiedBy
:验证jti声明是否与预期值匹配\Lcobucci\JWT\Validation\Constraint\IssuedBy
:验证iss声明是否列在预期值中\Lcobucci\JWT\Validation\Constraint\PermittedFor
:验证aud声明是否包含预期值\Lcobucci\JWT\Validation\Constraint\RelatedTo
:验证sub声明是否与预期值匹配\Lcobucci\JWT\Validation\Constraint\SignedWith
:验证令牌是否使用预期的签名者和密钥签名\Lcobucci\JWT\Validation\Constraint\StrictValidAt
:验证iat、nbf和exp声明的存在和有效性(支持容差配置)\Lcobucci\JWT\Validation\Constraint\LooseValidAt
:验证iat、nbf和exp声明,当存在时(支持容差配置)\Lcobucci\JWT\Validation\Constraint\HasClaimWithValue
:验证自定义声明是否具有预期值(不建议在比较加密散列时使用)
重要
- 您必须配置
\sizeg\jwt\Jwt::$constraints
,通过Yii::$app->jwt->loadToken()
通知所有要验证的声明,该方法也包含在\sizeg\jwt\JwtHttpBearerAuth
中。
令牌签名
我们可以使用签名来验证令牌在生成后是否被修改。此扩展实现了Hmac、RSA和ECDSA签名(使用256、384和512)。
重要
不要允许发送给解析器的字符串指定要使用的签名算法,否则您的应用程序将容易受到关键JWT安全漏洞的影响。
以下示例是安全的,因为Signer
的选择是硬编码的,无法被恶意用户影响。
Hmac
Hmac签名非常简单易用。
您可能需要配置组件
'components' => [ 'jwt' => [ 'class' => \sizeg\jwt\Jwt::class, 'signer' => \sizeg\jwt\JwtSigner::HS256, 'signerKey' => \sizeg\jwt\JwtKey::PLAIN_TEXT, 'signerKeyContents' => random_bytes(32), 'signerKeyPassphrase' => 'secret', 'constraints' => [ function () { // Verifies the claims iat, nbf, and exp, when present (supports leeway configuration) return new \Lcobucci\JWT\Validation\Constraint\LooseValidAt( \Lcobucci\Clock\SystemClock::fromSystemTimezone() ); }, function () { // Verifies if the token was signed with the expected signer and key return new \Lcobucci\JWT\Validation\Constraint\SignedWith( Yii::$app->jwt->getSigner(), Yii::$app->jwt->getSignerKey() ); }, ], ], ],
use \Lcobucci\JWT\Validation\Constraint\SignedWith; $now = new DateTimeImmutable(); $algorithm = $this->jwt->getSigner(\sizeg\jwt\JwtSigner::HS256); // ... and key $contents = random_bytes(32); $passphrase = 'secret'; $key = $this->jwt->getSignerKey(\sizeg\jwt\JwtKey::PLAIN_TEXT, $contents, $passphrase); $token = Yii::$app->jwt->getBuilder() // Configures the issuer (iss claim) ->issuedBy('http://example.com') // Configures the audience (aud claim) ->permittedFor('http://example.org') // Configures the id (jti claim) ->identifiedBy('4f1g23a12aa') // Configures the time that the token was issue (iat claim) ->issuedAt($now) // Configures the time that the token can be used (nbf claim) ->canOnlyBeUsedAfter($now->modify('+1 minute')) // Configures the expiration time of the token (exp claim) ->expiresAt($now->modify('+1 hour')) // Configures a new claim, called "uid" ->withClaim('uid', 1) // Configures a new header, called "foo" ->withHeader('foo', 'bar') // Builds a new token ->getToken($algorithm, $key); if (!Yii::$app->jwt->validate($token, new SignedWith( Yii::$app->jwt->getSigner(\sizeg\jwt\JwtSigner::HS256), Yii::$app->jwt->getSignerKey(JwtKey::PLAIN_TEXT, $contents, $passphrase) ))) { echo 'Invalid token (1)!', PHP_EOL; // will not print this } if (!Yii::$app->jwt->validate($token, new SignedWith( Yii::$app->jwt->getSigner(\sizeg\jwt\JwtSigner::HS256), Yii::$app->jwt->getSignerKey(JwtKey::PLAIN_TEXT, random_bytes(32), 'other-secret') ))) { echo 'Invalid token (1)!', PHP_EOL; // will print this }
RSA和ECDSA
RSA和ECDSA签名基于公钥和私钥,因此您必须使用私钥生成,并使用公钥验证
use \Lcobucci\JWT\Validation\Constraint\SignedWith; $now = new DateTimeImmutable(); // you can use 'ES256' if you're using ECDSA keys $algorithm = Yii::$app->jwt->getSigner(\sizeg\jwt\JwtSigner::RS256); $privateKey = Yii::$app->jwt->getSignerKey(\sizeg\jwt\JwtKey::FILE, 'file://{path to your private key}'); $token = Yii::$app->jwt->getBuilder() // Configures the issuer (iss claim) ->issuedBy('http://example.com') // Configures the audience (aud claim) ->permittedFor('http://example.org') // Configures the id (jti claim) ->identifiedBy('4f1g23a12aa') // Configures the time that the token was issue (iat claim) ->issuedAt($now) // Configures the time that the token can be used (nbf claim) ->canOnlyBeUsedAfter($now->modify('+1 minute')) // Configures the expiration time of the token (exp claim) ->expiresAt($now->modify('+1 hour')) // Configures a new claim, called "uid" ->withClaim('uid', 1) // Configures a new header, called "foo" ->withHeader('foo', 'bar') // Builds a new token ->getToken($algorithm, $privateKey); $publicKey = Yii::$app->jwt->getSignerKey(\sizeg\jwt\JwtKey::FILE, 'file://{path to your public key}'); var_dump(Yii::$app->jwt->validate($token, new SignedWith( Yii::$app->jwt->getSigner(\sizeg\jwt\JwtSigner::RS256), Yii::$app->jwt->getSignerKey(JwtKey::FILE, $publicKey) ))); // true when the public key was generated by the private one =)
重要的是要指出,如果您正在使用RSA密钥,则不应调用ECDSA签名者(反之亦然),否则sign()
和verify()
将引发异常!
Yii2基本模板示例
基本方案
- 客户端发送凭证。例如,登录 + 密码
- 后端验证它们
- 如果凭证有效,客户端接收令牌
- 客户端存储令牌以供将来请求使用
逐步使用示例
-
创建 Yii2 应用程序
在此示例中,我们将使用基本模板,但您也可以以相同的方式使用高级模板。
composer create-project --prefer-dist --stability=dev yiisoft/yii2-app-basic yii2-jwt-test
-
安装组件
composer require sizeg/yii2-jwt
-
将组件添加到 config/web.php 文件的
components
部分$config = [ 'components' => [ // other default components here.. 'jwt' => [ 'class' => \sizeg\jwt\Jwt::class, 'constraints' => [ function () { return new \Lcobucci\JWT\Validation\Constraint\LooseValidAt( \Lcobucci\Clock\SystemClock::fromSystemTimezone() ); }, ], ], ], ];
-
更改方法
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->claims()->get('uid')) { return new static($user); } } return null; }
-
创建控制器
<?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() { $now = new DateTimeImmutable(); $algorithm = $this->jwt->getSigner(); $key = $this->jwt->getSignerKey(); /** @var Jwt $jwt */ $jwt = Yii::$app->jwt; $token = Yii::$app->jwt->getBuilder() // Configures the issuer (iss claim) ->issuedBy('http://example.com') // Configures the audience (aud claim) ->permittedFor('http://example.org') // Configures the id (jti claim) ->identifiedBy('4f1g23a12aa') // Configures the time that the token was issue (iat claim) ->issuedAt($now) // Configures the expiration time of the token (exp claim) ->expiresAt($now->modify('+1 hour')) // Configures a new claim, called "uid" ->withClaim('uid', 100) // Builds a new token ->getToken($algorithm, $key); return $this->asJson([ 'token' => $token->toString(), ]); } /** * @return \yii\web\Response */ public function actionData() { return $this->asJson([ 'success' => true, ]); } }
-
发送简单的登录请求以获取令牌。在此示例中,我们没有发送任何凭证以简化示例。如我们在
authenticator
行为动作中指定的,authenticator
跳过对该动作的认证检查。