PHP code example of kode / jwt
1. Go to this page and download the library: Download kode/jwt library . Choose the download type require .
2. Extract the ZIP file and open the index.php.
3. Add this code to the index.php.
<?php
require_once('vendor/autoload.php');
/* Start to develop here. Best regards https://php-download.com/ */
kode / jwt example snippets
declare(strict_types=1);
/**
* JWT 配置文件
* 由 kode/jwt CLI 工具生成
*
* @generated_at 2025-12-30 09:14:47
*/
return [
/**
* 默认配置
*/
'defaults' => [
'guard' => 'api', // 默认守卫名称
'provider' => 'users', // 默认用户提供者
'platform' => 'web', // 默认平台
],
/**
* 守卫配置
* 每个守卫对应一种认证策略
*/
'guards' => [
'api' => [
'driver' => 'kode', // 驱动类型(固定为 kode)
'provider' => 'users', // 用户提供者
'storage' => 'redis', // 存储驱动:redis, memory, null
'blacklist_enabled' => true, // 是否启用黑名单
'refresh_enabled' => true, // 是否支持自动续期
'refresh_ttl' => 20160, // 续期窗口(分钟,默认2周)
'ttl' => 1440, // Token 有效期(分钟,默认24小时)
'algo' => 'RS256', // 加密算法:RS256, HS256
'secret' => null, // HMAC 密钥(RS256 可为 null)
'public_key' => null, // RSA 公钥路径或内容
'private_key' => null, // RSA 私钥路径或内容
],
],
/**
* 平台配置
* 用于多平台 Token 隔离
*/
'platforms' => [
'web' => [
'enabled' => true,
'guard' => 'api',
'ttl' => 1440,
],
'h5' => [
'enabled' => true,
'guard' => 'api',
'ttl' => 1440,
],
'pc' => [
'enabled' => true,
'guard' => 'api',
'ttl' => 1440,
],
'app' => [
'enabled' => true,
'guard' => 'api',
'ttl' => 1440,
],
'wx_mini' => [
'enabled' => true,
'guard' => 'api',
'ttl' => 1440,
],
'ali_mini' => [
'enabled' => true,
'guard' => 'api',
'ttl' => 1440,
],
'tt_mini' => [
'enabled' => true,
'guard' => 'api',
'ttl' => 1440,
],
],
/**
* SSO 配置
* 单点登录:同一用户在同一平台仅允许一个有效 Token
*/
'sso' => [
'enabled' => false, // 是否启用 SSO
'scope' => 'platform', // 隔离范围:platform(平台级), guard(守卫级)
],
/**
* MLO 配置
* 多点登录:支持同一用户多个设备同时在线
*/
'mlo' => [
'enabled' => false, // 是否启用 MLO
'max_devices' => 5, // 最大设备数
'kick_old' => false, // 是否踢掉旧设备
],
/**
* 存储配置
*/
'storage' => [
'redis' => [
'connection' => 'default', // Redis 连接名称
'prefix' => 'kode:jwt:', // Key 前缀
],
'memory' => [
'limit' => 10000, // 最大缓存数量
],
],
/**
* 事件配置
*/
'events' => [
'enabled' => true,
'listeners' => [
// \App\Listeners\OnTokenIssued::class,
// \App\Listeners\OnTokenRevoked::class,
],
],
];
namespace Kode\Jwt\Token;
use Kode\Jwt\Contract\Arrayable;
final readonly class Payload implements Arrayable
{
public function __construct(
public int|string|null $uid = null,
public ?string $username = null,
public string $platform,
public int $exp,
public int $iat,
public string $jti,
public ?array $roles = null,
public ?array $perms = null,
public array $custom = []
) {}
public function toArray(): array
{
return get_object_vars($this);
}
/**
* 从数组创建Payload实例
*
* @param array $data 包含Payload数据的数组
* @return static
* @throws \InvalidArgumentException 当必需字段缺失时抛出异常
*/
public static function fromArray(array $data): static
{
// 验证必需字段
$@param string $platform 平台标识
* @param int $exp 过期时间戳
* @param int $iat 签发时间戳
* @param string $jti JWT ID
* @param array|null $roles 用户角色列表
* @param array|null $perms 用户权限列表
* @param array|string|null $customData 自定义数据,可以是数组或加密字符串
* @return static
*/
public static function create(
int $uid,
string $username,
string $platform,
int $exp,
int $iat,
string $jti,
?array $roles = null,
?array $perms = null,
array|string|null $customData = null
): static {
$custom = [];
// 处理自定义数据
if (is_string($customData)) {
// 如果是字符串,将其存储为加密数据
$custom['encrypted_data'] = $customData;
} elseif (is_array($customData)) {
// 如果是数组,直接合并到custom字段
$custom = $customData;
}
return new static(
$uid,
$username,
$platform,
$exp,
$iat,
$jti,
$roles,
$perms,
$custom
);
}
}
// 1. 使用数组自定义数据
$payload = Payload::create(
uid: 456,
username: 'jane_doe',
platform: 'web',
exp: time() + 3600,
iat: time(),
jti: uniqid('jwt_'),
roles: ['user', 'editor'],
perms: ['read', 'write'],
customData: [
'department' => 'Marketing',
'level' => 3,
'preferences' => [
'theme' => 'dark',
'language' => 'zh-CN'
]
]
);
// 2. 使用加密字符串自定义数据
$encryptedData = base64_encode(json_encode([
'sensitive_info' => 'secret_data',
'timestamp' => time()
]));
$payload = Payload::create(
uid: 789,
username: 'bob_smith',
platform: 'mobile',
exp: time() + 3600,
iat: time(),
jti: uniqid('jwt_'),
roles: ['user'],
perms: ['read'],
customData: $encryptedData
);
// 从数组创建Payload(包含必需字段验证)
$data = [
'uid' => 123,
'username' => 'john_doe',
'platform' => 'app',
'exp' => time() + 3600,
'iat' => time(),
'jti' => uniqid('jwt_'),
'roles' => ['user'],
'perms' => ['read', 'write'],
'custom' => [
'department' => 'IT',
'location' => 'Beijing'
]
];
$payload = Payload::fromArray($data);
// 获取所有自定义数据
$customData = $payload->getCustomData();
// 获取特定自定义数据
$department = $payload->getCustom('department', 'Unknown');
// 检查是否存在特定自定义数据
if ($payload->hasCustom('department')) {
echo "Department: " . $payload->getCustom('department');
}
// 获取加密的自定义数据
$encryptedData = $payload->getEncryptedData();
// 检查是否存在加密的自定义数据
if ($payload->hasEncryptedData()) {
$data = json_decode(base64_decode($encryptedData), true);
// 处理解密后的数据
}
// 检查用户是否具有指定角色(使用严格比较)
if ($payload->hasRole('admin')) {
// 用户具有管理员角色
}
// 检查用户是否具有指定权限(使用严格比较)
if ($payload->hasPermission('delete')) {
// 用户具有删除权限
}
// 获取用户信息
$userInfo = $payload->getUserInfo();
// 检查Token是否已过期
if ($payload->isExpired()) {
// Token已过期
}
// 获取剩余有效时间
$ttl = $payload->getTtl();
// 获取用户标识
$userIdentifier = $payload->getUserIdentifier();
namespace Kode\Jwt\Guard;
use Kode\Jwt\Contract\GuardInterface;
use Kode\Jwt\Storage\StorageInterface;
class SsoGuard implements GuardInterface
{
public function __construct(
private StorageInterface $storage
) {}
public function isUnique(string $uid, string $platform): bool
{
$key = "sso:{$uid}:{$platform}";
$existing = $this->storage->get($key);
if ($existing) {
// 可选:自动踢出旧 Token
$this->storage->blacklist($existing);
$this->storage->delete($key);
}
return true;
}
public function register(string $uid, string $platform, string $jti): void
{
$this->storage->set(
"sso:{$uid}:{$platform}",
$jti,
config('jwts.guards.api.ttl')
);
}
}
namespace Kode\Jwt\Storage;
use Swoole\Coroutine\Redis as CoRedis;
class RedisStorage implements StorageInterface
{
private ?CoRedis $redis = null;
public function __construct()
{
$this->connect();
}
private function connect(): void
{
$config = config('jwts.storage.redis');
$this->redis = new CoRedis();
$this->redis->connect('127.0.0.1', 6379);
$this->redis->auth($config['password'] ?? '');
$this->redis->select($config['db'] ?? 0);
}
public function blacklist(string $jti, int $ttl = 3600): bool
{
return (bool)$this->redis->setex(
"blacklist:{$jti}",
$ttl,
'1'
);
}
public function isBlacklisted(string $jti): bool
{
return (bool)$this->redis->exists("blacklist:{$jti}");
}
}
use Kode\Jwt\KodeJwt;
$payload = new Payload(
uid: 123,
username: 'john_doe',
platform: 'app',
exp: now()->addMinutes(1440)->getTimestamp(),
iat: now()->getTimestamp(),
jti: uniqid('jwt_'),
roles: ['user'],
perms: ['read', 'write']
);
$token = KodeJwt::guard('api')->issue($payload);
// 返回: ['token' => 'eyJ...', 'expires_in' => 1440, 'refresh_ttl' => 20160]
try {
$payload = KodeJwt::guard('api')->authenticate($token);
echo $payload->username; // john_doe
} catch (TokenInvalidException $e) {
// 处理异常
}
$newToken = KodeJwt::guard('api')->refresh($oldToken);
KodeJwt::guard('api')->invalidate($token);
// 使用Builder的便捷方法
$token = KodeJwt::builder()
->setUid(123)
->setUsername('john_doe')
->setPlatform('app')
->setRoles(['user'])
->setPermissions(['read', 'write'])
->setCustom(['department' => 'IT'])
->issue();
// 获取用户的所有活跃Token
$tokens = KodeJwt::getUserTokens('123', 'app');
// 强制注销用户的所有Token
$count = KodeJwt::revokeUserTokens('123', 'app');
// 检查Token是否有效
$isValid = KodeJwt::isTokenValid($token);
// 获取Token详细信息
$info = KodeJwt::getTokenInfo($token);
// 返回: ['uid' => 123, 'platform' => 'app', 'exp' => 1234567890, ...]
// 清理过期的Token
$cleanedCount = KodeJwt::cleanExpired();
// 获取存储统计信息
$stats = KodeJwt::getStats();
// 返回: ['total' => 100, 'expired' => 20, 'active' => 80]
// 使用增强的Payload创建方法
// 1. 使用数组自定义数据
$payload = Payload::create(
uid: 456,
username: 'jane_doe',
platform: 'web',
exp: time() + 86400,
iat: time(),
jti: uniqid('jwt_'),
roles: ['user'],
perms: ['read', 'write'],
customData: [
'department' => 'Marketing',
'level' => 3,
'preferences' => [
'theme' => 'dark',
'language' => 'zh-CN'
]
]
);
// 2. 使用加密字符串自定义数据
$encryptedData = base64_encode(json_encode([
'sensitive_info' => 'secret_data',
'timestamp' => time()
]));
$payload = Payload::create(
uid: 789,
username: 'bob_smith',
platform: 'mobile',
exp: time() + 86400,
iat: time(),
jti: uniqid('jwt_'),
customData: $encryptedData
);
Kode\Jwt\KodeJwt;
use Kode\Jwt\Token\Payload;
// 1. 初始化(使用内存存储,演示用)
KodeJwt::init([
'guards' => [
'api' => [
'driver' => 'sso',
'storage' => 'memory',
'secret' => 'your-256-bit-secret-key',
],
],
]);
// 2. 签发 Token
$now = time();
$payload = Payload::create(
uid: 1001,
username: 'alice',
platform: 'web',
exp: $now + 3600,
iat: $now,
jti: Payload::generateJti(), // 高熵 JTI
);
$token = KodeJwt::issue($payload)['token'];
// 3. 验证 Token
$verified = KodeJwt::authenticate($token);
echo $verified->username; // alice
KodeJwt::init([
'guards' => [
'api' => [
'driver' => 'sso',
'storage' => 'redis',
'algo' => 'RS256',
'secret' => 'your-rs256-secret',
'expected_claims' => [
'iss' => 'https://auth.example.com',
'aud' => ['api.example.com', 'mobile'],
],
'clock_skew' => 30,
],
],
'storage' => [
'redis' => [
'host' => '127.0.0.1',
'port' => 6379,
'password' => getenv('REDIS_PASSWORD'),
'prefix' => 'kode:jwt:',
],
],
'replay' => [
'mode' => 'strict', // strict / lenient / off
'
use Kode\Jwt\Security\AntiReplay;
$payload = Payload::create(
uid: 1001,
username: 'alice',
platform: 'web',
exp: time() + 3600,
iat: time(),
jti: Payload::generateJti(),
audience: ['api.example.com'],
issuer: 'https://auth.example.com',
subject: 'auth-service',
nonce: AntiReplay::generateNonce(16), // 32 字节一次性 Nonce
roles: ['user', 'admin'],
perms: ['read', 'write'],
customData: [
'tenant_id' => 't_42',
'department' => 'IT',
],
);
$result = KodeJwt::issue($payload);
// ['token' => 'eyJ...', 'expires_in' => 3600, 'refresh_ttl' => 604800]
use Kode\Jwt\Exception\TokenInvalidException;
use Kode\Jwt\Exception\TokenBlacklistedException;
use Kode\Jwt\Exception\TokenReplayException;
use Kode\Jwt\Exception\TokenExpiredException;
try {
$payload = KodeJwt::authenticate($token);
} catch (TokenReplayException $e) {
// 重放攻击:记录安全日志、触发风控告警
return response()->json(['error' => '请求被拒绝'], 401);
} catch (TokenBlacklistedException $e) {
// 已注销:引导重新登录
return response()->json(['error' => '会话已过期'], 401);
} catch (TokenExpiredException $e) {
// 已过期:尝试 refresh
return response()->json(['error' => '需要刷新'], 401);
} catch (TokenInvalidException $e) {
// 签名错误 / 算法不匹配 / 业务声明不匹配
return response()->json(['error' => '无效 Token'], 401);
}
use Kode\Jwt\KodeJwt;
use Kode\Jwt\Key\JwkFactory;
// 1. 生成 RSA 密钥对(私钥用于签发,公钥用于发布)
$keyPair = JwkFactory::generateRsaKeyPair(2048, 'kid-2026-01');
// 2. 创建 JWKS 发布器(公钥集合自动剥离私钥参数)
$jwksSet = $keyPair['public']->toJwkSet(); // 假设已包装为 JwkSet
$publisher = KodeJwt::jwksPublisher($jwksSet, maxAge: 3600);
// 3. 处理 HTTP 请求(传入 If-None-Match 头)
$response = $publisher->handle([
'If-None-Match' => $_SERVER['HTTP_IF_NONE_MATCH'] ?? '',
]);
// 4. 输出响应(适配你框架的 Response 对象)
http_response_code($response->status);
foreach ($response->headers as $name => $value) {
header("{$name}: {$value}");
}
echo $response->body;
// 命中协商缓存时返回 304 Not Modified,body 为空
use Kode\Jwt\KodeJwt;
// 1. 创建 Introspector(使用默认守卫)
$introspector = KodeJwt::introspector();
// 2. 内省 Token(自动完成验签 + 黑名单检查)
$response = $introspector->introspect(
token: $bearerToken,
expectedPlatform: 'web',
clientId: 'client-app-001',
);
// 3. 输出 RFC 7662 标准响应
header('Content-Type: application/json');
echo $response->toJson();
// 有效 Token:{"active":true,"scope":"openid profile","client_id":"client-app-001","username":"alice","token_type":"Bearer","exp":1800000000,"iat":1700000000,"sub":"user-123","aud":"web","iss":"kode","jti":"..."}
// 无效 Token:{"active":false}
use Kode\Jwt\KodeJwt;
// 1. 创建 Discovery 配置
$config = KodeJwt::discoveryConfiguration(
issuer: 'https://auth.example.com',
authorizationEndpoint: 'https://auth.example.com/authorize',
tokenEndpoint: 'https://auth.example.com/token',
jwksUri: 'https://auth.example.com/.well-known/jwks',
userinfoEndpoint: 'https://auth.example.com/userinfo',
introspectionEndpoint: 'https://auth.example.com/introspect',
revocationEndpoint: 'https://auth.example.com/revoke',
);
// 2. 创建发布器并处理请求
$publisher = KodeJwt::discoveryPublisher($config, maxAge: 86400);
$response = $publisher->handle([
'If-None-Match' => $_SERVER['HTTP_IF_NONE_MATCH'] ?? '',
]);
http_response_code($response->status);
foreach ($response->headers as $name => $value) {
header("{$name}: {$value}");
}
echo $response->body;
use Kode\Jwt\KodeJwt;
// 1. 从 Token 中的 scope 字符串构造
$scope = KodeJwt::scope('openid profile email');
// 2. 集合运算
$scope->has('openid'); // true
$scope->hasAny(['profile', 'phone']); // true
$scope->hasAll(['openid', 'phone']); // false
$scope->intersect(['openid', 'email'])->toArray(); // ['openid', 'email']
$scope->merge(['offline_access'])->toString(); // "openid profile email offline_access"
// 3. 校验
$scope->allAllowed(['openid', 'profile', 'email', 'address']); // true
$scope->allStandard(); // true(全部为 OIDC 标准 scope)
// 4. 嵌入 Token
$scope->__toString(); // "openid profile email"(可直接作为 Payload.scope)
use Kode\Jwt\KodeJwt;
use Kode\Jwt\Exception\TokenInvalidException;
$inspector = KodeJwt::claimInspector();
try {
$inspector
->assertIssuer($payload, 'https://auth.example.com')
->assertAudience($payload, 'web')
->assertSubject($payload, 'user-123')
->assertTimeWindow($payload, clockSkew: 30)
->assertScopesAll($payload, ['openid', 'profile'])
->assertPlatform($payload, 'web')
->assertCustomEquals($payload, 'tenant', 'acme');
// 全部通过
} catch (TokenInvalidException $e) {
// 校验失败,$e->jti 携带 Token JTI 便于排查
error_log("Token 校验失败:{$e->getMessage()} (jti={$e->jti})");
}
use Kode\Jwt\KodeJwt;
// 1. 链式构建策略
$policy = KodeJwt::tokenPolicy()
->withIssuer('https://auth.example.com')
->withAudience('web')
->withPlatform('web')
->withRequiredScopes(['openid', 'profile'])
->withAnyScopes(['read', 'write']) // 至少满足一个
->withRequiredCustom(['tenant' => 'acme'])
->withClockSkew(30)
->withIgnoreExpiration(false);
// 2. enforce:失败抛异常
try {
$policy->enforce($payload);
} catch (\Kode\Jwt\Exception\TokenInvalidException $e) {
// ...
}
// 3. satisfies:不抛异常的判定版本
if ($policy->satisfies($payload)) {
// 校验通过
}
// 4. 提取命中 scope
$allowedScope = $policy->extractAllowedScope($payload);
// 5. 序列化(用于配置缓存)
$array = $policy->toArray();
$policy2 = \Kode\Jwt\Policy\TokenPolicy::fromArray($array);
use Kode\Jwt\Key\JwkFactory;
// 生成 HS256 密钥(32 字节)
$jwk = JwkFactory::generateOctKey('HS256');
echo $jwk->toJson();
// {"kty":"oct","k":"...","use":"sig","alg":"HS256","kid":"oct-xxxx..."}
// 自动按算法选择密钥长度:HS256=32B / HS384=48B / HS512=64B
// 默认 2048 位(最低 2048,NIST 建议)
$pair = JwkFactory::generateRsaKeyPair(bits: 2048, alg: 'RS256');
$privateJwk = $pair['private']; // 含 d/p/q/dp/dq/qi,用于签发
$publicJwk = $pair['public']; // 仅含 n/e,可安全分发
// 公钥分发前可再调用 toPublic() 防御性剥离
$distributable = $privateJwk->toPublic();
use Kode\Jwt\Key\KeyConverter;
// PEM → JWK
$jwk = KeyConverter::rsaPublicKeyToJwk('/path/to/public.pem', kid: 'key-1', alg: 'RS256');
// JWK → PEM(仅 RSA 公钥)
$pem = KeyConverter::jwkToPem($jwk);
use Kode\Jwt\Key\JwkSet;
$set = JwkSet::create()
->with($jwk1)
->with($jwk2);
// 按 kid 选择
$selected = $set->get('key-1');
// 按算法筛选
$candidates = $set->findByAlgorithm('RS256');
// 安全分发(自动剥离所有私钥参数)
$publicSet = $set->toPublic();
use Kode\Jwt\Security\Fingerprint;
$fp = new Fingerprint();
// 计算指纹(基于 UA + IP 前缀)
$context = [
'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? '',
'ip' => $_SERVER['REMOTE_ADDR'] ?? '',
];
$fingerprintHash = $fp->compute($context);
// 签发时绑定(业务层将 $fingerprintHash 写入 Payload.custom.fingerprint)
$payload = Payload::create(
uid: 123,
platform: 'web',
exp: time() + 3600,
iat: time(),
jti: Payload::generateJti(),
customData: ['fingerprint' => $fingerprintHash],
);
// 验证时校验
$fp->ensureMatch($context, $payload->getCustom('fingerprint'));
// 不匹配时抛出 JwtException
// 单算法场景(默认)
'guards' => [
'api' => [
'algo' => 'RS256',
],
],
// 多算法并存场景(密钥轮换)
'guards' => [
'api' => [
'allowed_algorithms' => ['RS256', 'RS384'], // 显式白名单
// 'algo' 可不设置
],
],
// SQLite 方言
'INSERT OR REPLACE INTO ...'
// MySQL 方言
'INSERT ... ON DUPLICATE KEY UPDATE ...'
namespace Kode\Jwt\Contract;
interface SsoStorageInterface extends StorageInterface
{
/** 原子化撤销(黑名单 + SSO 清理 + 用户列表清理 + 详情清理) */
public function atomicRevoke(string $jti, string $uid, string $platform, int $ttl = 3600): int;
/** 记录到用户活跃 Token 列表(最多保留 50 条) */
public function trackUserToken(string $uid, string $platform, string $jti, int $ttl = 0): bool;
/** 设置 SSO 平台 → JTI 映射 */
public function setSsoMapping(string $uid, string $platform, string $jti, int $ttl = 0): bool;
/** 获取 SSO 平台 → JTI 映射 */
public function getSsoMapping(string $uid, string $platform): ?string;
}
use Kode\Jwt\Contract\SsoStorageInterface;
/** @var \Kode\Jwt\Contract\StorageInterface $storage */
// 通用调用:所有存储后端都支持
$storage->blacklist($jti, 3600);
// 高级能力:仅在支持时使用,否则降级
if ($storage instanceof SsoStorageInterface) {
// 推荐:原子化撤销(生产环境使用 Redis 时为 Lua 脚本原子操作)
$affected = $storage->atomicRevoke($jti, $uid, $platform, 3600);
// 维护用户活跃 Token 列表
$storage->trackUserToken($uid, $platform, $jti, 3600);
// SSO 平台 → JTI 映射
$storage->setSsoMapping($uid, $platform, $jti, 3600);
$bound = $storage->getSsoMapping($uid, $platform);
} else {
// 降级实现(顺序执行,非原子)
$storage->blacklist($jti, 3600);
$storage->delete("sso:{$uid}:{$platform}");
$storage->delete("token:{$jti}");
}
return [
'guards' => [
'api' => [
'driver' => 'sso',
'storage' => 'redis', // 指定 Redis 存储
'algo' => 'RS256',
'ttl' => 3600,
'refresh_ttl' => 604800,
'blacklist_enabled' => true, // 启用黑名单
],
],
'storage' => [
'redis' => [
'host' => '127.0.0.1',
'port' => 6379,
'password' => getenv('REDIS_PASSWORD'),
'database' => 0,
'prefix' => 'kode:jwt:',
'persistent' => true, // 长连接(可选)
'persistent_id' => 'kode_jwt',
],
],
];
// 用户主动退出登录
KodeJwt::guard('api')->invalidate($token);
// 强制注销某用户某平台所有 Token
$count = KodeJwt::revokeUserTokens('123', 'app');
// 强制注销某用户所有平台 Token
$count = KodeJwt::revokeUserTokens('123');
return [
'replay' => [
'mode' => 'strict', // strict / lenient / off
'',
'ttl' => 3600, // Nonce 保留时间(秒)
],
];
$payload = Payload::create(
uid: 123,
platform: 'web',
exp: time() + 3600,
iat: time(),
jti: Payload::generateJti(),
nonce: AntiReplay::generateNonce(16), // 32 字节随机值
);
$token = KodeJwt::guard('api')->issue($payload);
return [
'replay' => [
'mode' => 'lenient',
'window' => 60, // 窗口大小(秒)
'max_requests' => 5, // 窗口内最大允许次数
],
];
use Kode\Jwt\Exception\TokenReplayException;
use Kode\Jwt\Exception\TokenBlacklistedException;
try {
$payload = KodeJwt::guard('api')->authenticate($token);
} catch (TokenReplayException $e) {
// 重放攻击:记录安全日志、触发风控告警
security_log('replay_attack', [
'jti' => $e->getJti(),
'nonce' => $e->getNonce(),
'time' => $e->getReplayDetectedAt(),
]);
return response()->json(['error' => '请求被拒绝'], 401);
} catch (TokenBlacklistedException $e) {
// 已注销:引导重新登录
return response()->json(['error' => '会话已过期'], 401);
}
return [
'guards' => [
'api' => [
'algo' => 'RS256',
'expected_claims' => [
'iss' => 'https://auth.example.com', // 签发者必须匹配
'aud' => ['api.example.com', 'mobile'], // 受众命中其一即可
'sub' => 'auth-service', // 主体标识
'tenant_id' => 'tenant_42', // 自定义声明精确匹配
],
],
],
];
return [
'guards' => [
'api' => [
'clock_skew' => 30, // 允许 30 秒的时钟漂移
],
],
];
// 供 IDE 识别静态门面
/** @method static \Kode\Jwt\Token\Payload authenticate(string $token) */
/** @method static string issue(\Kode\Jwt\Token\Payload $payload) */
class KodeJwt {}
use Kode\Jwt\Token\Builder;
$builder = new Builder(['algo' => 'HS256']);
$builder->setUid(123);
$builder->setExpiration(time() + 3600);
// 多签配置
$signers = [
['key' => 'secret_key_1', 'keyId' => 'signer_a'],
['key' => 'secret_key_2', 'keyId' => 'signer_b'],
];
// 生成多签 JWS
$multiSigToken = $builder->buildMultiSignature($signers);
// 生成分离式签名
$detachedSig = $builder->buildDetachedSignature($signers);
use Kode\Jwt\OpenId\IdTokenBuilder;
use Kode\Jwt\OpenId\UserInfo;
// 构建 ID Token
$idTokenBuilder = new IdTokenBuilder([
'secret' => 'your-secret',
'issuer' => 'https://your-app.com',
]);
$idTokenBuilder
->setSubject('user-123')
->setAudience('client-app-id')
->setIssuer('https://your-app.com')
->setNonce('random-nonce-value')
->setAuthTime(time())
->setScopes(['openid', 'profile', 'email']);
$idToken = $idTokenBuilder->build();
// 用户信息
$userInfo = UserInfo::fromPayload($payload);
echo $userInfo->email;
echo $userInfo->name;
use Kode\Jwt\OAuth2\HybridProvider;
$provider = new HybridProvider([
'secret' => 'your-secret',
'access_token_ttl' => 3600,
'refresh_token_ttl' => 86400,
'issuer' => 'https://your-app.com',
]);
// Authorization Code 模式
$tokens = $provider->generateAuthorizationCodeTokens(
clientId: 'client-app',
userId: 123,
scopes: ['openid', 'profile'],
nonce: 'random-nonce'
);
// Implicit 模式
$tokens = $provider->generateImplicitTokens(
clientId: 'client-app',
userId: 123,
scopes: ['openid'],
state: 'state-value'
);
// Client Credentials 模式
$tokens = $provider->generateClientCredentialsTokens(
clientId: 'client-app',
scopes: ['api:read']
);
use Kode\Jwt\KeyRotation\KeyRotationManager;
use Kode\Jwt\Storage\MemoryStorage;
$storage = new MemoryStorage();
$rotationManager = new KeyRotationManager(
storage: $storage,
keyType: 'hmac',
defaultKeyLifetime: 2592000, // 30 天
transitionPeriod: 604800 // 7 天过渡期
);
// 生成新主密钥
$newKey = $rotationManager->generateNewKey();
// 获取签名密钥(当前主密钥)
$signingKey = $rotationManager->getSigningKey();
// 获取验证密钥列表(主密钥 + 过渡期内旧密钥)
$verificationKeys = $rotationManager->getVerificationKeys();
// 自动轮换(主密钥即将过期时触发)
$rotationManager->autoRotate(86400); // 提前 1 天轮换
// 查看轮换状态
$status = $rotationManager->getRotationStatus();
use Kode\Jwt\Metrics\PrometheusMetrics;
$metrics = new PrometheusMetrics('kode_jwt');
// 记录 Token 操作
$metrics->recordTokenIssued('api', 'web');
$metrics->recordTokenAuthenticated('api');
$metrics->recordTokenRefreshed('api');
$metrics->recordTokenInvalidated('api');
$metrics->recordAuthenticationFailure('expired', 'api');
// 更新统计值
$metrics->setActiveTokens(150, 'api');
$metrics->setBlacklistedTokens(12, 'api');
// 计时操作
$result = $metrics->timeOperation('authenticate', function() use ($token) {
return KodeJwt::authenticate($token);
});
// 导出 Prometheus 格式
echo $metrics->export();
// 输出:
// # HELP kode_jwt_tokens_issued_total Total count of tokens_issued_total
// # TYPE kode_jwt_tokens_issued_total counter
// kode_jwt_tokens_issued_total{guard="api",platform="web"} 1
declare(strict_types=1);
return [
'defaults' => [
'guard' => 'api',
'provider' => 'users',
'platform' => 'web',
],
'guards' => [
'api' => [
'driver' => 'sso',
'provider' => 'users',
'storage' => 'redis',
'blacklist_enabled' => true,
'refresh_enabled' => true,
'refresh_ttl' => 20160,
'ttl' => 1440,
'algo' => 'RS256',
'public_key' => storage_path('keys/public.pem'),
'private_key' => storage_path('keys/private.pem'),
],
],
'storage' => [
'redis' => [
'connection' => 'default',
'prefix' => 'kode:jwt:',
],
],
];
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Kode\Jwt\KodeJwt;
class JwtServiceProvider extends ServiceProvider
{
public function register(): void
{
$this->app->singleton('kode.jwt', function ($app) {
KodeJwt::detectAndLoadConfig();
return KodeJwt::guard();
});
}
public function boot(): void
{
// 发布配置文件
$this->publishes([
__DIR__ . '/../../config/jwt.php' => config_path('jwt.php'),
], 'jwt-config');
}
}
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Kode\Jwt\KodeJwt;
use Symfony\Component\HttpFoundation\Response;
class JwtAuthMiddleware
{
public function handle(Request $request, Closure $next): Response
{
$token = $request->bearerToken();
if (!$token) {
return response()->json(['error' => '未提供 Token'], 401);
}
try {
$payload = KodeJwt::authenticate($token);
$request->merge(['jwt_payload' => $payload]);
return $next($request);
} catch (\Exception $e) {
return response()->json(['error' => $e->getMessage()], 401);
}
}
}
// app/Http/Kernel.php
protected $routeMiddleware = [
'jwt.auth' => \App\Http\Middleware\JwtAuthMiddleware::class,
];
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use Kode\Jwt\KodeJwt;
use Kode\Jwt\Token\Payload;
class AuthController extends Controller
{
public function login()
{
$credentials = request()->validate([
'email' => 'json(['error' => '凭据无效'], 401);
}
// 生成 Token
$payload = Payload::create(
uid: $user->id,
username: $user->name,
platform: 'web',
exp: time() + 86400,
iat: time(),
jti: uniqid('jwt_'),
roles: [$user->role],
);
$result = KodeJwt::issue($payload);
return response()->json([
'token' => $result['token'],
'expires_in' => $result['expires_in'],
]);
}
public function me()
{
$payload = request()->get('jwt_payload');
return response()->json([
'id' => $payload->uid,
'username' => $payload->username,
]);
}
public function refresh()
{
$token = request()->bearerToken();
$result = KodeJwt::refresh($token);
return response()->json([
'token' => $result['token'],
'expires_in' => $result['expires_in'],
]);
}
public function logout()
{
$token = request()->bearerToken();
KodeJwt::invalidate($token);
return response()->json(['message' => '已注销']);
}
}
// routes/api.php
Route::prefix('auth')->group(function () {
Route::post('/login', [AuthController::class, 'login']);
Route::middleware('jwt.auth')->group(function () {
Route::get('/me', [AuthController::class, 'me']);
Route::post('/refresh', [AuthController::class, 'refresh']);
Route::post('/logout', [AuthController::class, 'logout']);
});
});
declare(strict_types=1);
return [
'defaults' => [
'guard' => 'api',
'provider' => 'users',
'platform' => 'api',
],
'guards' => [
'api' => [
'driver' => 'sso',
'provider' => 'users',
'storage' => 'coroutine_redis',
'blacklist_enabled' => true,
'refresh_enabled' => true,
'refresh_ttl' => 20160,
'ttl' => 1440,
'algo' => 'RS256',
'public_key' => BASE_PATH . '/storage/keys/public.pem',
'private_key' => BASE_PATH . '/storage/keys/private.pem',
],
],
'storage' => [
'coroutine_redis' => [
'host' => '127.0.0.1',
'port' => 6379,
'password' => null,
'database' => 0,
'prefix' => 'kode:jwt:',
],
],
];
declare(strict_types=1);
namespace App\Controller;
use Hyperf\HttpServer\Contract\RequestInterface;
use Hyperf\HttpServer\Contract\ResponseInterface;
use Kode\Jwt\KodeJwt;
use Kode\Jwt\Token\Payload;
class AuthController
{
public function login(RequestInterface $request, ResponseInterface $response)
{
$credentials = $request->all();
// 验证用户(示例)
$user = $this->validateUser($credentials);
// 生成 Token
$payload = Payload::create(
uid: $user['id'],
username: $user['name'],
platform: 'api',
exp: time() + 86400,
iat: time(),
jti: uniqid('jwt_'),
roles: [$user['role'] ?? 'user'],
);
$result = KodeJwt::issue($payload);
return $response->json([
'code' => 0,
'data' => [
'token' => $result['token'],
'expires_in' => $result['expires_in'],
],
]);
}
public function user(RequestInterface $request)
{
$payload = $request->getAttribute('jwt_payload');
return [
'code' => 0,
'data' => [
'id' => $payload->uid,
'username' => $payload->username,
],
];
}
private function validateUser(array $credentials): array
{
// 实现用户验证逻辑
return [
'id' => 1,
'name' => 'test_user',
'role' => 'admin',
];
}
}
declare(strict_types=1);
namespace App\Middleware;
use Hyperf\HttpServer\Contract\RequestInterface;
use Hyperf\HttpServer\Contract\ResponseInterface;
use Psr\Http\Message\ResponseInterface as PsrResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Kode\Jwt\KodeJwt;
class JwtAuthMiddleware
{
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): PsrResponseInterface
{
$token = $request->getHeader('Authorization')[0] ?? '';
if (!$token) {
return (new ResponseInterface())->json([
'code' => 401,
'message' => '未提供 Token',
]);
}
$token = str_replace('Bearer ', '', $token);
try {
$payload = KodeJwt::authenticate($token);
// 将 payload 添加到请求属性中
$request = $request->withAttribute('jwt_payload', $payload);
return $handler->handle($request);
} catch (\Exception $e) {
return (new ResponseInterface())->json([
'code' => 401,
'message' => $e->getMessage(),
]);
}
}
}
// config/autoload/middlewares.php
return [
'http' => [
\App\Middleware\JwtAuthMiddleware::class,
],
];
declare(strict_types=1);
return [
'defaults' => [
'guard' => 'api',
'provider' => 'users',
'platform' => 'web',
],
'guards' => [
'api' => [
'driver' => 'sso',
'provider' => 'users',
'storage' => 'redis',
'blacklist_enabled' => true,
'refresh_enabled' => true,
'refresh_ttl' => 20160,
'ttl' => 1440,
'algo' => 'RS256',
'public_key' => runtime_path() . 'keys/public.pem',
'private_key' => runtime_path() . 'keys/private.pem',
],
],
'storage' => [
'redis' => [
'host' => '127.0.0.1',
'port' => 6379,
'password' => '',
'database' => 0,
'prefix' => 'kode:jwt:',
],
],
];
declare(strict_types=1);
namespace app\base;
use think\App;
use think\Controller;
use Kode\Jwt\KodeJwt;
abstract class AuthController extends Controller
{
protected ?object $jwtPayload = null;
protected function initialize(): void
{
parent::initialize();
$token = $this->request->header('Authorization');
$token = $token ? str_replace('Bearer ', '', $token) : '';
if (!$token) {
$this->error('未提供 Token', [], 401);
}
try {
$this->jwtPayload = KodeJwt::authenticate($token);
} catch (\Exception $e) {
$this->error($e->getMessage(), [], 401);
}
}
protected function getUserId(): int|string
{
return $this->jwtPayload->uid;
}
protected function getUserPayload(): object
{
return $this->jwtPayload;
}
}
declare(strict_types=1);
namespace app\controller;
use app\base\AuthController;
use Kode\Jwt\KodeJwt;
use Kode\Jwt\Token\Payload;
class Auth extends AuthController
{
public function login()
{
$credentials = $this->request->post();
// 验证用户
$user = \app\model\User::where('email', $credentials['email'] ?? '')->find();
if (!$user || !password_verify($credentials['password'] ?? '', $user->password)) {
$this->error('凭据无效');
}
$payload = Payload::create(
uid: $user->id,
username: $user->name,
platform: 'web',
exp: time() + 86400,
iat: time(),
jti: uniqid('jwt_'),
roles: [$user->role],
);
$result = KodeJwt::issue($payload);
return json([
'token' => $result['token'],
'expires_in' => $result['expires_in'],
]);
}
public function me()
{
return json([
'id' => $this->getUserId(),
'username' => $this->jwtPayload->username,
]);
}
public function refresh()
{
$token = $this->request->header('Authorization');
$token = $token ? str_replace('Bearer ', '', $token) : '';
$result = KodeJwt::refresh($token);
return json([
'token' => $result['token'],
'expires_in' => $result['expires_in'],
]);
}
public function logout()
{
$token = $this->request->header('Authorization');
$token = $token ? str_replace('Bearer ', '', $token) : '';
KodeJwt::invalidate($token);
return json(['message' => '已注销']);
}
}
// route/app.php
use app\controller\Auth;
Route::post('auth/login', [Auth::class, 'login']);
Route::group(function () {
Route::get('auth/me', [Auth::class, 'me']);
Route::post('auth/refresh', [Auth::class, 'refresh']);
Route::post('auth/logout', [Auth::class, 'logout']);
})->middleware(\app\middleware\AuthMiddleware::class);
declare(strict_types=1);
Jwt\Token\Payload;
// 初始化(使用默认配置或加载配置文件)
KodeJwt::detectAndLoadConfig();
// 或手动配置
KodeJwt::init([
'defaults' => [
'guard' => 'api',
'storage' => 'memory',
],
'guards' => [
'api' => [
'driver' => 'sso',
'storage' => 'memory',
'algo' => 'HS256',
'secret' => file_get_contents(__DIR__ . '/storage/keys/secret'),
'ttl' => 3600,
'refresh_ttl' => 604800,
],
],
]);
// 生成 Token
$payload = Payload::create(
uid: 'user_123',
username: 'test_user',
platform: 'web',
exp: time() + 3600,
iat: time(),
jti: uniqid('jwt_'),
);
$result = KodeJwt::issue($payload);
$token = $result['token'];
echo "Token: {$token}\n";
// 验证 Token
try {
$payload = KodeJwt::authenticate($token);
echo "用户: {$payload->username}\n";
echo "过期时间: " . date('Y-m-d H:i:s', $payload->exp) . "\n";
} catch (\Exception $e) {
echo "验证失败: {$e->getMessage()}\n";
}
// 刷新 Token
$newResult = KodeJwt::refresh($token);
echo "新 Token: {$newResult['token']}\n";
// 注销 Token
KodeJwt::invalidate($token);
echo "已注销\n";
namespace App\Security;
use Kode\Jwt\KodeJwt;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Http\Authenticator\AbstractAuthenticator;
use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
class JwtAuthenticator extends AbstractAuthenticator
{
public function __construct(
private KodeJwt $jwtService
) {}
public function supports(Request $request): ?bool
{
return $request->headers->has('Authorization');
}
public function authenticate(Request $request): SelfValidatingPassport
{
$token = $request->headers->get('Authorization');
$token = str_replace('Bearer ', '', $token);
$payload = $this->jwtService->authenticate($token);
return new SelfValidatingPassport(
new UserBadge($payload->uid, function () use ($payload) {
return new User($payload->uid, [], [], $payload->roles ?? []);
})
);
}
public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response
{
return new JsonResponse(['error' => '认证失败'], 401);
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response
{
return null;
}
}
return [
'components' => [
'jwt' => [
'class' => 'Kode\Jwt\KodeJwt',
'config' => [
'defaults' => [
'guard' => 'api',
'provider' => 'user',
'platform' => 'web',
],
'guards' => [
'api' => [
'driver' => 'sso',
'provider' => 'user',
'storage' => 'redis',
'blacklist_enabled' => true,
'refresh_enabled' => true,
'refresh_ttl' => 20160,
'ttl' => 1440,
'algo' => 'RS256',
'public_key' => '@app/runtime/keys/public.pem',
'private_key' => '@app/runtime/keys/private.pem',
],
],
'storage' => [
'redis' => [
'connection' => 'default',
'prefix' => 'kode:jwt:',
],
],
],
],
],
];
namespace app\commands;
use Yii;
use yii\console\Controller;
use Kode\Jwt\KodeJwt;
class JwtController extends Controller
{
public function actionInit()
{
$keyDir = Yii::getAlias('@app/runtime/keys');
if (!is_dir($keyDir)) {
mkdir($keyDir, 0755, true);
}
KodeJwt::init([
'defaults' => [
'guard' => 'api',
],
'guards' => [
'api' => [
'driver' => 'sso',
'storage' => 'redis',
'algo' => 'RS256',
'public_key' => $keyDir . '/public.pem',
'private_key' => $keyDir . '/private.pem',
'ttl' => 1440,
'refresh_ttl' => 20160,
],
],
]);
$result = KodeJwt::generateKeys('rsa', $keyDir);
if ($result['success']) {
echo "✅ 密钥生成成功!\n";
echo "私钥: {$result['private_key_path']}\n";
echo "公钥: {$result['public_key_path']}\n";
} else {
echo "❌ 密钥生成失败: {$result['error']}\n";
}
}
}
namespace app\components;
use Yii;
use yii\base\Behavior;
use yii\web\Controller;
use Kode\Jwt\KodeJwt;
use Kode\Jwt\Token\Payload;
class AuthenticatedBehavior extends Behavior
{
public function events()
{
return [
Controller::EVENT_BEFORE_ACTION => 'beforeAction',
];
}
public function beforeAction($action)
{
$request = Yii::$app->request;
$authHeader = $request->getHeaders()->get('Authorization');
if (!$authHeader || !str_starts_with($authHeader, 'Bearer ')) {
Yii::$app->response->statusCode = 401;
echo json_encode(['error' => '未提供认证令牌']);
return false;
}
$token = substr($authHeader, 7);
try {
$payload = KodeJwt::authenticate($token);
Yii::$app->user->identity = $this->findUser($payload->uid);
Yii::$app->jwtPayload = $payload;
return true;
} catch (\Exception $e) {
Yii::$app->response->statusCode = 401;
echo json_encode(['error' => '认证失败: ' . $e->getMessage()]);
return false;
}
}
protected function findUser($uid)
{
return \app\models\User::findOne($uid);
}
}
namespace app\controllers;
use Yii;
use yii\rest\Controller;
use app\components\AuthenticatedBehavior;
use Kode\Jwt\KodeJwt;
use Kode\Jwt\Token\Payload;
class ApiController extends Controller
{
public function behaviors()
{
$behaviors = parent::behaviors();
$behaviors['auth'] = AuthenticatedBehavior::class;
return $behaviors;
}
public function actionLogin()
{
$request = Yii::$app->request;
$username = $request->post('username');
$password = $request->post('password');
$user = \app\models\User::findOne(['username' => $username]);
if (!$user || !$user->validatePassword($password)) {
throw new \yii\web\UnauthorizedHttpException('用户名或密码错误');
}
$payload = Payload::create(
uid: $user->id,
username: $user->username,
platform: 'web',
exp: time() + 1440 * 60,
iat: time(),
jti: uniqid('jwt_'),
roles: [$user->role],
);
$result = KodeJwt::issue($payload);
return [
'token' => $result['token'],
'expires_in' => $result['expires_in'],
'refresh_ttl' => $result['refresh_ttl'],
];
}
public function actionProfile()
{
$payload = Yii::$app->jwtPayload;
return [
'uid' => $payload->uid,
'username' => $payload->username,
'roles' => $payload->roles,
];
}
public function actionRefresh()
{
$request = Yii::$app->request;
$refreshToken = $request->post('refresh_token');
$result = KodeJwt::refresh($refreshToken);
return [
'token' => $result['token'],
'expires_in' => $result['expires_in'],
];
}
public function actionLogout()
{
$request = Yii::$app->request;
$token = $request->post('token');
KodeJwt::invalidate($token);
return ['message' => '已成功注销'];
}
}
return [
'Jwt' => [
'defaults' => [
'guard' => 'api',
'provider' => 'Users',
'platform' => 'web',
],
'guards' => [
'api' => [
'driver' => 'sso',
'provider' => 'Users',
'storage' => 'redis',
'blacklist_enabled' => true,
'refresh_enabled' => true,
'refresh_ttl' => 20160,
'ttl' => 1440,
'algo' => 'RS256',
'public_key' => ROOT . '/config/keys/public.pem',
'private_key' => ROOT . '/config/keys/private.pem',
],
],
'storage' => [
'redis' => [
'connection' => 'default',
'prefix' => 'kode:jwt:',
],
],
],
];
use Kode\Jwt\KodeJwt;
$jwtConfig =
namespace App\Shell;
use Cake\Console\Shell;
use Kode\Jwt\KodeJwt;
class JwtShell extends Shell
{
public function main()
{
$keyDir = ROOT . '/config/keys';
if (!is_dir($keyDir)) {
mkdir($keyDir, 0755, true);
}
$this->out('正在生成 RSA 密钥对...');
$result = KodeJwt::generateKeys('rsa', $keyDir);
if ($result['success']) {
$this->out('<success>✅ 密钥生成成功!</success>');
$this->out("私钥: {$result['private_key_path']}");
$this->out("公钥: {$result['public_key_path']}");
} else {
$this->out('<error>❌ 密钥生成失败: ' . $result['error'] . '</error>');
}
}
}
namespace App\Middleware;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Kode\Jwt\KodeJwt;
use Kode\Jwt\Token\Payload;
class JwtAuthenticationMiddleware
{
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
$authHeader = $request->getHeaderLine('Authorization');
if (!$authHeader || !str_starts_with($authHeader, 'Bearer ')) {
return $this->unauthorizedResponse('未提供认证令牌');
}
$token = substr($authHeader, 7);
try {
$payload = KodeJwt::authenticate($token);
$request = $request->withAttribute('jwt_payload', $payload);
$request = $request->withAttribute('user_id', $payload->uid);
return $handler->handle($request);
} catch (\Exception $e) {
return $this->unauthorizedResponse('认证失败: ' . $e->getMessage());
}
}
protected function unauthorizedResponse(string $message): ResponseInterface
{
return new \ Laminas\Diactoros\Response\JsonResponse([
'error' => $message,
], 401);
}
}
public function middleware(MiddlewareQueue $middlewareQueue): MiddlewareQueue
{
$middlewareQueue->add(new \App\Middleware\JwtAuthenticationMiddleware());
return $middlewareQueue;
}
namespace App\Controller;
use Cake\Controller\Controller;
use Kode\Jwt\KodeJwt;
use Kode\Jwt\Token\Payload;
class AuthController extends Controller
{
public function login()
{
$username = $this->request->getData('username');
$password = $this->request->getData('password');
$user = $this->Users->findByUsername($username)->first();
if (!$user || !$user->verifyPassword($password)) {
$this->response = $this->response->withStatus(401);
$this->set(['error' => '用户名或密码错误']);
$this->set('_serialize', ['error']);
return;
}
$payload = Payload::create(
uid: $user->id,
username: $user->username,
platform: 'web',
exp: time() + 1440 * 60,
iat: time(),
jti: uniqid('jwt_'),
roles: [$user->role],
);
$result = KodeJwt::issue($payload);
$this->set([
'token' => $result['token'],
'expires_in' => $result['expires_in'],
'refresh_ttl' => $result['refresh_ttl'],
]);
$this->set('_serialize', ['token', 'expires_in', 'refresh_ttl']);
}
public function profile()
{
$payload = $this->request->getAttribute('jwt_payload');
$this->set([
'uid' => $payload->uid,
'username' => $payload->username,
'roles' => $payload->roles,
]);
$this->set('_serialize', ['uid', 'username', 'roles']);
}
public function refresh()
{
$refreshToken = $this->request->getData('refresh_token');
$result = KodeJwt::refresh($refreshToken);
$this->set([
'token' => $result['token'],
'expires_in' => $result['expires_in'],
]);
$this->set('_serialize', ['token', 'expires_in']);
}
public function logout()
{
$token = $this->request->getData('token');
KodeJwt::invalidate($token);
$this->set(['message' => '已成功注销']);
$this->set('_serialize', ['message']);
}
}
namespace App\Controller\Component;
use Cake\Controller\Component;
use Kode\Jwt\KodeJwt;
use Kode\Jwt\Token\Payload;
class JwtComponent extends Component
{
protected $_defaultConfig = [
'guard' => 'api',
];
public function initialize(array $config): void
{
parent::initialize($config);
KodeJwt::detectAndLoadConfig();
}
public function issue(array $userData, string $platform = 'web'): array
{
$payload = Payload::create(
uid: $userData['id'],
username: $userData['username'] ?? null,
platform: $platform,
exp: time() + 1440 * 60,
iat: time(),
jti: uniqid('jwt_'),
roles: $userData['roles'] ?? null,
perms: $userData['perms'] ?? null,
);
return KodeJwt::issue($payload);
}
public function authenticate(string $token): Payload
{
return KodeJwt::authenticate($token);
}
public function refresh(string $token): array
{
return KodeJwt::refresh($token);
}
public function invalidate(string $token): void
{
KodeJwt::invalidate($token);
}
public function getPayload(): ?Payload
{
return $this->getController()->request->getAttribute('jwt_payload');
}
public function getUserId(): mixed
{
$payload = $this->getPayload();
return $payload?->uid;
}
public function hasRole(string $role): bool
{
$payload = $this->getPayload();
return $payload && in_array($role, $payload->roles ?? []);
}
public function hasPermission(string $permission): bool
{
$payload = $this->getPayload();
return $payload && in_array($permission, $payload->perms ?? []);
}
}
namespace App\Controller;
class ApiController extends Controller
{
public function initialize(): void
{
parent::initialize();
$this->loadComponent('Jwt');
}
public function protectedAction()
{
$userId = $this->Jwt->getUserId();
$hasAdminRole = $this->Jwt->hasRole('admin');
$this->set(compact('userId', 'hasAdminRole'));
}
}
// 方式1:自动检测并加载配置文件
KodeJwt::detectAndLoadConfig();
// 方式2:手动初始化(使用默认配置)
KodeJwt::init();
// 方式3:手动初始化(使用自定义配置)
KodeJwt::init([
'defaults' => [
'guard' => 'api',
'platform' => 'web',
],
'guards' => [
'api' => [
'driver' => 'sso',
'storage' => 'redis',
'algo' => 'RS256',
'ttl' => 1440,
],
],
]);
// 方式4:从文件加载配置
KodeJwt::loadConfigFromFile('/path/to/config/jwt.php');
// 获取默认守卫
$guard = KodeJwt::guard();
// 获取指定守卫
$guard = KodeJwt::guard('api');
// 获取默认守卫(别名)
$guard = KodeJwt::guard('default');
// 签发 Token
$result = KodeJwt::issue(Payload $payload): array;
// 返回: ['token' => string, 'expires_in' => int, 'refresh_ttl' => int]
// 验证 Token 并返回 Payload
$payload = KodeJwt::authenticate(string $token): Payload;
// 刷新 Token
$result = KodeJwt::refresh(string $token): array;
// 返回: ['token' => string, 'expires_in' => int]
// 注销 Token(加入黑名单)
KodeJwt::invalidate(string $token): void;
// 检查 Token 是否有效
$isValid = KodeJwt::isTokenValid(string $token): bool;
// 获取 Token 详细信息
$info = KodeJwt::getTokenInfo(string $token): array;
// 返回: ['uid' => int|string, 'platform' => string, 'exp' => int, ...]
// 获取用户的所有活跃 Token
$tokens = KodeJwt::getUserTokens(int|string $uid, string $platform): array;
// 强制注销用户的所有 Token
$count = KodeJwt::revokeUserTokens(int|string $uid, string $platform): int;
// 清理过期的 Token
$count = KodeJwt::cleanExpired(): int;
// 获取存储统计信息
$stats = KodeJwt::getStats(): array;
// 返回: ['total' => int, 'expired' => int, 'active' => int]
// 获取防重放保护器
$replay = KodeJwt::antiReplay(): ?AntiReplay;
// 生成一次性 Nonce(32 字节随机值)
$nonce = AntiReplay::generateNonce(16);
// 手动消费 Nonce(高级用法,通常由 Guard 自动调用)
$passed = $replay->check($jti, $nonce, $ttl);
// 查询 Nonce 是否已被消费
$seen = $replay->seen($jti, $nonce);
// 检查是否启用
$enabled = $replay->isEnabled();
// 生成密钥对
$result = KodeJwt::generateKeys(string $type, ?string $path = null): array;
// $type: 'rsa' | 'hmac'
// 返回: ['success' => bool, 'private_key_path' => string, 'public_key_path' => string, 'error' => string]
// 示例
$result = KodeJwt::generateKeys('rsa', '/path/to/keys');
if ($result['success']) {
echo "私钥: {$result['private_key_path']}";
echo "公钥: {$result['public_key_path']}";
}
// 获取事件调度器实例
$events = KodeJwt::events(): EventDispatcher;
// 监听事件
KodeJwt::events()->on(TokenIssued::class, function ($event) {
// $event->payload
});
// 移除监听器
KodeJwt::events()->off(TokenIssued::class);
// 方式1:使用构造函数
$payload = new Payload(
uid: 123,
username: 'john_doe',
platform: 'web',
exp: time() + 3600,
iat: time(),
jti: uniqid('jwt_'),
roles: ['user'],
perms: ['read', 'write'],
custom: ['department' => 'IT']
);
// 方式2:使用静态方法 create()
$payload = Payload::create(
uid: 123,
username: 'john_doe',
platform: 'web',
exp: time() + 3600,
iat: time(),
jti: uniqid('jwt_'),
roles: ['user'],
perms: ['read', 'write'],
customData: ['department' => 'IT']
);
// 方式3:从数组创建
$payload = Payload::fromArray([
'uid' => 123,
'username' => 'john_doe',
'platform' => 'web',
'exp' => time() + 3600,
'iat' => time(),
'jti' => uniqid('jwt_'),
'roles' => ['user'],
'perms' => ['read', 'write'],
'custom' => ['department' => 'IT'],
]);
// 转换为数组
$array = $payload->toArray(): array;
// 获取自定义数据
$custom = $payload->getCustomData(): array;
// 获取特定自定义数据
$value = $payload->getCustom(string $key, mixed $default = null): mixed;
// 检查自定义数据是否存在
$exists = $payload->hasCustom(string $key): bool;
// 检查是否具有角色
$hasRole = $payload->hasRole(string $role): bool;
// 检查是否具有权限
$hasPerm = $payload->hasPermission(string $permission): bool;
// 获取用户信息
$userInfo = $payload->getUserInfo(): array;
// 检查是否已过期
$isExpired = $payload->isExpired(): bool;
// 获取剩余有效时间(秒)
$ttl = $payload->getTtl(): int;
// 获取用户标识
$userId = $payload->getUserIdentifier(): mixed;
// 🆕 获取一次性 Nonce
$nonce = $payload->getNonce(): ?string;
// 🆕 获取受众(aud)
$aud = $payload->getAudience(): string|array|null;
// 🆕 获取签发者(iss)
$iss = $payload->getIssuer(): ?string;
// 🆕 获取主体(sub)
$sub = $payload->getSubject(): ?string;
// 🆕 生成高熵 JTI(32 字节随机值)
$jti = Payload::generateJti(): string;
use Kode\Jwt\Contract\GuardInterface;
interface GuardInterface
{
// 签发 Token
public function issue(Payload $payload): array;
// 验证 Token
public function authenticate(string $token): Payload;
// 刷新 Token
public function refresh(string $token): array;
// 注销 Token
public function invalidate(string $token): void;
// 检查 Token 是否有效
public function isValid(string $token): bool;
// 获取 Token 信息
public function getTokenInfo(string $token): array;
}
use Kode\Jwt\Contract\StorageInterface;
interface StorageInterface
{
// 设置缓存
public function set(string $key, mixed $value, int $ttl = 0): bool;
// 获取缓存
public function get(string $key, mixed $default = null): mixed;
// 删除缓存
public function delete(string $key): bool;
// 检查键是否存在
public function has(string $key): bool;
// 加入黑名单
public function blacklist(string $jti, int $ttl = 3600): bool;
// 检查是否在黑名单中
public function isBlacklisted(string $jti): bool;
// 批量设置
public function setMultiple(array $values, int $ttl = 0): bool;
// 批量获取
public function getMultiple(array $keys, mixed $default = null): array;
// 批量删除
public function deleteMultiple(array $keys): bool;
// 清空所有缓存
public function flush(): bool;
// 获取存储统计信息
public function stats(): array;
}
use Kode\Jwt\Event\TokenIssued;
$event = new TokenIssued(Payload $payload);
// 访问 Payload
$uid = $event->payload->uid;
$jti = $event->payload->jti;
use Kode\Jwt\Event\TokenExpired;
$event = new TokenExpired(Payload $payload);
use Kode\Jwt\Event\TokenRevoked;
$event = new TokenRevoked(Payload $payload);
use Kode\Jwt\Exception\TokenInvalidException;
use Kode\Jwt\Exception\TokenExpiredException;
use Kode\Jwt\Exception\TokenBlacklistedException;
// Token 无效
throw new TokenInvalidException(string $message = '');
// Token 已过期
throw new TokenExpiredException(string $message = '');
// Token 在黑名单中
throw new TokenBlacklistedException(string $message = '');
// 推荐:使用环境变量
$secret = getenv('JWT_SECRET') ?: $_ENV['JWT_SECRET'];
// 或从文件加载
$privateKey = file_get_contents(storage_path('keys/private.pem'));
$publicKey = file_get_contents(storage_path('keys/public.pem'));
// 配置
KodeJwt::init([
'guards' => [
'api' => [
'algo' => 'RS256',
'private_key' => $privateKey,
'public_key' => $publicKey,
],
],
]);
return [
'defaults' => [
'guard' => 'api',
],
'guards' => [
'api' => [
'driver' => 'sso',
'storage' => 'redis',
'algo' => 'RS256',
'ttl' => 3600,
'platform' => null,
],
'admin' => [
'driver' => 'sso',
'storage' => 'redis',
'algo' => 'RS256',
'ttl' => 1800, // 管理员 Token 更短
'platform' => 'admin',
],
'mobile' => [
'driver' => 'mlo', // 多点登录
'storage' => 'redis',
'algo' => 'HS256',
'ttl' => 86400,
'max_devices' => 3,
],
],
];
use Kode\Jwt\KodeJwt;
// Token 签发事件
KodeJwt::events()->on(\Kode\Jwt\Event\TokenIssued::class, function ($event) {
error_log("Token 签发: uid={$event->payload->uid}, jti={$event->payload->jti}");
});
// Token 注销事件
KodeJwt::events()->on(\Kode\Jwt\Event\TokenRevoked::class, function ($event) {
error_log("Token 注销: uid={$event->payload->uid}");
});
use Kode\Jwt\Exception\TokenInvalidException;
use Kode\Jwt\Exception\TokenExpiredException;
use Kode\Jwt\Exception\TokenBlacklistedException;
try {
$payload = KodeJwt::authenticate($token);
} catch (TokenInvalidException $e) {
// Token 无效(签名错误)
return response()->json(['error' => 'Token 无效'], 401);
} catch (TokenExpiredException $e) {
// Token 已过期
return response()->json(['error' => 'Token 已过期,请刷新'], 401);
} catch (TokenBlacklistedException $e) {
// Token 已被加入黑名单
return response()->json(['error' => 'Token 已被注销'], 401);
} catch (\Exception $e) {
// 其他错误
return response()->json(['error' => '认证失败'], 500);
}
namespace App\Storage;
use Kode\Jwt\Contract\StorageInterface;
class CustomStorage implements StorageInterface
{
public function set(string $key, mixed $value, int $ttl = 0): bool
{
// 实现逻辑
}
public function get(string $key, mixed $default = null): mixed
{
// 实现逻辑
}
public function delete(string $key): bool
{
// 实现逻辑
}
public function has(string $key): bool
{
// 实现逻辑
}
public function cleanExpired(): int
{
// 实现逻辑
}
public function getStats(): array
{
// 实现逻辑
}
}
KodeJwt::init([
'guards' => [
'api' => [
'driver' => 'sso',
'storage' => 'custom', // 使用自定义存储
],
],
'storage' => [
'custom' => [
'driver' => \App\Storage\CustomStorage::class,
],
],
]);
namespace App\Guard;
use Kode\Jwt\Contract\GuardInterface;
use Kode\Jwt\Contract\StorageInterface;
use Kode\Jwt\Token\Payload;
class CustomGuard implements GuardInterface
{
public function __construct(
private StorageInterface $storage,
private array $config
) {}
public function issue(Payload $payload): array
{
// 自定义签发逻辑
}
public function authenticate(string $token): Payload
{
// 自定义验证逻辑
}
public function refresh(string $token): array
{
// 自定义刷新逻辑
}
public function invalidate(string $token): bool
{
// 自定义注销逻辑
}
public function validateToken(string $token): bool
{
// 自定义验证逻辑
}
}
bash
# 进入你的项目目录
cd /path/to/your/project
# 安装配置文件和生成密钥(RSA 密钥对 + HMAC 密钥)
php vendor/bin/jwt install
# 或者仅生成配置文件
php vendor/bin/jwt install --config-only
# 或者仅生成密钥
php vendor/bin/jwt install --key-only
# 强制覆盖已存在的文件
php vendor/bin/jwt install --force
bash
# 生成 RSA 密钥对(默认)
php jwt key rsa
# 生成 HMAC 密钥
php jwt key hmac
# 生成并输出到控制台
php jwt key rsa stdout
# 强制覆盖现有密钥
php jwt key rsa --force
bash
# 生成 Token
php bin/jwt token generate --uid=123 --username=john --platform=web
# 验证 Token
php bin/jwt token verify --token=eyJ...
# 刷新 Token
php bin/jwt token refresh --token=eyJ...
# 注销 Token
php bin/jwt token invalidate --token=eyJ...
# 查看 Token 信息
php bin/jwt token info --token=eyJ...
bash
# 安装依赖
composer 生成 config/jwt.php)
php artisan jwt:install
# 生成密钥
php artisan jwt:key
bash
php yii jwt/init
bash
# 下载并解压包后
php bin/jwt install --config-only
php bin/jwt key rsa --force