elbanyaoui / slim-jwt-auth
PSR-7 JWT 认证中间件,由 elbanyaoui 基于tuupola/slim-jwt-auth项目创建的定制版本
Requires
- php: ^5.5 || ^7.0
- firebase/php-jwt: ^3.0 || ^4.0 || ^5.0
- http-interop/http-factory: ^0.3.0
- http-interop/http-middleware: ^0.4.1
- psr/http-message: ^1.0
- psr/log: ^1.0
- tuupola/http-factory: ^0.3.0
Requires (Dev)
- equip/dispatch: ^0.4.0
- phpunit/phpunit: ^4.6
- squizlabs/php_codesniffer: ^2.3
- zendframework/zend-diactoros: ^1.3
README
此中间件实现了 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_AUTHORIZATION
和 REDIRECT_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"]
。支持的算法有 HS256
、HS384
、HS512
和 RS256
。请注意,启用 HS256
和 RS256
同时存在是一个 安全风险。
$app = new \Slim\App(); $app->add(new \Slim\Middleware\JwtAuthentication([ "secret" => "supersecretkeyyoushouldnotcommittogithub", "algorithm" => ["HS256", "HS384"] ]));
属性
当令牌解码成功且认证成功时,解码令牌的内容将保存为 token
属性到 $request
对象中。您可以使用 attribute
参数更改此设置。设置为 null
或 false
以禁用此行为
$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)。请参阅许可证文件以获取更多信息。