xfra35/f3-access

PHP Fat-Free 框架的路由访问控制

v1.2.2 2021-04-29 16:52 UTC

This package is auto-updated.

Last update: 2024-08-29 04:25:56 UTC


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

潜在改进

  • 考虑 HEADCONNECT:它们应该被授权还是始终允许?