dakujem / auth-middleware
高度灵活的PSR-15认证中间件。
Requires
- php: ^8.0
- dakujem/generic-middleware: ^1
- psr/http-factory: ^1.0
- psr/http-server-middleware: ^1.0
- psr/log: ^1.0
Requires (Dev)
- ext-json: *
- firebase/php-jwt: ^6.0|^5.5
- nette/tester: ^2.4.1
- slim/psr7: ^1.2
- slim/slim: ^4.5
- tracy/tracy: ^2.3
README
现代且高度灵活的PSR-15认证和授权中间件。
💿
composer require dakujem/auth-middleware
📒 变更日志
默认使用方法
要使用此包,您需要创建两个中间件层
- 一个解码JWT的中间件,该JWT存在于请求中并验证其真实性
- 以及一个通过断言解码令牌存在的中间件来授权请求
使用Dakujem\Middleware\AuthWizard
方便起见
/* @var Slim\App $app */ $app->add(AuthWizard::assertTokens($app->getResponseFactory())); $app->add(AuthWizard::decodeTokens(new Secret('a-secret-api-key-never-to-commit', 'HS256')));
这对中间件(MW)将在Authorization
头部或token
cookie中查找JWT。
然后它将解码JWT并将解码后的有效负载注入到可由应用程序访问的token
请求属性中。
如果令牌不存在或无效,断言中间件将终止执行流程并返回401 Unauthorized
响应。
可以通过请求属性访问令牌
/* @var Request $request */ $decodedToken = $request->getAttribute('token');
您可以选择只对选定的路由应用断言,而不是对每个路由应用断言
$mwFactory = AuthWizard::factory(new Secret('a-secret-api-key-never-to-commit', 'HS256'), $app->getResponseFactory()); // Decode the token for all routes, $app->add($mwFactory->decodeTokens()); // but only apply the assertion to selected ones. $app->group('/foo', ...)->add($mwFactory->assertTokens());
也可以应用自定义的令牌检查
$app->group('/admin', ...)->add(AuthWizard::inspectTokens( $app->getResponseFactory(), function(MyToken $token, $next, $withError): Response { return $token->grantsAdminAccess() ? $next() : $withError('Admin privilege required!'); } ));
💡
有关实例化中间件的灵活选项,请参阅下面的"自定义中间件"章节。
上面的示例使用Slim PHP框架,但相同的用法适用于任何PSR-15兼容的中间件分发器。
提取和解码JWT
AuthWizard::decodeTokens(__ // a combination of secret and the encryption algorithm used new Secret('a-secret-api-key-never-to-commit', 'HS256'), 'token', // what attribute to put the decoded token to 'Authorization', // what header to look for the Bearer token in 'token', // what cookie to look for the raw token in 'token.error' // what attribute to write error messages to );
这创建了一个使用默认JWT解码器并将解码的令牌注入到应用程序堆栈中可进一步访问的token
请求属性的TokenMiddleware
实例。
如果解码的令牌出现在属性中,它就是
- 存在(显然)
- 真实的(已使用密钥创建)
- 有效的(未过期)
授权
上面的中间件将仅解码(如果存在)真实且有效的令牌,但不会在任何情况下终止管道❗
授权必须由单独的中间件完成
AuthWizard::assertTokens( $responseFactory, // PSR-17 Request factory 'token', // what attribute to look for the decoded token in 'token.error' // what attribute to look for error messages in );
这创建了一个将断言请求的token
属性包含解码令牌的中间件。
否则,将终止管道并返回401(未授权)响应。错误消息将被编码为JSON放入响应中。
如你所见,这两个中间件像一对夫妻一样工作,但为了灵活性而解耦。
由AuthWizard::assertTokens
创建的中间件仅断言解码令牌的存在。
当然可以创建自定义检查
$inspector = function (object $token, callable $next, callable $withError): Response { if ($token->sub === 42) { // Implement your inspection logic here. return $next(); // Invoke the next middleware for valid tokens } // or return $withError('Bad token.'); // return an error response for invalid ones. }; AuthWizard::inspectTokens( $responseFactory, // PSR-17 Request factory $inspector, 'token', // what attribute to look for the decoded token in 'token.error' // what attribute to look for error messages in );
使用AuthWizard::inspectTokens
,可以在涉及令牌的任何条件下终止管道。
可以将自定义错误消息或数据传递给响应。
如果令牌不存在,中间件的行为与由assertTokens
创建的中间件相同,并且检查器不会被调用。
您当然可以将令牌转换为自定义类,例如使用 MyToken::grantsAdminAccess
方法来判断令牌是否授权用户进行管理员访问。
AuthWizard::inspectTokens( $responseFactory, function(MyToken $token, $next, $withError): Response { return $token->grantsAdminAccess() ? $next() : $withError('Admin privilege required!'); } );
这种转换可以在解码器中完成,也可以在单独的中介中完成。
编写您自己的中介
在上面的示例中,我们使用了 AuthWizard
辅助工具,它提供了合理的默认设置。
然而,使用此包提供的组件自行构建中介是可能的,也是受到鼓励的。
您可以根据任何用例对中介进行微调。
在本文档中,为了简洁起见,我使用的是别名而不是完整的接口名称。
以下是完整的接口名称
TokenMiddleware
TokenMiddleware
负责查找和解码令牌,并将其提供给应用的其他部分。
TokenMiddleware
由以下部分组成
- 一组 提取器
- 提取器负责从请求中查找和提取令牌,或者返回
null
- 按顺序执行,直到其中一个返回字符串
fn(Request,Logger):?string
- 提取器负责从请求中查找和提取令牌,或者返回
- 一个 解码器
- 解码器接受原始令牌并将其解码
- 必须只返回有效的令牌对象或
null
fn(string,Logger):?object
- 一个 注入器
- 注入器负责将解码的令牌或错误消息装饰到请求中
- 通过运行传递给其第一个参数的可调用函数来获取解码的令牌,该参数是
fn():?object
fn(callable,Request,Logger):Request
这些可调用组件中的任何一个都可以被替换或扩展。
默认组件也提供了自定义功能。
以下是 AuthWizard::decodeTokens
提供的默认值
new TokenMiddleware( // decode JWT tokens new FirebaseJwtDecoder('a-secret-never-to-commit', ['HS256', 'HS512', 'HS384']), [ // look for the tokens in the `Authorization` header TokenManipulators::headerExtractor('Authorization'), // look for the tokens in the `token` cookie TokenManipulators::cookieExtractor('token'), ], // target the `token` and `token.error` attributes for writing the decoded token or error message TokenManipulators::attributeInjector('token', 'token.error') );
使用提示 💡
- 可以通过交换解码器来使用 OAuth 令牌 或不同的 JWT 实现。
- 可以通过将提供程序的可调用函数包装在 try-catch 块中来捕获和处理异常
try { $token = $provider(); } catch (RuntimeException $e) { ...
- 解码器可以返回任何对象,这是将原始有效载荷转换为所选对象的地方。或者,也可以使用单独的中介来完成此目的。
AuthWizard
, AuthFactory
AuthWizard
是一个摩擦减少器,它可以帮助快速实例化带有合理默认设置的令牌解码和断言中介。
AuthFactory
是一个可配置的工厂,为了方便提供了合理的默认设置。
AuthWizard
在内部实例化 AuthFactory
并作为工厂的静态外观。
使用 AuthFactory::decodeTokens
来创建令牌解码中介。
使用 AuthFactory::assertTokens
来创建断言存在解码令牌的中介。
使用 AuthFactory::inspectTokens
来创建针对令牌的具有自定义授权规则的中介。
GenericMiddleware
GenericMiddleware
被用于 AuthWizard
/ AuthFactory
的令牌存在性和自定义授权。
它也可以用于方便的嵌入式中介实现。
$app->add(new GenericMiddleware(function(Request $request, Handler $next): Response { $request = $request->withAttribute('foo', 42); $response = $next->handle($request); return $response->withHeader('foo', 'bar'); }));
TokenManipulators
TokenManipulators
静态类提供了各种请求/响应操纵器,可用于令牌处理。
它们用作中介的组件。
FirebaseJwtDecoder
FirebaseJwtDecoder
类作为 JWT 令牌解码的默认实现。
它用作TokenMiddleware
的解码器。
您可以将其替换为不同的实现。
为了使用此解码器,您需要安装Firebase JWT包。
composer require firebase/php-jwt:"^5.5"
日志记录器
TokenMiddleware
接受一个PSR-3 Logger
实例以用于调试目的。
提示
多个令牌解码和令牌检查中间件也可以堆叠!
通常将令牌解码应用于应用级别的中间件(每个路由),但断言可以根据需要组合并应用于组或单个路由。
测试
使用以下命令运行单元测试
$
composer test
兼容性
为了使用FirebaseJwtDecoder
解码器,必须安装正确的firebase/php-jwt
版本。尽管如此,使用此解码器并非必需。
贡献
欢迎提出想法、功能请求和其他贡献。请发送PR或创建一个issue。
安全问题
如果您偶然发现一个安全问题,请在不透露任何相关信息的情况下创建一个issue,我们将私下联系并讨论细节。