关于acl:PingCode-Wiki-权限设计之ACL

> 本文由PingCode@阿杰分享 2021年 Wiki 退出了很多强硬的个性,其中包含协同编辑 、页面权限、表情符号 等,这些性能给用户带来了更好的体验。作为 Wiki 使用者兼开发者,今日来聊聊年初上线的页面权限,同时总结一下开发阶段波及到的技术、遇到的问题以及解决方案,对于权限自己之前曾经写过一篇 Worktile 权限的文章了,Worktile 权限着重讲了 RBAC(基于角色的权限管制计划)的设计与实现,本文基于 Wiki 页面权限抉择的另一个支流权限设计的计划:ACL。 本文大抵分为三局部: ACL 介绍介绍咱们的权限以及为什么抉择它设计实现一、ACL 介绍1. 什么是 ACL?ACL:Access Control List,权限管制列表,是对文件以及目录的权限管制计划。赫赫有名的 Linux 权限零碎,它就是 ACL 的典型案例,自己在开发过程中也受到了 Linux 权限设计的一些启发。 2. ACL的应用场景应用场景也能够换个问法:为什么要应用 ACL ?对于这个问题咱们还以 Linux 作为案例:Linux 自身只提供了Owner(所有者)、Group(用户组)、Others(其余成员),也就是说其余成员或用户组是无奈指定更细粒度的权限。 为了更好的解释,咱们来举个简略的例子(场景): 有 4 个成员有 A、B、C、D,其中 A、B、C 是开发组G的成员,A成员创立了一个代码仓库并把团队开发的代码搁置到该目录中,其中这些代码次要是对于G组的,与其余成员无关,所以A把文件目录设置了权限,权限是组内可读可写,其他人没有任何权限。 当初来剖析一下各个角色,A 是仓库的 Owner,G 是 Group(含B、C),D 是与该文件无关的成员,所以是 Others。当初入职了一个用户E,因为E是新人,所以不想让E去操作代码,只容许他查看相熟代码。 面对这种场景,试想一下如何给 E 成员设置对应权限呢?答案是 oh no,因为 E 既不能依照 G 组权限,也不能依照 Others 权限,更不能是 Owner!所以面对这种鸡肋的权限,ACL 就作为了其补充,ACL 能够反对针对某一个用户或某个用户组做独立的权限,完满解决了相似场景。 二、PingCode Wiki 权限架构OK,理解了什么是 ACL 以及应用场景,咱们来聊一下 Wiki 的权限架构,根本架构见下图。 ...

February 10, 2022 · 2 min · jiezi

Parse-Server-快速实现-Serverless-的利器

