Yii2 JWT
这个扩展为Yii framework 2.0提供了JWT集成(需要PHP 5.6+)。它包括基本的HTTP身份验证支持。
目录
安装
Package is available on Packagist,
you can install it using Composer.
composer require sizeg/yii2-jwt
依赖关系
- PHP 5.6+
- OpenSSL Extension
- lcobucci/jwt 3.3
基本用法
将 jwt
组件添加到配置文件中,
'components' => [
'jwt' => [
'class' => sizegjwtJwt::class,
'key' => 'secret',
],
],
按如下方式配置 authenticator
行为。
namespace appcontrollers;
class ExampleController extends yii
estController
{
/**
* @inheritdoc
*/
public function behaviors()
{
$behaviors = parent::behaviors();
$behaviors['authenticator'] = [
'class' => sizegjwtJwtHttpBearerAuth::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' => sizegjwtJwt:class,
'key' => 'secret',
'jwtValidationData' => [
'class' => sizegjwtJwtValidationData::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
,nbf
和iat
声明默认在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基本模板示例
基本方案
- 客户端发送凭据。例如,登录+密码
- 后端验证它们
- 如果凭据是有效的客户端接收令牌
- 未来请求的客户端存储令牌
分步使用示例
-
创建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' => sizegjwtJwt::class, 'key' => 'secret', // You have to configure ValidationData informing all claims you want to validate the token. 'jwtValidationData' => appcomponentsJwtValidationData::class, ], ], ];
-
创建JwtValidationData类。在这里,您必须配置ValidationData来通知您要验证令牌的所有声明。
<?php namespace appcomponents; class JwtValidationData extends sizegjwtJwtValidationData { /** * @inheritdoc */ public function init() { $this->validationData->setIssuer('http://example.com'); $this->validationData->setAudience('http://example.org'); $this->validationData->setId('4f1g23a12aa'); parent::init(); } }
-
修改方法
appmodelsUser::findIdentityByAccessToken()
/** * {@inheritdoc} * @param LcobucciJWTToken $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; }
-
新建控制器
<?php namespace appcontrollers; use sizegjwtJwt; use sizegjwtJwtHttpBearerAuth; use Yii; use yii estController; class RestController extends Controller { /** * @inheritdoc */ public function behaviors() { $behaviors = parent::behaviors(); $behaviors['authenticator'] = [ 'class' => JwtHttpBearerAuth::class, 'optional' => [ 'login', ], ]; return $behaviors; } /** * @return yiiwebResponse */ 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 yiiwebResponse */ public function actionData() { return $this->asJson([ 'success' => true, ]); } }
-
发送简单的登录请求以获取令牌。这里我们不发送任何凭证来简化示例。正如我们在
authenticator
行为操作login
中将authenticator
跳过该操作的身份验证检查指定为可选。
-
首先,我们尝试向rest/data发送请求,但不带令牌,并且获取错误
Unauthorized
-
然后我们重试请求,但已经用令牌添加了
Authorization