ThinkPHP5集成JWT Token实现API接口认证全流程
在构建基于ThinkPHP5的RESTful API或前后端分离项目时,传统的Session/Cookie认证往往面临跨域和分布式扩展难题。JWT(JSON Web Token)作为一种开放标准,能够实现无状态的身份验证。本文将演示如何利用Lcobucci/jwt库,在ThinkPHP5中实现从Token生成、配置到自动验证的全流程代码。
准备条件:环境配置与依赖安装
首先,确保你的开发环境中已配置好 Composer。为了加速依赖包的下载,建议先将镜像源切换为阿里云镜像:
composer config -g repo.packagist composer https://mirrors.aliyun.com/composer/
接着,在 ThinkPHP5 项目根目录下,通过 Composer 安装 JWT 库。这里推荐使用稳定版本 3.3:
composer require lcobucci/jwt:3.3
第一步:创建 Token 辅助类
为了方便调用,建议在项目的 extend 目录下新建一个自定义类文件,例如 Token.php。该类将封装 Token 的生成与验证逻辑,避免在业务控制器中耦合过多底层代码。
在 Token.php 中,引入必要的命名空间并定义 createToken 静态方法。该方法负责组装 JWT 的各个部分,包括发行者、受众、唯一标识符、签发时间、生效时间及过期时间等。
<?php
namespace extend;
use Lcobucci\JWT\Builder;
use Lcobucci\JWT\signer\Hmac\Sha256;
class Token
{
/**
* 生成 JWT Token
* @param string|null $uid 用户ID
* @return string
*/
public static function createToken($uid = null)
{
// 1. 设置签名算法 (HS256 需要 Secret Key)
$signer = new Sha256('your-secret-key-here');
// 2. 获取当前时间戳
$time = time();
// 3. 构建 Token 内容
$token = (new Builder())
->issuedBy('my-app') // Issuer (发行人)
->canonlyBeUsedBy('api-users') // Audience (受众/接收人)
->identifiedBy('unique-id') // JTI (JWT ID)
->issuedAt($time) // IAT (Issued At)
->canonlyBeUsedAfter($time) // NBF (Not Before) - 立即生效
->expiresAt($time + 3600) // EXP (Expiration) - 1小时后过期
->with('uid', $uid); // 自定义 Payload,存放用户ID
// 4. 生成签名并返回字符串形式的 Token
return (string)$token->sign($signer)->getToken();
}
}
注意:在实际项目中,'your-secret-key-here' 应当作为敏感信息存储在配置文件(如 config/app.php 或环境变量)中,切勿硬编码或在代码仓库中泄露。此外,canonlyBeUsedAfter 用于设置令牌生效前的等待时间,通常设为即时生效即可。
第二步:在控制器中测试生成 Token
创建一个简单的控制器来测试 Token 的生成。在 app/index/controller 下新建或修改控制器:
<?php
namespace app\index\controller;
use think\Controller;
use extend\Token;
class Test extends Controller
{
public function generate()
{
// 假设用户ID为 1001
$token = Token::createToken(1001);
echo "Generated Token: " . $token;
}
}
访问该接口后,你将获得一串由三个点 . 分隔的 Base64url 编码字符串。这就是标准的 JWT 格式。
第三步:实现 Token 验证逻辑
仅生成 Token 是不够的,核心在于验证其有效性。我们需要在 Token.php 中增加 verifyToken 方法。该方法需要重新构建相同的签名算法和配置参数,以对比传入的 Token 是否被篡改或过期。
在 Token.php 中添加以下静态方法:
// 在 extend/Token.php 中
use Lcobucci\JWT\Parser;
use Lcobucci\JWT\ValidationData;
use Lcobucci\JWT\signer\Hmac\Sha256;
// ... (前面的 createToken 方法保持不变)
/**
* 验证 Token 的有效性
* @param string $token
* @return mixed 验证失败返回 false,成功则返回 payload 中的 uid
*/
public static function verifyToken($token)
{
if (empty($token)) {
return false;
}
try {
// 1. 解析 Token
$parser = new Parser();
$tokenObj = $parser->parse((string) $token);
// 2. 设置验证数据 (必须与生成时一致)
$data = new ValidationData();
$data->setIssuer('my-app'); // 发行人
$data->setAudience('api-users'); // 受众
$data->setId('unique-id'); // ID
$data->setIssuedAtRange(time() - 5, time() + 5); // 允许的时间偏差 (可选,应对服务器时间差异)
// 3. 验证声明 (Claims)
if (!$tokenObj->validate($data)) {
return false;
}
// 4. 验证签名 (Signature)
$signer = new Sha256('your-secret-key-here');
if (!$tokenObj->verify($signer, 'your-secret-key-here')) {
return false;
}
// 5. 验证通过,提取载荷中的用户ID
return $tokenObj->getClaim('uid');
} catch (\Exception $e) {
// 捕获解析异常
return false;
}
}
第四步:实战应用——登录鉴权流程
以下是一个完整的登录接口实现示例。展示如何在校验用户名密码后签发 Token,并在后续请求中利用 BaseController 自动验证 Token。
1. 登录接口 (Login Controller)
在此处不推荐直接使用原生 DB,建议使用 Model。此处仅为原理演示:
<?php
namespace app\index\controller;
use think\Controller;
use think\Db;
use extend\Token;
class Login extends Controller
{
public function doLogin()
{
$input = input();
$username = isset($input['username']) ? trim($input['username']) : '';
$password = isset($input['password']) ? trim($input['password']) : '';
if (empty($username) || empty($password)) {
return json(['code' => 400, 'msg' => '账号或密码不能为空']);
}
// 简单MD5加密 (生产环境建议加盐或使用 bcrypt)
$password = md5($password);
// 查询用户
$user = Db::name('users')->where(['username' => $username, 'password' => $password])->find();
if (!$user) {
return json(['code' => 401, 'msg' => '账号或密码错误']);
}
// 生成 Token
$token = Token::createToken($user['id']);
// 移除敏感信息 (如 password)
unset($user['password']);
$user['token'] = $token;
return json(['code' => 200, 'msg' => '登录成功', 'data' => $user]);
}
}
2. 受保护接口与基础控制器自动验证
为了让所有需要认证的接口共享验证逻辑,创建一个 Base 控制器,在其他业务控制器中继承它。
<?php
namespace app\index\controller;
use think\Controller;
use think\Request;
use extend\Token;
class Base extends Controller
{
public function _initialize(Request $request = null)
{
parent::_initialize();
// 获取前端传递的 Token (通常在 Header 中,这里模拟从参数获取)
$token = $request->param('token', '');
if (empty($token)) {
// 尝试从 Header 获取
$token = $request->header('Authorization');
// 如果是 Bearer Token 格式,截取后半部分
if ($token && strpos($token, 'Bearer ') === 0) {
$token = substr($token, 7);
}
}
// 验证 Token
$userId = Token::verifyToken($token);
if (!$userId) {
exit(json_encode([
'code' => 403,
'msg' => '无效的 Token 或未授权访问'
], JSON_UNESCAPED_UNICODE));
}
// 验证通过后,将用户ID存入全局变量或Request对象
$this->assignAuthUser($userId);
}
}
随后,其他受保护的接口只需继承 Base 类即可自动完成认证:
<?php
namespace app\index\controller;
class Order extends Base
{
public function getList()
{
// 由于继承了 Base,此处 token 已在 _initialize 中被验证
// 可以直接使用 $this->authUserId 获取当前用户ID
$userId = session::get('current_uid'); // 假设你在 Base 中存入了 session
$orders = Db::name('orders')->where('user_id', $userId)->select();
return json(['code' => 200, 'data' => $orders]);
}
}
总结与安全建议
通过上述步骤,我们完成了在 ThinkPHP5 中基于 JWT 的 Token 认证闭环。关键点在于:
1. 保持生成与验证时的配置(Issuer, Audience, Signer, Key)完全一致。
2. 使用安全的密钥(Secret Key),严禁使用弱口令。
3. 合理设置过期时间(Expiration),对于高安全性要求的服务,可配合 Refresh Token 机制使用。
4. Token 一旦签发无法在服务端主动“注销”,除非引入黑名单机制或使用较短的生命周期。
以上就是在 ThinkPHP5 中实现 JWT Token 认证的详细内容,更多关于 PHP API 安全开发的资料请关注本站其它相关文章!
免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