原文地址 近年来NODEJS发展迅速,很多工程师尤其是前端工程师,用NODEJS来开发一些后端应用。同时,研发效率和研发成本成为开发者关注的重点,对于一些基础常用功能,如何避免重复开发,成为大家关注的重点,而 Parse Server 就是抽象了常用功能的NODEJS开源项目。 首先,从整体上看看 Parse Server 提供了哪些基础功能: 用户的登录注册用户身份的认证数据存储 && 灵活查询文件存储实时查询消息推送缓存服务与云平台很好的对接自定义业务逻辑与Hook机制服务快速搭建默认情况下,Parse Server 使用的默认数据库是 MonogDB,所以需要提前安装该数据库。关于数据库的安装与使用不是本文的重点,暂且跳过。 const config = require('./config');const app = express();var api = new ParseServer({ databaseURI: config.databaseURI, cloud: './cloud/main.js', appId: config.appId, masterKey: config.masterKey, // push: { ... }, // See the Push wiki page // filesAdapter: ..., // 对应不同云厂商的 FilesAdapter // javascriptKey: config.javascriptKey, // js客户端认证 liveQuery: { // 建立websocket链接,实现实时通信 classNames: ['Sdtuent'] }});var dashboard = new ParseDashboard({ "apps": [ { "serverURL": "http://localhost:1337/parse", "appId": config.appId, "masterKey": config.masterKey, "appName": "test" }, ]});// Serve the Parse API at /parse URL prefixapp.use('/parse', api);app.use('/dashboard', dashboard);const port = 1337;const httpServer = http.createServer(app);httpServer.listen(port, function() { console.log('parse-server-example running on port ' + port + '.');});var parseLiveQueryServer = ParseServer.createLiveQueryServer(httpServer);通过上述少量的代码,快速完成服务的搭建,/parse 是API的前缀,/dashboard 是后台的页面路由前缀,这样就可以快速使用 Parse Server 提供的各种功能。 ...

July 5, 2019 · 3 min · jiezi

Swoft 系列教程:(2)认证服务及组件

Swoft 提供了一整套认证服务组件,基本做到了配置后开箱即用。用户只需根据自身业务实现相应的登录认证逻辑,框架认证组件会调用你的登录业务进行token的签发,而后的请求中token解析、合法性验证也都由框架提供,同时框架开放了token权限认证接口给用户,我们需根据自身业务实现token对当前访问资源权限的认证。下面我们详细讲一下 jwt 的签发及验证、访问控制的流程。token 签发token 签发的基本流程为请求用户登录认证服务,认证通过则签发token。Swoft 的认证组件为我们完成了token签发工作,同时 Swoft 约定了一个Swoft\Auth\Mapping\AuthManagerInterface::login方法作为用户的认证业务的入口。使用到的组件及服务:#认证组件服务,使用此接口类名作为服务名注册到框架服务中Swoft\Auth\Mapping\AuthManagerInterface::class#框架认证组件的具体实现者 token 的签发、合法校验、解析Swoft\Auth\AuthManager#token的会话载体 存储着token的信息Swoft\Auth\Bean\AuthSession#约定用户的认证业务需实现返回Swoft\Auth\Bean\AuthResult的login方法和bool的authenticate的方法Swoft\Auth\Mapping\AccountTypeInterface#用于签发token的必要数据载体 iss/sub/iat/exp/data 传递给 Swoft\Auth\AuthManager 签发 tokenSwoft\Auth\Bean\AuthResult配置项:config/properties/app.php设定auth模式jwtreturn [ … ‘auth’ => [ ‘jwt’ => [ ‘algorithm’ => ‘HS256’, ‘secret’ => ‘big_cat’ ], ] …];config/beans/base.php为\Swoft\Auth\Mapping\AuthManagerInterface::class服务绑定具体的服务提供者return [ ‘serverDispatcher’ => [ ‘middlewares’ => [ … ], … ], // token签发及合法性验证服务 \Swoft\Auth\Mapping\AuthManagerInterface::class => [ ‘class’ => \App\Services\AuthManagerService::class ],];App\Models\Logic\AuthLogic实现用户业务的认证,以 Swoft\Auth\Mapping\AccountTypeInterface 接口的约定实现了 login/authenticate方法。login方法返回Swoft\Auth\Bean\AuthResult对象,存储用于jwt签发的凭证:setIdentity 对应 sub,即jwt的签发对象,一般使用uid即可setExtendedData 对应 payload, 即jwt的载荷,存储一些非敏感信息即可authenticate方法签发时用不到,主要在验证请求的token合法性时用到,即检测jwt的sub是否为本平台合法用户<?phpnamespace App\Models\Logic;use Swoft\Auth\Bean\AuthResult;use Swoft\Auth\Mapping\AccountTypeInterface;class AuthLogic implements AccountTypeInterface{ /** * 用户登录认证 需返回 AuthResult 对象 * 返回 Swoft\Auth\Bean\AuthResult 对象 * @override Swoft\Auth\Mapping\AccountTypeInterface * @param array $data * @return AuthResult / public function login(array $data): AuthResult { $account = $data[‘account’]; $password = $data[‘password’]; $user = $this->userDao->getByConditions([‘account’ => $account]); $authResult = new AuthResult(); // 用户验证成功则签发token if ($user instanceof User && $this->userDao->verifyPassword($user, $password)) { // authResult 主标识 对应 jwt 中的 sub 字段 $authResult->setIdentity($user->getId()); // authResult 附加数据 jwt 的 payload $authResult->setExtendedData([self::ID => $user->getId()]); } return $authResult; } /* * 验证签发对象是否合法 这里我们简单验证签发对象是否为本平台用户 * $identity 即 jwt 的 sub 字段 * @override Swoft\Auth\Mapping\AccountTypeInterface * @param string $identity token sub 字段 * @return bool / public function authenticate(string $identity): bool { return $this->userDao->exists($identity); }}Swoft\Auth\AuthManager::login 要求传入用户业务的认证类,及相应的认证字段,根据返回Swoft\Auth\Bean\AuthResult对象判断登录认证是否成功,成功则签发token,返回Swoft\Auth\Bean\AuthSession对象。App\Services\AuthManagerService用户认证管理服务,继承框架Swoft\Auth\AuthManager做定制扩展。比如我们这里实现一个auth方法供登录请求调用,auth 方法中则传递用户业务认证模块来验证和签发token,获取token会话数据。<?php/* * 用户认证服务 * User: big_cat * Date: 2018/12/17 0017 * Time: 16:36 /namespace App\Services;use App\Models\Logic\AuthLogic;use Swoft\Redis\Redis;use Swoft\Bean\Annotation\Bean;use Swoft\Bean\Annotation\Inject;use Swoft\Auth\AuthManager;use Swoft\Auth\Bean\AuthSession;use Swoft\Auth\Mapping\AuthManagerInterface;/* * @Bean() * @package App\Services /class AuthManagerService extends AuthManager implements AuthManagerInterface{ /* * 缓存类 * @var string / protected $cacheClass = Redis::class; /* * jwt 具有自包含的特性 能自己描述自身何时过期 但只能一次性签发 * 用户主动注销后 jwt 并不能立即失效 所以我们可以设定一个 jwt 键名的 ttl * 这里使用是否 cacheEnable 来决定是否做二次验证 * 当获取token并解析后,token 的算法层是正确的 但如果 redis 中的 jwt 键名已经过期 * 则可认为用户主动注销了 jwt,则依然认为 jwt 非法 * 所以我们需要在用户主动注销时,更新 redis 中的 jwt 键名为立即失效 * 同时对 token 刷新进行验证 保证当前用户只有一个合法 token 刷新后前 token 立即失效 * @var bool 开启缓存 / protected $cacheEnable = true; // token 有效期 7 天 protected $sessionDuration = 86400 * 7; /* * 定义登录认证方法 调用 Swoft的AuthManager@login 方法进行登录认证 签发token * @param string $account * @param string $password * @return AuthSession / public function auth(string $account, string $password): AuthSession { // AuthLogic 需实现 AccountTypeInterface 接口的 login/authenticate 方法 return $this->login(AuthLogic::class, [ ‘account’ => $account, ‘password’ => $password ]); }}App\Controllers\AuthController处理用户的登录请求<?php/* * Created by PhpStorm. * User: big_cat * Date: 2018/12/10 0010 * Time: 17:05 /namespace App\Controllers;use App\Services\AuthManagerService;use Swoft\Http\Message\Server\Request;use Swoft\Http\Server\Bean\Annotation\Controller;use Swoft\Http\Server\Bean\Annotation\RequestMapping;use Swoft\Http\Server\Bean\Annotation\RequestMethod;use Swoft\Bean\Annotation\Inject;use Swoft\Bean\Annotation\Strings;use Swoft\Bean\Annotation\ValidatorFrom;/* * 登录认证模块 * @Controller("/v1/auth") * @package App\Controllers /class AuthController{ /* * 用户登录 * @RequestMapping(route=“login”, method={RequestMethod::POST}) * @Strings(from=ValidatorFrom::POST, name=“account”, min=6, max=11, default="", template=“帐号需{min}{max}位,您提交的为{value}”) * @Strings(from=ValidatorFrom::POST, name=“password”, min=6, max=25, default="", template=“密码需{min}{max}位,您提交的为{value}”) * @param Request $request * @return array / public function login(Request $request): array { $account = $request->input(‘account’) ?? $request->json(‘account’); $password = $request->input(‘password’) ?? $request->json(‘password’); // 调用认证服务 - 登录&签发token $session = $this->authManagerService->auth($account, $password); // 获取需要的jwt信息 $data_token = [ ’token’ => $session->getToken(), ’expired_at’ => $session->getExpirationTime() ]; return [ “err” => 0, “msg” => ‘success’, “data” => $data_token ]; }}POST /v1/auth/login 的结果{ “err”: 0, “msg”: “success”, “data”: { “token”: “eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJBcHBcXE1vZGVsc1xcTG9naWNcXEF1dGhMb2dpYyIsInN1YiI6IjgwIiwiaWF0IjoxNTUxNjAyOTk4LCJleHAiOjE1NTIyMDc3OTgsImRhdGEiOnsidWlkIjo4MH19.u2g5yU9ir1-ETVehLFIIZZgtW7u9aOvH2cndMsIY98Y”, “expired_at”: 1552207798 }}这里提及一下为什么要提供在服务端缓存token的选项$cacheEnable。普通的token不像jwt具有自我描述的特性,我们为维护token的有效期只能在服务端缓存其有效期,防止过期失效的token被滥用。jwt可以自我描述过期时间,为什么也要缓存呢?因为jwt自身的描述是只读的,即我们无法让jwt提前过期失效,如果用户退出登录,则销毁token是个不错的安全开发习惯,所以只有在服务端也维护了一份jwt的过期时间,用户退出时过期此token,那么就可以自由控制jwt的过期时间。/* * @param string $token * @return bool /public function authenticateToken(string $token): bool{ … // 如果开启了服务端缓存选项 则验证token是否过期 可变向控制jwt的有效期 if ($this->cacheEnable === true) { try { $cache = $this->getCacheClient() ->get($this->getCacheKey($session->getIdentity(), $session->getExtendedData())); if (! $cache || $cache !== $token) { throw new AuthException(ErrorCode::AUTH_TOKEN_INVALID); } } catch (InvalidArgumentException $e) { $err = sprintf(‘Identity : %s ,err : %s’, $session->getIdentity(), $e->getMessage()); throw new AuthException(ErrorCode::POST_DATA_NOT_PROVIDED, $err); } } $this->setSession($session); return true;}token 解析、验证token的解析及合法性验证实现流程,注意只是验证token的合法性,即签名是否正确,签发者,签发对象是否合法,是否过期。并未对 token 的访问权限做认证。使用到的组件及服务:#调用token拦截服务尝试获取token,并调用token管理服务做解析及合法性验证Swoft\Auth\Middleware\AuthMiddleware#token拦截服务``Swoft\Auth\Mapping\AuthorizationParserInterface::class#token拦截服务提供者,根据token类型调用相应的token解析器``Swoft\Auth\Parser\AuthorizationHeaderParser#token管理服务,由token管理服务提供者提供基础服务,被token解析器调用Swoft\Auth\Mapping\AuthManagerInterface::class#token管理服务提供者,负责签发、解析、合法性验证Swoft\Auth\AuthManagerSwoft\Auth\Middleware\AuthMiddleware负责拦截请求并调用token解析及验证服务。会尝试获取请求头中的Authorization字段值,根据类型Basic/Bearer来选择相应的权限认证服务组件对token做合法性的校验并生成token会话。但并不涉及业务访问权限ACL的验证,即只保证某个token 是本平台合法签发的,不保证此token对当前资源有合法的访问权限。如果Authorization为空的话则视为普通请求。执行流程:Swoft\Auth\Middleware\AuthMiddleware调用 Swoft\Auth\Mapping\AuthorizationParserInterface::class 服务,服务具体由 Swoft\Auth\Parser\AuthorizationHeaderParser实现。服务AuthorizationHeaderParser尝试获取请求头中的Authorization字段值,如果获取到token,则根据token的类型:BasicorBearer来调用具体的解析器。Basic的解析器为`Swoft\Auth\Parser\Handler::BasicAuthHandler,Bearer的解析器为 Swoft\Auth\Parser\Handler::BearerTokenHandler,下面我们具体以Bearer模式的jwt为示例。在获取到类型为Bearer的token后,BearerTokenHandler将会调用Swoft\Auth\Mapping\AuthManagerInterface::class服务的authenticateToken方法来对token进行合法性的校验和解析,即判断此token的签名是否合法,签发者是否合法,签发对象是否合法(注意:调用了App\Models\Logic\AuthLogic::authenticate方法验证),是否过期等。token解析验证非法,则抛出异常中断请求处理。token解析验证合法,则将payload载入本次会话并继续执行。所以我们可以将此中间件注册到全局,请求携带token则解析验证,不携带token则视为普通请求。#config/beans/base.phpreturn [ ‘serverDispatcher’ => [ ‘middlewares’ => [ … \Swoft\Auth\Middleware\AuthMiddleware::class ], … ], // token签发及合法性验证服务 \Swoft\Auth\Mapping\AuthManagerInterface::class => [ ‘class’ => \App\Services\AuthManagerService::class ],];<?phpnamespace AppModelsLogic;use SwoftAuthBeanAuthResult;use SwoftAuthMappingAccountTypeInterface;class AuthLogic implements AccountTypeInterface{…/* * 验证签发对象是否合法 这里我们简单验证签发对象是否为本平台用户 * $identity 即 jwt 的 sub 字段 * @override Swoft\Auth\Mapping\AccountTypeInterface * @param string $identity token sub 字段 * @return bool /public function authenticate(string $identity): bool{ return $this->userDao->exists($identity);}}acl鉴权token 虽然经过了合法性验证,只能说明token是本平台签发的,还无法判断此token是否有权访问当前业务资源,所以我们还要引入Acl认证。使用到的组件及服务:#Acl认证中间件Swoft\Auth\Middleware\AclMiddleware#用户业务权限auth服务Swoft\Auth\Mapping\AuthServiceInterface::class#token会话访问组件Swoft\Auth\AuthUserServiceSwoft\Auth\Middleware\AclMiddleware中间件会调用Swoft\Auth\Mapping\AuthServiceInterface::class服务,此服务主要用于Acl认证,即验证当前请求是否携带了合法token,及token是否对当前资源有访问权限。Swoft\Auth\Mapping\AuthServiceInterface::class服务由框架的Swoft\Auth\AuthUserService组件实现获取token会话的部分功能,auth方法则交由用户层重写,所以我们需继承Swoft\Auth\AuthUserService并根据自身业务需求实现auth方法。在继承了Swoft\Auth\AuthUserService的用户业务认证组件中,我们可以尝试获取token会话的签发对象及payload数据:getUserIdentity/getUserExtendData。然后在auth方法中判断当前请求是否有token会话及是否对当前资源有访问权限,来决定返回true or false给AclMiddleware中间件。AclMiddleware中间件获取到用户业务下的auth为false(请求没有携带合法token 401 或无权访问当前资源 403),则终端请求处理。AclMiddleware中间件获取到在用户业务下的auth为true,则说明请求携带合法token,且token对当前资源有权访问,继续请求处理。config/bean/base.phpreturn [ ‘serverDispatcher’ => [ ‘middlewares’ => [ …. //系统token解析中间件 \Swoft\Auth\Middleware\AuthMiddleware::class, … ] ], // token签发及合法性验证服务 \Swoft\Auth\Mapping\AuthManagerInterface::class => [ ‘class’ => \App\Services\AuthManagerService::class ], // Acl用户资源权限认证服务 \Swoft\Auth\Mapping\AuthServiceInterface::class => [ ‘class’ => \App\Services\AclAuthService::class, ‘userLogic’ => ‘${’ . \App\Models\Logic\UserLogic::class . ‘}’ // 注入UserLogicBean ],];App\Services\AclAuthService对token做Acl鉴权。<?phpnamespace App\Services;use Swoft\Auth\AuthUserService;use Swoft\Auth\Mapping\AuthServiceInterface;use Psr\Http\Message\ServerRequestInterface;/* * Bean 因在 config/beans/base.php 中已经以参数配置的方式注册,故此处不能再使用Bean注解声明 * Class AclAuthService * @package App\Services /class AclAuthService extends AuthUserService implements AuthServiceInterface{ /* * 用户逻辑模块 * 因本模块是以参数配置的方式注入到系统服务的 * 所以其相关依赖也需要使用参数配置方式注入 无法使用Inject注解声明 * @var App\Models\Logic\UserLogic / protected $userLogic; /* * 配合 AclMiddleware 中间件 验证用户请求是否合法 * true AclMiddleware 通过 *false AclMiddleware throw AuthException * @override AuthUserService * @param string $requestHandler * @param ServerRequestInterface $request * @return bool */ public function auth(string $requestHandler, ServerRequestInterface $request): bool { // 签发对象标识 $sub = $this->getUserIdentity(); // token载荷 $payload = $this->getUserExtendData(); // 验证当前token是否有权访问业务资源 aclAuth为自己的认证逻辑 if ($this->aclAuth($sub, $payload)) { return true; } return false; }} ...

March 5, 2019 · 4 min · jiezi

猫头鹰的深夜翻译:对于RestAPI简单的基于身份的权限控制

前言基于角色的权限控制(RBAC)是管理用户对某种资源或操作的权限的通用方法。权限可以明确指定可以访问的资源和操作。基本原理如下:权限将被分配给某个角色,并将该角色分配给某个用户或者是用户组,而不是直接分配给某个用户。角色与权限捆绑将权限与单个用户关联起来是一件很复杂的事情,随着更多的用户使用系统,维护用户的权限变得更加困难,且容易出错。权限的错误分配会阻止用户访问所需的系统,甚至是允许非授权用户访问限制区域或是执行危险操作。在这篇文章中,我会介绍如何对应用开启权限控制。权限控制的模型有许多种,比如RBAC(基于角色的权限控制),DAC(自由访问控制)等。虽然文档中解释的原则可以应用于各种模型,但我选择RBAC作为参考,因为它被广泛接受并且非常直观。查看用户的活动通常只会产生用户执行的有限数量的操作(如读取数据,提交表单)。深入观察这些用户的行为会发现,这些行为通常一起执行,即执行A操作的用户往往也会执行B操作。比如,读取并更新报告,或者是添加和删除用户。这些都可以与角色绑定,比如编辑或是账户管理员。注意这里的角色并不一定和职称或是组织结构绑定,而是以有意义的方式反映相关的用户操作。当恰当划分好角色并分配给用户时,就可以将权限分配给每个角色,而非用户。管理少量角色的权限是一件相对简单的事情。如下,是没有角色作为中介的权限与用户图:而如下,则是完全相同的用户和权限集,由角色组织:显而易见,角色使得权限管理更容易了用户与群组绑定将用户与群组绑定是一种更好的实践。在观察用户关于上述角色的行为模式时,我们经常发现用户之间有很多共同之处,比如某一组用户常常行为相似–在共同的资源上执行相同的操作。这允许我们将用户组织到组中,然后将角色分配给少数组,而不是许多用户。比如,会发现一组用户都需要系统管理员权限,因此我们新建一个名叫账户管理员的群组,将用户添加到该组并将该角色分配给该组,而不是每个用户。实现角色时的注意事项不要将行为和验证细节耦合在许多系统中,开发人员通过直接在实现方法上指定权限来限制对特定操作的访问。没错,就在代码上!通常,角色的验证通过注解添加到需要检查的方法上,比如这里提供了一个spring-security的一个范例:@PreAuthorize(“hasRole(‘Editor’)”) public void update_order(Order order);在不同语言和框架中,这种做法非常常见。虽然很容易实现,但遗憾的是,它在所需角色和动作的实现之间产生了不希望的耦合。想象一下有几十个方法都需要添加这样的注解。跟踪每一个角色的有效操作将会变得很艰难,几乎肯定会导致依赖于不准确或过时的文档,或者更糟糕的是 - 分散在您的应用程序中的未知,非托管权限。从客户的角度来看,这种耦合使得无法修改开发人员事先定义的角色集或者他们的权限,因为更改它意味着每次都必须编译和打包代码!这种用户体验也许不是我们的目标。如何避免耦合更好的方式是,首先从要由外部授权机制处理的代码中提取可能的操作列表,然后,我们可以使代码不知道角色或任何其他授权细节,简单地询问当前用户(无论它是否被检索)是否具有执行特定方法所需的权限(无论在何处定义)。这允许我们使用更加通用的注解,如下所示:@Secured public void update_order(Order order);角色和权限的映射(即执行特定操作的权限)现在可以在配置文件中完成,可以由客户轻松定制!比如,假设有这样的一个roles_config.yml文件:order_manager: - ‘create_order’ - ‘view_order’ - ‘delete_order’ - ‘update_order’order_inspector: - ‘viewer_order’由@secured注解的方法回去查询配置文件确定当前用户是否具有执行该操作的权限。这意味着当前用户必须具有order_manager的角色,而这一点也是很容易配置的。但是,授权机制必须知道如何将每个权限与代码中的特定方法相匹配,并且有人必须记录所有可用的方法(即create_order,view_order等)。关注点分离–外部授权既然方法实现代码不包含授权细节,整个授权逻辑可以移动到单独的独立模块。通过使用通用标题(例如注解@secure),我们允许修改整个授权机制而不影响应用程序的代码。例如,可以将@secure实现为基于角色的检查,但也可以使用访问控制列表(ACL)。比如,检查当前用户是否列在订单的ACL列表中。另一种解决方案可以是通过询问第三方是否允许用户执行该动作来使用oauth。Rest是最佳选择提取操作–举手之劳REST接口肯定更好,或者至少是最容易匹配这个模型的。设计良好的Rest服务通过标准的基于HTTP的API暴露资源和方法,资源通过URI定义,方法通过HTTP动词(如GET,PUT)等定义。比如,POST http://www.domain.com/bookings会创建一本新书,而GET http://www.domain.com/orders/12345会返回订单#12345的详情。这意味着可以轻而易举的获得资源的名称和对资源的操作。请求网关除了标准的建模操作之外,REST服务通常是请求流中评估身份验证和授权的好地方,因为这通常是系统的主要入口点。为了使访问控制机制有意义,建议阻止所有其他到系统的路由,例如直接访问数据存储或代码中的任何远程调用机制。该架构的另一个重要优点是响应过滤,以防某些不应当返回给用户的数据写在响应中。请求也是访问控制工具REST服务处理传入请求,这意味着请求中找到的信息可用于制定访问控制决策。一些有用的细节是:请求源:允许阻止来自不明IP或是网段的请求请求头:许多有意义的细节可以在请求头传递,比如用户凭证,从而支持全面的认证/授权过程。目标终端:如请求的URI所示。根据其他条件,访问可以仅限于应用程序端点的子集。例如,虽然version端点对所有人开放,但secret端点仅对经过身份验证的用户开放。目标方法:由HTTP动词(例如DELETE)表示,这意味着可以基于被调用的方法传递或阻止请求。总而言之:用REST来实现权限控制所有的资源将会通过REST的URI表示,操作通过HTTP动词表示,这能够覆盖所有能被执行且需要验证的操作。在下面的例子中,定义了三个角色:order_manager:能够查看,创建,更新和删除订单order_editor:能够查看,创建,更新订单,但不能删除他们order_inspector:只能查看订单order_manager: ‘/orders’: - ‘GET’ - ‘POST’ - ‘PUT’ - ‘DELETE’order_editor: ‘/orders’: - ‘GET’ - ‘POST’ - ‘PUT’order_inspector: ‘/orders’: - ‘GET’由此可见,REST天然能够实现权限控制。通过处理传入请求,REST服务能够检索有价值的信息,这些信息可以移交给单独的模块以执行身份验证和授权。如果用户被授权在目标资源上执行所请求的方法,则可以继续请求处理。否则,在到达任何内部应用程序代码之前拒绝进一步访问。

January 29, 2019 · 1 min · jiezi

PyCasbin: 支持 ACL、RBAC、ABAC 多种模型的 Python 权限管理框架

PyCasbin 是一个用 Python 语言打造的轻量级开源访问控制框架( https://github.com/casbin/pyc… ),目前在 GitHub 开源。PyCasbin 采用了元模型的设计思想,支持多种经典的访问控制方案,如基于角色的访问控制 RBAC、基于属性的访问控制 ABAC 等。PyCasbin 的主要特性包括1.支持自定义请求的格式,默认的请求格式为{subject, object, action};2.具有访问控制模型 model 和策略 policy 两个核心概念;3.支持 RBAC 中的多层角色继承,不止主体可以有角色,资源也可以具有角色;4.支持超级用户,如 root 或 Administrator,超级用户可以不受授权策略的约束访问任意资源;5.支持多种内置的操作符,如 keyMatch,方便对路径式的资源进行管理,如 /foo/bar 可以映射到 /foo*PyCasbin 不做的事情:1.身份认证 authentication (即验证用户的用户名、密码),PyCasbin 只负责访问控制。应该有其他专门的组件负责身份认证,然后由 PyCasbin 进行访问控制,二者是相互配合的关系;2.管理用户列表或角色列表。PyCasbin 认为由项目自身来管理用户、角色列表更为合适,PyCasbin 假设所有策略和请求中出现的用户、角色、资源都是合法有效的。安装pip install casbinHelloWorld 例子1.初始化一个 enforcer,传入两个参数:模型文件路径和策略文件路径;import casbine = casbin.Enforcer(“path/to/model.conf”, “path/to/policy.csv”)2.在你的代码需要进行访问控制的位置,加入如下钩子;sub = “alice” # the user that wants to access a resource.obj = “data1” # the resource that is going to be accessed.act = “read” # the operation that the user performs on the resource.if e.enforce(sub, obj, act): # permit alice to read data1 passelse: # deny the request, show an error pass3.采用管理 API 进行权限的管理,如获取一个用户所有的角色;roles = e.get_roles(“alice”)社区进展PyCasbin 目前正在积极向社区进行推送,并且可以通过插件的方式已经支持与 Django 等 Web 框架进行集成,将来会推广到更多 Web 框架以及社区。Casbin 已经有 Golang 版本、Java 版本、PHP 版本、Node.js 版本、Pytho n版本 等主流语言版本。有跨语言需求的开发者可以只用 Casbin 这一套框架就实现多个不同语言的项目的权限管理任务。PyCasbin (Python): https://github.com/casbin/pyc...Casbin (Go): https://github.com/casbin/casbinjCasbin (Java): https://github.com/casbin/jca...PHP-Casbin (PHP): https://github.com/php-casbin...Node-Casbin (Node.js): https://github.com/casbin/nod…协议PyCasbin 采用 Apache 2.0 开源协议发布。联系作者有问题请提交 Issues: https://github.com/casbin/pyc…或者加入 QQ 群:546057381( Casbin 访问控制讨论群) ...

January 25, 2019 · 1 min · jiezi