xfra35 / f3-access
PHP Fat-Free 框架的路由访问控制
Requires
- bcosca/fatfree-core: 3.*
README
PHP Fat-Free 框架的路由访问控制
此插件为 Fat-Free 框架 提供了控制对路由访问的功能。
要求
此插件负责授权,而不是身份验证。所以在使用之前,请确保你有识别应用程序用户的方法。
安装
要安装此插件,只需将 lib/access.php
文件复制到您的 lib/
或 AUTOLOAD
文件夹。
基本用法
实例化插件并定义一个默认访问策略(允许 或 拒绝)
$access=Access::instance(); $access->policy('allow'); // allow access to all routes by default
然后定义一组规则来保护特定的路由
$access->deny('/secured.htm'); // globally deny access to /secured.htm $access->allow('/secured.htm','admin'); // allow "admin" to access /secured.htm
或一组路由
// globally deny access to any URI prefixed by /protected $access->deny('/protected*'); // allow "admin" to access any URI prefixed by /protected $access->allow('/protected*','admin');
然后在最合适的位置调用 authorize()
方法(在或之后 $f3->run()
)
$access->authorize($somebody); // e.g: $somebody=$f3->get('SESSION.user')
就是这样!
我们已经限制了 /secured.htm
和所有以 /protected
开头的 URI 的访问。任何未识别为 "admin" 的用户将得到一个 错误。
请注意,“admin”可以是您应用程序中任何有意义的名称:用户名、组、角色、权限等。
因此,我们可以将访问权限授予 "admin@domain.tld" 或 "admin-role" 或 "可以访问管理区域"。
因此,从现在起,我们将“admin”称为 主题。
单个规则可以针对多个主题
$access->allow('/foo','tic,tac,toe'); // csv string $access->allow('/foo',array('tic','tac','toe')); // array
注意:主题名称可以包含任何字符,但不能包含逗号。
高级用法
授权失败
如果主题被识别,拒绝访问将导致 403 错误;如果没有识别,则导致 401 错误。在我们的第一个例子中
$somebody='client'
将得到一个 403 错误(禁止访问)$somebody=''
将得到一个 401 错误(未经授权 => 用户应先进行身份验证)
您可以为 authorize()
方法提供一个回调,当授权失败时将触发此回调
$access->authorize($somebody,function($route,$subject){ echo "$subject is denied access to $route";// $route is a method followed by a path });
除非回退返回 FALSE,否则将跳过默认行为(403/401)。
HTTP 方法访问控制
路由权限可以在 HTTP 方法级别定义
$access->deny('/path'); $access->allow('GET /path'); $access->allow('POST|PATCH|PUT|DELETE /path','admin');
在此示例中,只有 "admin" 可以修改 /path
。任何其他主题只能执行 GET
。
重要:不提供 HTTP 方法相当于提供 所有 HTTP 方法(除非您正在使用命名路由,请参阅下面的说明)。
例如
// the following rules are equivalent: $access->deny('/path'); $access->deny('GET|HEAD|POST|PUT|PATCH|DELETE|CONNECT /path');
简单权限检查
如果您需要检查特定主题对路由的访问权限,请使用 granted()
方法
if ($access->granted('/admin/part1',$somebody)) { // Access granted } else { // Access denied }
此方法执行简单检查,不采取任何操作(不抛出错误)。
规则处理顺序
路径优先级
在处理之前,规则按照从最具体到最不具体的路径顺序进行排序。因此,以下规则
$access->deny('/admin*','mike'); $access->deny('/admin/blog/foo','mike'); $access->allow('/admin/blog','mike'); $access->allow('/admin/blog/foo/bar','mike'); $access->deny('/admin/blog/*/bar','mike');
按照以下顺序处理
$access->allow('/admin/blog/foo/bar','mike'); $access->deny('/admin/blog/*/bar','mike'); $access->deny('/admin/blog/foo','mike'); $access->allow('/admin/blog','mike'); $access->deny('/admin*','mike');
重要:适用第一条匹配路径的规则。如果没有规则匹配,则应用默认策略。
主题优先级
特定主题规则在全局规则之前处理。因此,以下规则
$access->allow('/part2');// rule #1 $access->deny('/part1/blog','zag');// rule #2 $access->allow('/part1','zig,zag');// rule #3
按照以下顺序处理
- 2,3,1 适用于主题 "zag"
- 3,1 适用于主题 "zig"
路由唯一性
规则按主题名称和路由索引,因此不能有针对相同主题和相同路由的两个规则。如果出现这种情况,第二个规则将覆盖第一个
$access->allow('/part1','Dina');// rule #1 $access->deny('/part1','Dina');// rule #2 $access->allow('POST /part1','Dina,Misha');// rule #3 $access->deny('/part1','Dina');// rule #4
在这个例子中
- 规则 #1 被忽略
- 规则 #3 仅对 Dina 被忽略(对 Misha 不适用)
路径不区分大小写
出于安全目的,路径被视为不区分大小写,无论框架变量 CASELESS
的值如何。
因此,以下规则是等效的
$access->deny('/restricted/area'); $access->deny('/RESTRICTED/AREA'); $access->deny('/rEsTrIcTeD/aReA');
通配符和令牌
通配符可以在各种地方使用
- 代替路由动词,表示“任何动词”:
* /path
- 等同于
/path
- 等同于
GET|HEAD|POST|PUT|PATCH|DELETE|CONNECT /path
- 等同于
- 在路由路径中,表示“任何字符”:
GET /foo/*/baz
- 代替主题,表示“任何主题”:
$f3->allow('/','*')
- 等同于
$f3->allow('/','')
- 等同于
$f3->allow('/')
- 等同于
注意:通配符匹配空字符串,因此 /admin*
匹配 /admin
。
也支持路由标记,因此 $f3->allow('/blog/@id/@slug')
被识别。
由于插件没有使用标记名称,您也可以省略它们: $f3->allow('/blog/@/@')
换句话说,@
是任何非斜线的字符的通配符,而 *
匹配包括斜线在内的任何内容。
重要:请阅读 陷阱 部分。
命名路由
如果您使用 命名路由,可以直接引用它们的别名: $f3->allow('@blog_entry')
;
在这种情况下,不提供 HTTP 方法与提供实际映射到给定路由的方法是等效的。请参阅
$f3->route('GET|POST @admin_user_edit: /admin/user/@id','Class->edit'); $f3->route('DELETE @admin_user_delete: /admin/user/@id','Class->delete'); // the following rules are equivalent: $access->deny('@admin_user_edit'); $access->deny('GET|POST @admin_user_edit'); $access->deny('GET|POST /admin/user/@id');
ini 配置
可以从 .ini 文件内部配置,使用 ACCESS
变量。
规则应以前缀 "allow" 或 "deny"(不区分大小写)开头
[ACCESS] policy = deny ;deny all routes by default [ACCESS.rules] ALLOW /foo = * ALLOW /bar* = Albert,Jean-Louis DENY /bar/baz = Jean-Louis
它也适用于 HTTP 动词
[ACCESS.rules] allow GET|POST /foo = Jim allow * /bar = Albert,Jim deny PUT /bar = Jim
实用配置示例
保护管理区域
[ACCESS.rules] allow /admin = * ; login form deny /admin/* = * allow /admin/* = superuser
保护类似 MVC 的路由
[ACCESS.rules] deny /*/edit = * deny /*/create = * allow /*/edit = superuser allow /*/create = superuser
保护类似 RMR 的路由
[ACCESS.rules]
deny * /* = *
deny GET /* = *
allow POST|PUT|PATCH|DELETE /* = superuser
保护仅限会员的网站
ACCESS.policy = deny [ACCESS.rules] allow / = * ; login form allow /* = member
陷阱
静态路由覆盖动态路由
当静态路由覆盖动态路由时要小心。
虽然不建议这样做,但以下设置可以通过框架实现
$f3->route('GET /admin/user/@id','User->edit'); $f3->route('GET /admin/user/new','User->create');
从授权的角度来看,我们可能会写
$access->deny('/admin*','*');// deny access to all admin paths by default $access->allow('/admin/user/@id','edit_role');// allow edit_role to access /admin/user/@id $access->allow('/admin/user/new','create_role');// allow create_role to access /admin/user/new
这样做,我们可能会认为 edit_role
不能访问 /admin/user/new
路径,但这是一种错觉。
实际上,@id
标记匹配任何字符串,包括 new
。
为了证实这一点,只需想象 /admin/user/@id
与 /admin/user/@anything
没有区别。
因此,为了实现角色之间的完全分离,正确的配置应该是,在这种情况下
$access->deny('/admin*','*');// deny access to all admin paths by default $access->allow('/admin/user/@id','edit_role');// allow edit_role to access /admin/user/@id. $access->deny('/admin/user/new','edit_role');// ... but not /admin/user/new $access->allow('/admin/user/new','create_role');// allow create_role to access /admin/user/new
更清晰的设置可以是
- 定义一个单独的路径
/admin/user/@id
,其中id=new
在单个控制器中处理 - 或定义两个明确的路径,例如
/admin/user/@id
和/admin/new-user
API
$access = Access::instance();
policy( $default=NULL )
获取/设置默认策略(默认='allow')
$access->policy('deny'); echo $access->policy();// 'deny'
allow( $route, $subjects='' )
允许指定的主题访问给定的路由
$access->allow('/path'); // Grant "all" access to /path $access->allow('/path',''); // idem $access->allow('/path','*'); // idem $access->allow('POST /foo','tip-top'); // Allow "tip-top" to POST /foo
deny( $route, $subjects='' )
拒绝指定的主题访问给定的路由
$access->deny('/path'); // Deny "all" access to /path $access->deny('/path',''); // idem $access->deny('/path','*'); // idem $access->deny('POST /foo','tip-top'); // Deny "tip-top" access to POST /foo
granted( $route, $subject='' )
如果给定的主题被授予访问给定路由的权限,则返回 TRUE
if ($access->granted('/admin/part1',$somebody)) { // Access granted } else { // Access denied }
注意:您还可以针对一组主题检查访问权限。这对于例如您已经实现用户角色或组系统非常有用
$access->granted('/admin/part1',array('customer')); // FALSE $access->granted('/admin/part1',array('customer','admin')); // TRUE
authorize( $subject='', $ondeny=NULL )
如果给定的主题被授予访问当前路由的权限,则返回 TRUE
如果没有提供 $subject
,则对“任何”主题执行授权。
$ondeny
应该是一个有效的 F3 回调(PHP 可调用或字符串)
$access->authorize(); // authorize "any" subject $access->authorize('admin',function($route,$subject){ echo "$subject is denied access to $route";// 'admin is denied access to GET /path' }); $access->authorize('admin','My\App->forbidden');
有关授权失败时发生情况的详细信息,请参阅此处。
注意:您还可以对一组主题执行授权。例如,如果您实现了用户角色或组系统,则只需传递要授权用户的角色/组数组。例如
$access->authorize(array('customer')); // unauthorized $access->authorize(array('customer','admin')); // authorized
潜在改进
- 考虑
HEAD
和CONNECT
:它们应该被授权还是始终允许?