JSON Web Tokens 在 Slim 框架中的运用

2,117 阅读2分钟
原文链接: github.com

  如果你还不知道 JSON Web Token(简称 JWT)是什么,有什么用途,常见的应用场景是什么,基本原理是什么,请先阅读 JSON Web Tokens 是什么? 学习相关知识。

  在 Slim 中,使用中间件引入 JWT 机制是最合适的。我们这里使用的是 Slim-jwt-auth 项目,这个项目的基础是 php-jwt 项目,有兴趣可以研究下这两个项目的代码。

配置中间件

  安装 Slim-jwt-auth 后在 Slim 中按如下方式引入

/*
 * Slim JWT 身份验证中间件
 * https://github.com/tuupola/slim-jwt-auth
 *
 * 参数说明
 *
 * cookie      :使用 cookie 存储 Token,此参数设置 cookie 的名称
 * secure      :是否使用 HTTPS 验证
 * relaxed     :不进行 HTTPS 验证的域名
 * secret      :密钥
 * path        : 需要进行身份验证的路径,设置为 '/' 表示验证全部的路径
 * passthrough :不需要进行身份验证的路径
 * attribute   :在 Request 中的标志,可通过 $request->getAttribute('') 读取
 * environment :
 * header      :在 header 中的标志,默认是 Authorization,配合 environment 设置一起使用
 * error       : 未能通过验证时的回调函数
 * callback    : 通过身份验证时的回调函数
 */
$app->add(
    new \Slim\Middleware\JwtAuthentication(
        [
          "attribute" => "JWT_Auth",
               "path" => "/v1/user",
        "passthrough" => "/token",
        "environment" => ["HTTP_JWT_AUTH", "REDIRECT_HTTP_JWT_AUTH"],
             "header" => "JWT_Auth",
             "secret" => getenv("JWT_SECRET"),
             "secure" => false,
              "error" => function ($request, $response, $args) use ($container) {
                    $json['code'] = -1;
                    $json['note'] = $args['message'];
                    $json['help'] = 'http://api.app.com';
                    
                    $container->log->write($args['message']);
                    
                    return $response->withStatus(401)
                                    ->withHeader("Content-Type", "application/json")
                                    ->write(json_encode($json, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT));
            },
            "callback" => function ($request, $response, $args) use ($container) {
                    $container->log->write($args);
            }
        ]
    )
);

  主要就是要理解这里面的各种设置项,可仔细阅读 Slim-jwt-auth 的说明文档。我这里使用的自己写的日志类库来记录日志,也可以参考官方的做法,使用 Monolog\Logger 来实现。

use Monolog\Logger;
use Monolog\Handler\RotatingFileHandler;

$logger   = new Logger("slim");
$rotating = new RotatingFileHandler(__DIR__ . "/logs/slim.log", 0, Logger::DEBUG);
$logger->pushHandler($rotating);

$app->add(
    new \Slim\Middleware\JwtAuthentication(
        [
          "attribute" => "JWT_Auth",
               "path" => "/v1/user",
        "passthrough" => "/token",
             "logger" => $logger
        ]
    )
);

  当身份验证未通过时会自动将错误信息写入日志文件,也就是错误回调中参数中的 message 中的内容。

生成 Token

  当用户提供 username 和 password 并通过验证后,服务器端需要为其生成一个 JSON Web Token,并返回给客户端,客户端将其存在本地。

  无论何时想要访问一个受保护的路由或资源,客户端应该随请求一起发送 JSON Web Token。JSON Web Token 通常在 Authentication HTTP 头部中,使用 Bearer 格式(schema)存放,HTTP 头部的内容通常是这样的格式:

Authorization: Bearer <token>

  下面是在 Slim 中生成 JSON Web Token 的例子:

public function index($request, $response, $args)
{
    $data = array(
        "iss" => "https://auth.medlande.com/v1", //签发者
        "sub" => "Auth",       // 主题
        "aud" => '1',          // 受众
        "iat" => time(),       // 签发时间
        "nbf" => time(),       // 启用时间
        "exp" => time() + 3600 // 过期时间
    );

    $token = JWT::encode($data, getenv("JWT_SECRET"));

    $json['code'] = 0;
    $json['note'] = 'Success.';
    $json['data'] = array('token' => $token);
    $json['help'] = 'http://api.hbdx.com';

    // setcookie("JWT_Auth", $token, time() + 7200);
    
    // 前台得到 Token 后,可以写入 cookie,或者 HTTP HEADER,配合 middleware 的设置来做
    return $response->withStatus(200)
        ->withHeader("Content-Type", "application/json")
        ->write(json_encode($json, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT));
}

  输出的结果为:

{
    code: 0,
    note: "Success.",
    data: {
        token: "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwczpcL1wvYXV0aC5tZWRsYW5kZS5jb21cL3YxIiwic3ViIjoiQXV0aCIsImF1ZCI6IjEiLCJpYXQiOjE1MDI0MzY5ODAsIm5iZiI6MTUwMjQzNjk4MCwiZXhwIjoxNTAyNDQwNTgwfQ.FboOTNextlqK3-7LHyje6-QmlkqhjA-EYnSj-wsE4EM"
    },
    help: "http://api.hbdx.com"
}

客户端请求

  我这里使用 PHP 来做模拟请求,使用的是 Guzzle Http 请求库,代码如下:

$server = 'http://api.hbdx.cc/v1';

$client = new \GuzzleHttp\Client();
$res    = $client->request('GET', $server . '/token');
$json   = $res->getBody();
$data   = json_decode($json);
$token  = $data->data->token;

// var_dump($token);

$res    = $client->request('GET', $server . '/user/1', [
    'headers' => [
        'JWT_Auth' => 'Bearer ' . $token
    ]
]);

$json   = $res->getBody();

echo $json;

  首先,我们通过 token 接口向服务器端请求了一个 JSON Web Token,在实际应用中就是登录接口。user 接口是一个受保护资源,需要身份验证,我们在 HTTP Header 中按照 JSON Web Token 要求的格式添加 Token。注意,这里使用的是 JWT_Auth 字段,这是因为我们在中间件的配置中进行了设置:

"environment" => ["HTTP_JWT_AUTH", "REDIRECT_HTTP_JWT_AUTH"],
     "header" => "JWT_Auth"

  默认使用 Authorization 就可以了。

解析 Payload

  请求通过 JSON Web Token 验证后,在 Slim 中我们可以通过

$auth = $request->getAttribute("JWT_Auth");

  来获取 Payload 信息,也就是在生成 JSON Web Token 时设置的内容。JWT_Auth 是在中间件中设置的 attribute 属性,从中取出用户标识,取出用户数据,返回给客户端。

  结束。