elbanyaoui/slim-jwt-auth

PSR-7 JWT 认证中间件,由 elbanyaoui 基于tuupola/slim-jwt-auth项目创建的定制版本

3.0.0-rc.2 2017-07-17 19:23 UTC

README

Latest Version Packagist Software License Build Status Coverage

此中间件实现了 JSON Web Token 认证。它最初是为 Slim 框架开发的,但也可以与任何使用 PSR-7 风格中间件的框架一起使用。它已经与 Slim 框架Zend Expressive 进行了测试。

中间件实现 OAuth 2.0 授权服务器,也不提供生成、颁发或存储认证令牌的方式。它仅当令牌通过头部或 cookie 传递时解析和验证令牌。例如,当您想使用 JSON Web Tokens 作为 API 密钥 时,这很有用。

例如实现请参见 Slim API 骨架

安装

使用 composer 安装最新版本。

$ composer require elbanyaoui/slim-jwt-auth

如果您使用 Apache,请将以下内容添加到 .htaccess 文件中。否则,PHP 无法访问 Authorization: Bearer 头部。

RewriteRule .* - [env=HTTP_AUTHORIZATION:%{HTTP:Authorization}]

用法

配置选项作为数组传递。唯一必需的参数是 secret,它用于验证令牌签名。请注意,secret 不是令牌。它是用于签名令牌的秘密。

为了简化,示例显示了硬编码在代码中的 secret。在实际生活中,您应该将其存储在其他地方。一个好的选择是环境变量。您可以使用 dotenv 或类似工具进行开发。示例假设您正在使用 Slim 框架。

$app = new \Slim\App();

$app->add(new \Slim\Middleware\JwtAuthentication([
    "secret" => "supersecretkeyyoushouldnotcommittogithub"
]));

以下是一个示例,其中您的秘密存储为环境变量

$app = new \Slim\App();

$app->add(new \Slim\Middleware\JwtAuthentication([
    "secret" => getenv("JWT_SECRET")
]));

当发起请求时,中间件会尝试验证和解析令牌。如果找不到令牌或验证和解析令牌时出现错误,服务器将以 401 未授权 响应。

当令牌被篡改或令牌已过期时,会触发验证错误。有关所有可能的验证错误,请参阅 JWT 库 源代码。

可选参数

路径

可选的 path 参数允许您指定网站受保护的部分。它可以是字符串或数组。您无需指定每个 URL。相反,将 path 设置视为文件夹。在下面的示例中,以 /api 开头的所有内容都将被认证。

$app = new \Slim\App();

$app->add(new \Slim\Middleware\JwtAuthentication([
    "path" => "/api", /* or ["/api", "/admin"] */
    "secret" => "supersecretkeyyoushouldnotcommittogithub"
]));

透传

使用可选的 passthrough 参数,您可以针对 path 参数进行例外处理。在下面的示例中,以 /api/admin 开头的所有内容都将被认证,但 /api/token/admin/ping 将不会被认证。

$app = new \Slim\App();

$app->add(new \Slim\Middleware\JwtAuthentication([
    "path" => ["/api", "/admin"],
    "passthrough" => ["/api/token", "/admin/ping"],
    "secret" => "supersecretkeyyoushouldnotcommittogithub"
]));

通过请求和方法透传

$app = new \Slim\App();

$app->add(new \Slim\Middleware\JwtAuthentication([
    "path" => ["/api", "/admin"],
    "passthrough" => ["/api/token", "/admin/ping"],
    "secret" => "supersecretkeyyoushouldnotcommittogithub",
	"rules" => [
        new\Slim\Middleware\JwtAuthentication\RequestMethodRule([
            "passthrough" => ["OPTIONS"]
        ]),
        new\Slim\Middleware\JwtAuthentication\RequestMethodPathRule([
            "passthrough" => [
                "GET"=>"/items/"
            ]
        ]),
        new\Slim\Middleware\JwtAuthentication\RequestMethodPathRule([
            "passthrough" => [
                "GET"=>"/categories/"
            ]
        ])
    ],
]));

环境

默认情况下,中间件尝试从 HTTP_AUTHORIZATIONREDIRECT_HTTP_AUTHORIZATION 环境中查找令牌。您可以使用 environment 参数进行更改。请注意,通常您还应该使用匹配的 header 参数。

