参数说明
iss 【issuer】签发人(可以是,发布者的url地址)
sub 【subject】该JWT所面向的用户,用于处理特定应用,不是常用的字段
aud 【audience】受众人(可以是客户端的url地址,用作验证是否是指定的人或者url)
exp 【expiration】 该jwt销毁的时间;unix时间戳
nbf 【not before】 该jwt的使用时间不能早于该时间;unix时间戳
iat 【issued at】 该jwt的发布时间;unix 时间戳
jti 【JWT ID】 该jwt的唯一ID编号
下载
composer require lcobucci/jwt 3.*
3.3
<?php
namespace tools\jwt;
use Lcobucci\JWT\Builder;
use Lcobucci\JWT\Parser;
use Lcobucci\JWT\Signer\Hmac\Sha256;
use Lcobucci\JWT\ValidationData;
/**
* Created by PhpStorm.
* User: asus
* Date: 2019/4/5
* Time: 13:02
*/
class Token
{
private static $_config = [
'audience' => 'http://www.pyg.com',//接收人
'id' => '3f2g57a92aa',//token的唯一标识,这里只是一个简单示例
'sign' => 'pinyougou',//签名密钥
'issuer' => 'http://adminapi.pyg.com',//签发人
'expire' => 3600 * 24 //有效期
];
//生成token
public static function getToken($user_id)
{
//签名对象
$signer = new Sha256();
//获取当前时间戳
$time = time();
//设置签发人、接收人、唯一标识、签发时间、立即生效、过期时间、用户id、签名
$token = (new Builder())->issuedBy(self::$_config['issuer'])
->canOnlyBeUsedBy(self::$_config['audience'])
->identifiedBy(self::$_config['id'], true)
->issuedAt($time)
->canOnlyBeUsedAfter($time - 1)
->expiresAt($time + self::$_config['expire'])
->with('user_id', $user_id)
->sign($signer, self::$_config['sign'])
->getToken();
return (string)$token;
}
//从请求信息中获取token令牌
private static function getRequestToken()
{
if (empty($_SERVER['HTTP_AUTHORIZATION'])) {
return false;
}
$header = $_SERVER['HTTP_AUTHORIZATION'];
$method = 'bearer';
//去除token中可能存在的bearer标识
return trim(str_ireplace($method, '', $header));
}
//从token中获取用户id (包含token的校验)
public static function getUserId($token = null)
{
$user_id = null;
$token = empty($token) ? self::getRequestToken() : $token;
if (!empty($token)) {
//为了注销token 加以下if判断代码
$delete_token = cache('delete_token') ?: [];
if (in_array($token, $delete_token)) {
//token已被删除(注销)
return $user_id;
}
$token = (new Parser())->parse((string)$token);
//验证token
$data = new ValidationData();
$data->setIssuer(self::$_config['issuer']);//验证的签发人
$data->setAudience(self::$_config['audience']);//验证的接收人
$data->setId(self::$_config['id']);//验证token标识
if (!$token->validate($data)) {
//token验证失败
return $user_id;
}
//验证签名
$signer = new Sha256();
if (!$token->verify($signer, self::$_config['sign'])) {
//签名验证失败
return $user_id;
}
//从token中获取用户id
$user_id = $token->getClaim('user_id');
}
return $user_id;
}
}
3.4 所有异常放一起验证
//-------------------
//验证接收人
$audience = new PermittedFor(self::$_config['audience']);
//验证是否过期
$timezone = new DateTimeZone('Asia/Shanghai');
$now = new SystemClock($timezone);
$valid_at = new ValidAt($now);
//把验证结果都放进去
$config->setValidationConstraints($issued, $audience, $valid_at);
//错误捕获
try {
$config->validator()->assert($token, ...$config->validationConstraints());
} catch (RequiredConstraintsViolated $e) {
if($e instanceof ValidAt){
echo "过期?";
}
dump($e->violations()[0]);
die();
//echo "捕获到异常了";
//echo "这里是捕获到的错误";
// list of constraints violation exceptions:
//dump($e->violations());
//
//$res = $e->violations();
//
//var_dump($res[0]->getFile());
//dump($e);
//dump($e->violations());
}
3.4 异常单独捕获然后通过json格式返回
<?php
namespace tools\jwt;
use DateTimeImmutable;
use DateTimeZone;
use InvalidArgumentException;
use Lcobucci\Clock\SystemClock;
use Lcobucci\JWT\Configuration;
use Lcobucci\JWT\Signer\Hmac\Sha256;
use Lcobucci\JWT\Signer\Key\InMemory;
use Lcobucci\JWT\Validation\Constraint\IssuedBy;
use Lcobucci\JWT\Validation\Constraint\PermittedFor;
use Lcobucci\JWT\Validation\Constraint\ValidAt;
use Lcobucci\JWT\Validation\RequiredConstraintsViolated;
class Token
{
//配置属性
private static $_config = [
'issuer' => 'http://adminapi.pyg.com',//签发人
'audience' => 'http://www.pyg.com',//接收人
'id' => '3f2g57a92aa',//token的唯一标识,这里只是一个简单示例
'sign' => 'pinyougou',//签名密钥
'expire' => 3600 * 24 //有效期 86400
];
//生成配置对象
private static function getConfig()
{
return Configuration::forSymmetricSigner(
new Sha256(),
InMemory::plainText(self::$_config['id'])
);
}
//生成token
public static function getToken($user_id)
{
$config = self::getConfig();
$now = new DateTimeImmutable();
return $config->builder()
->issuedBy(self::$_config['issuer'])
//接收人 // canOnlyBeUsedBy方法在4.x中将会被移除被permittedFor替代
->permittedFor(self::$_config['audience'])
//唯一标志
->identifiedBy(self::$_config['id'])
//签发时间
->issuedAt($now)
//生效时间(立即生效:签发时间前一秒)
->canOnlyBeUsedAfter($now->modify('-1 second'))
//过期时间
->expiresAt($now->modify('+20 second'))
//用户id // with方法在4.x中将会被移除被withClaim替代
->withClaim('user_id', $user_id)
//签名
->getToken($config->signer(), $config->signingKey())->toString();
}
//从请求信息中获取token令牌
public static function getRequestToken()
{
//取不到那就是没传递,返回false
if (empty($_SERVER['HTTP_AUTHORIZATION'])) {
return false;
}
$header = $_SERVER['HTTP_AUTHORIZATION'];
$method = 'bearer';
//去除token中可能存在的bearer标识,这是一种兼容做法,因为前端传递的规范是可以有那个bearer的
return trim(str_ireplace($method, '', $header));
}
//从token中获取用户id (包含token的校验)
public static function getUserId($token = null)
{
$config = self::getConfig();
$token = empty($token) ? self::getRequestToken() : $token;
if (!empty($token)) {
//注销token逻辑
$delete_token = cache('delete_token') ?: [];
if (in_array($token, $delete_token)) {
//token已被删除(注销)
return null;
}
//token解析异常必须要用try catch抓取
try {
//解析token
$token = $config->parser()->parse((string)$token);
} catch (\Exception $e) {
// list of constraints violation exceptions:
fail('token解析错误');
}
//验证签发人
$issued = new IssuedBy(self::$_config['issuer']);
if(!$config->validator()->validate($token,$issued)){
fail('签发异常');
}
//验证接收
$audience = new PermittedFor(self::$_config['audience']);
if(!$config->validator()->validate($token,$audience)){
fail('接收异常');
}
//验证是否过期
$timezone = new DateTimeZone('Asia/Shanghai');
$now = new SystemClock($timezone);
$valid_at = new ValidAt($now);
if(!$config->validator()->validate($token,$valid_at)){
fail('token过期');
}
//从token中取出user_id
return $token->claims()->get('user_id');
}
return null;
}
}
网上网友的版本(用于参考)
<?php
/**
* jwt封装的一个简单的类
*/
use Lcobucci\JWT\Configuration;
use Lcobucci\JWT\Signer\Hmac\Sha256;
use Lcobucci\JWT\Signer\Key\InMemory;
use DateTimeImmutable;
use Lcobucci\JWT\Token\Plain;
use Lcobucci\JWT\Validation\RequiredConstraintsViolated;
use Lcobucci\JWT\Validation\Constraint\SignedWith;
class Service
{
/**
* 配置秘钥加密
* @return Configuration
*/
public static function getConfig()
{
$configuration = Configuration::forSymmetricSigner(
// You may use any HMAC variations (256, 384, and 512)
new Sha256(),
// replace the value below with a key of your own!
InMemory::base64Encoded('YWFhc0pOU0RLSkJITktKU0RiamhrMTJiM2Joa2ox')
// You may also override the JOSE encoder/decoder if needed by providing extra arguments here
);
return $configuration;
}
/**
* 签发令牌
*/
public static function createToken()
{
$config = self::getConfig();
assert($config instanceof Configuration);
$now = new DateTimeImmutable();
$token = $config->builder()
// 签发人
->issuedBy('http://example.com')
// 受众
->permittedFor('http://example.org')
// JWT ID 编号 唯一标识
->identifiedBy('123')
// 签发时间
->issuedAt($now)
// 在1分钟后才可使用
// ->canOnlyBeUsedAfter($now->modify('+1 minute'))
// 过期时间1小时
->expiresAt($now->modify('+1 hour'))
// 自定义uid 额外参数
->withClaim('uid', 1)
// 自定义header 参数
->withHeader('foo', 'bar')
// 生成token
->getToken($config->signer(), $config->signingKey());
//result:
//eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiIsImZvbyI6ImJhciJ9.eyJpc3MiOiJodHRwOlwvXC9leGFtcGxlLmNvbSIsImF1ZCI6Imh0dHA6XC9cL2V4YW1wbGUub3JnIiwianRpIjoiNGYxZzIzYTEyYWEiLCJpYXQiOjE2MDk0Mjk3MjMsIm5iZiI6MTYwOTQyOTc4MywiZXhwIjoxNjA5NDMzMzIzLCJ1aWQiOjF9.o4uLWzZjk-GJgrxgirypHhXKkMMUEeL7z7rmvmW9Mnw
//base64 decode:
//{"typ":"JWT","alg":"HS256","foo":"bar"}{"iss":"http:\/\/example.com","aud":"http:\/\/example.org","jti":"4f1g23a12aa","iat":1609429723,"nbf":1609429783,"exp":1609433323,"uid":1}[6cb`"*Gr0ńxoL
return $token->toString();
}
/**
* 解析令牌
*/
public static function parseToken(string $token)
{
$config = self::getConfig();
assert($config instanceof Configuration);
$token = $config->parser()->parse('eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiIsImZvbyI6ImJhciJ9.eyJpc3MiOiJodHRwOlwvXC9leGFtcGxlLmNvbSIsImF1ZCI6Imh0dHA6XC9cL2V4YW1wbGUub3JnIiwianRpIjoiNGYxZzIzYTEyYWEiLCJpYXQiOjE2MDk0Mjk3MjMsIm5iZiI6MTYwOTQyOTc4MywiZXhwIjoxNjA5NDMzMzIzLCJ1aWQiOjF9.o4uLWzZjk-GJgrxgirypHhXKkMMUEeL7z7rmvmW9Mnw'
);
assert($token instanceof Plain);
dump($token->headers()); // Retrieves the token headers
dump($token->claims()); // Retrieves the token claims
}
/**
* 验证令牌
*/
public static function validationToken(string $token)
{
$config = self::getConfig();
assert($config instanceof Configuration);
$token = $config->parser()->parse($token);
assert($token instanceof Plain);
//Lcobucci\JWT\Validation\Constraint\IdentifiedBy: 验证jwt id是否匹配
//Lcobucci\JWT\Validation\Constraint\IssuedBy: 验证签发人参数是否匹配
//Lcobucci\JWT\Validation\Constraint\PermittedFor: 验证受众人参数是否匹配
//Lcobucci\JWT\Validation\Constraint\RelatedTo: 验证自定义cliam参数是否匹配
//Lcobucci\JWT\Validation\Constraint\SignedWith: 验证令牌是否已使用预期的签名者和密钥签名
//Lcobucci\JWT\Validation\Constraint\ValidAt: 验证要求iat,nbf和exp(支持余地配置)
//验证jwt id是否匹配
$validate_jwt_id = new \Lcobucci\JWT\Validation\Constraint\IdentifiedBy('123');
$config->setValidationConstraints($validate_jwt_id);
//验证签发人url是否正确
$validate_issued = new \Lcobucci\JWT\Validation\Constraint\IssuedBy('http://example.com');
$config->setValidationConstraints($validate_issued);
//验证客户端url是否匹配
$validate_aud = new \Lcobucci\JWT\Validation\Constraint\PermittedFor('http://example.org');
$config->setValidationConstraints($validate_aud);
//验证是否过期
$timezone = new \DateTimeZone('Asia/Shanghai');
$now = new \Lcobucci\Clock\SystemClock($timezone);
$validate_jwt_at = new \Lcobucci\JWT\Validation\Constraint\ValidAt($now);
$config->setValidationConstraints($validate_jwt_at);
$constraints = $config->validationConstraints();
try {
$config->validator()->assert($token, ...$constraints);
} catch (RequiredConstraintsViolated $e) {
// list of constraints violation exceptions:
var_dump($e->violations());
}
}
}