$app = new \Slim\App();

$app->add(new \Slim\Middleware\JwtAuthentication([
    "environment" => ["HTTP_BRAWNDO", "REDIRECT_HTTP_BRAWNDO"],
    "header" => "Brawndo",
    "secret" => "supersecretkeyyoushouldnotcommittogithub"
]));

头部

如果从环境中找不到令牌,中间件会尝试从 Authorization 头部中查找令牌。您可以使用 header 参数更改 cookie 名称。请注意,通常您还应该使用匹配的 environment 参数。

$app = new \Slim\App();

$app->add(new \Slim\Middleware\JwtAuthentication([
    "environment" => "HTTP_X_TOKEN",
    "header" => "X-Token",
    "secret" => "supersecretkeyyoushouldnotcommittogithub"
]));

cookie

如果既没有从环境或头部中找到令牌,中间件会尝试从名为 token 的 cookie 中找到它。您可以使用 cookie 参数更改 cookie 名称。

$app = new \Slim\App();

$app->add(new \Slim\Middleware\JwtAuthentication([
    "cookie" => "nekot",
    "secret" => "supersecretkeyyoushouldnotcommittogithub"
]));

正则表达式

默认情况下,中间件假设头部的值是 Bearer <token> 格式。您可以使用 regexp 参数更改此行为。例如,如果您有自定义头,例如 X-Token: <token>,则应传递头和正则表达式参数。

$app = new \Slim\App();

$app->add(new \Slim\Middleware\JwtAuthentication([
    "header" => "X-Token",
    "regexp" => "/(.*)/",
    "secret" => "supersecretkeyyoushouldnotcommittogithub"
]));

算法

您可以通过 algorithm 参数设置支持算法。这可以是字符串或字符串数组。默认值为 ["HS256", "HS512", "HS384"]。支持的算法有 HS256HS384HS512RS256。请注意,启用 HS256RS256 同时存在是一个 安全风险

$app = new \Slim\App();

$app->add(new \Slim\Middleware\JwtAuthentication([
    "secret" => "supersecretkeyyoushouldnotcommittogithub",
    "algorithm" => ["HS256", "HS384"]
]));

属性

当令牌解码成功且认证成功时,解码令牌的内容将保存为 token 属性到 $request 对象中。您可以使用 attribute 参数更改此设置。设置为 nullfalse 以禁用此行为

$app = new \Slim\App();

$app->add(new \Slim\Middleware\JwtAuthentication([
    "attribute" => "jwt",
    "secret" => "supersecretkeyyoushouldnotcommittogithub"
]));

/* ... */

$decoded = $request->getAttribute("jwt");

日志记录器

可选的 logger 参数允许您传递一个 PSR-3 兼容的日志记录器,以帮助调试或其他应用程序日志记录需求。

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

$app = new \Slim\App();

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

$app->add(new \Slim\Middleware\JwtAuthentication([
    "path" => "/api",
    "logger" => $logger,
    "secret" => "supersecretkeyyoushouldnotcommittogithub"
]));

回调

回调仅在认证成功时调用。它接收解码令牌作为参数。如果回调返回布尔值 false,则强制认证失败。

您还可以使用回调来存储解码令牌的值以供以后使用。

$app = new \Slim\App();

$container = $app->getContainer();

$container["jwt"] = function ($container) {
    return new StdClass;
};

$app->add(new \Slim\Middleware\JwtAuthentication([
    "secret" => "supersecretkeyyoushouldnotcommittogithub",
    "callback" => function ($request, $response, $arguments) use ($container) {
        $container["jwt"] = $arguments["decoded"];
    }
]));

错误

当认证失败时调用错误。它接收最后的错误信息作为参数。

$app = new \Slim\App();

$app->add(new \Slim\Middleware\JwtAuthentication([
    "secret" => "supersecretkeyyoushouldnotcommittogithub",
    "error" => function ($request, $response, $arguments) {
        $data["status"] = "error";
        $data["message"] = $arguments["message"];
        return $response
            ->withHeader("Content-Type", "application/json")
            ->write(json_encode($data, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT));
    }
]));

规则

可选的 rules 参数允许您传递定义请求是否需要认证的规则。规则是一个可调用的,它接收请求作为参数。如果任何规则返回布尔值 false,则请求将不会被认证。

默认情况下,中间件配置如下。所有路径都使用除 OPTIONS 以外的所有请求方法进行认证。

$app = new \Slim\App();

$app->add(new \Slim\Middleware\JwtAuthentication([
    "rules" => [
        new \Slim\Middleware\JwtAuthentication\RequestPathRule([
            "path" => "/",
            "passthrough" => []
        ]),
        new \Slim\Middleware\JwtAuthentication\RequestMethodRule([
            "passthrough" => ["OPTIONS"]
        ])
    ]
]));

RequestPathRule 包含一个 path 参数和一个 passthrough 参数。后者包含不需要认证的路径。RequestMethodRule 包含不应认证的请求方法的 passthrough 参数。将 passthrough 视为白名单。

此用例的示例是一个 API。令牌可以通过 HTTP Basic Auth 受保护的地址检索。还有一个未受保护的 URL 用于 ping。其余的 API 由 JWT 中间件保护。

$app = new \Slim\App();

$app->add(new \Slim\Middleware\JwtAuthentication([
    "logger" => $logger,
    "secret" => "supersecretkeyyoushouldnotcommittogithub",
    "rules" => [
        new RequestPathRule([
            "path" => "/api",
            "passthrough" => ["/api/token", "/api/ping"]
        ]),
        new \Slim\Middleware\JwtAuthentication\RequestMethodRule([
            "passthrough" => ["OPTIONS"]
        ])
    ]
]));

$app->add(new \Slim\Middleware\HttpBasicAuthentication([
    "path" => "/api/token",
    "users" => [
        "user" => "password"
    ]
]));

$app->post("/token", function () {
  /* Here generate and return JWT to the client. */
});

安全性

JSON Web Tokens 实质上是密码。您应将其视为密码,并且始终使用 HTTPS。如果中间件检测到 HTTP 上的不安全使用,则将抛出 RuntimeException。对于本地主机的请求,此规则较宽松。要允许不安全使用,您必须手动将其设置为 false

$app->add(new \Slim\Middleware\JwtAuthentication([
    "secure" => false,
    "secret" => "supersecretkeyyoushouldnotcommittogithub"
]));

或者,您可以将您的开发主机列入白名单以具有较宽松的安全设置。

$app->add(new \Slim\Middleware\JwtAuthentication([
    "secure" => true,
    "relaxed" => ["localhost", "dev.example.com"],
    "secret" => "supersecretkeyyoushouldnotcommittogithub"
]));

授权

默认情况下,中间件仅进行认证。这并不很有趣。JWT 的美妙之处在于您可以在令牌中传递额外数据。此数据可以包括范围,可用于授权。

您需要实现如何存储令牌数据或可能的授权实现。

假设您有一个包含范围数据的令牌。在中间件回调中,您将解码的令牌数据存储到 $app->jwt 中,然后稍后用于授权。

[
    "iat" => "1428819941",
    "exp" => "1744352741",
    "scope" => ["read", "write", "delete"]
]
$app = new \Slim\App();

$container = $app->getContainer();

$container["jwt"] = function ($container) {
    return new StdClass;
};

$app->add(new \Slim\Middleware\JwtAuthentication([
    "secret" => "supersecretkeyyoushouldnotcommittogithub",
    "callback" => function ($request, $response, $arguments) use ($container) {
        $container["jwt"] = $arguments["decoded"];
    }
]));

$app->delete("/item/{id}", function ($request, $response, $arguments) {
    if (in_array("delete", $this->jwt->scope)) {
        /* Code for deleting item */
    } else {
        /* No scope so respond with 401 Unauthorized */
        return $response->withStatus(401);
    }
});

测试

您可以手动运行单个测试...

$ composer phplint
$ composer phpcs
$ composer phpunit

... 或者在每个代码更改时自动运行。为此,您需要 entr

$ composer watch

贡献

有关详细信息,请参阅 CONTRIBUTING

安全性

如果您发现任何与安全相关的问题,请通过电子邮件 tuupola@appelsiini.net 反馈,而不是使用问题跟踪器。

许可

麻省理工学院许可证(MIT)。请参阅许可证文件以获取更多信息。