安全性¶
Pyramid 提供可选的声明性安全系统。系统确定当前用户的身份(身份验证)以及用户是否有权访问某些资源(授权)。
这个 Pyramid 安全系统可以防止 view 从基于 security policy . 在调用视图之前,授权系统可以使用 request 随着 context 用于确定是否允许访问的资源。下面是它在高层次上的工作方式:
用户可能曾经访问过应用程序,也可能没有提供过身份验证凭据,包括 userid . 如果是这样,应用程序可能会调用
pyramid.security.remember()
记住这些。A request 当用户访问应用程序时生成。
根据请求,a context 资源定位通过 resource location . 根据应用程序是否使用 traversal 或 URL dispatch 但在这两种情况下,最终都会找到上下文。见 URL调度 第章了解更多信息。
A view callable 位于 view lookup 使用请求的上下文和其他属性。
如果A security policy 有效且 view configuration 与找到的视图可调用项关联的 permission 与之相关联的策略被传递 request , the context 和 permission 与视图关联;它将允许或拒绝访问。
如果安全策略允许访问,则调用视图callable。
如果安全策略拒绝访问,则不会调用视图callable。取而代之的是 forbidden view 被调用。
通过修改应用程序以包含 security policy . Pyramid 提供各种帮助程序来帮助创建此策略。
编写安全策略¶
Pyramid 默认情况下不启用任何安全策略。所有视图都可以由完全匿名的用户访问。为了开始基于安全设置保护视图不被执行,您需要编写一个安全策略。
安全策略是实现的简单类 pyramid.interfaces.ISecurityPolicy
. 简单的安全策略可能如下所示:
1from pyramid.security import Allowed, Denied
2
3class SessionSecurityPolicy:
4 def identity(self, request):
5 """ Return app-specific user object. """
6 userid = request.session.get('userid')
7 if userid is None:
8 return None
9 return load_identity_from_db(request, userid)
10
11 def authenticated_userid(self, request):
12 """ Return a string ID for the user. """
13 identity = self.identity(request)
14 if identity is None:
15 return None
16 return string(identity.id)
17
18 def permits(self, request, context, permission):
19 """ Allow access to everything if signed in. """
20 identity = self.identity(request)
21 if identity is not None:
22 return Allowed('User is signed in.')
23 else:
24 return Denied('User is not signed in.')
25
26 def remember(request, userid, **kw):
27 request.session['userid'] = userid
28 return []
29
30 def forget(request, **kw):
31 del request.session['userid']
32 return []
使用 set_security_policy()
方法 Configurator
对应用程序强制执行安全策略。
参见
有关实现 permits
方法,见 使用安全策略允许和拒绝访问 .
使用助手编写安全策略¶
为了帮助编写通用安全策略,Pyramid提供了几个助手。以下身份验证帮助程序帮助实现 identity
, remember
和 forget
.
用例 |
帮手 |
---|---|
存储 userid 用“授权票”饼干。 |
|
使用HTTP基本身份验证检索用户凭据。 |
使用 |
检索 userid 从 |
|
例如,我们的上述安全策略可以利用这些助手,如下所示:
1from pyramid.security import Allowed, Denied
2from pyramid.authentication import SessionAuthenticationHelper
3
4class SessionSecurityPolicy:
5 def __init__(self):
6 self.helper = SessionAuthenticationHelper()
7
8 def identity(self, request):
9 """ Return app-specific user object. """
10 userid = self.helper.authenticated_userid(request)
11 if userid is None:
12 return None
13 return load_identity_from_db(request, userid)
14
15 def authenticated_userid(self, request):
16 """ Return a string ID for the user. """
17 identity = self.identity(request)
18 if identity is None:
19 return None
20 return str(identity.id)
21
22 def permits(self, request, context, permission):
23 """ Allow access to everything if signed in. """
24 identity = self.identity(request)
25 if identity is not None:
26 return Allowed('User is signed in.')
27 else:
28 return Denied('User is not signed in.')
29
30 def remember(request, userid, **kw):
31 return self.helper.remember(request, userid, **kw)
32
33 def forget(request, **kw):
34 return self.helper.forget(request, **kw)
Helpers用于特定于应用程序的代码。注意上面的代码是如何从helper获取userid并使用它来加载 identity 从数据库中。 authenticated_userid
拉动 userid 从 identity 为了保证存储在会话中的用户ID存在于数据库中(“authenticated”)。
使用权限保护视图¶
保护一个 view callable 当特定类型的资源成为 context ,您必须通过 permission 到 view configuration . 权限通常只是字符串,它们没有必需的组合:您可以随意命名权限。
例如,以下视图声明保护名为 add_entry.html
当上下文资源类型为 Blog
与 add
使用权限 pyramid.config.Configurator.add_view()
应用程序编程接口:
1# config is an instance of pyramid.config.Configurator
2
3config.add_view('mypackage.views.blog_entry_add_view',
4 name='add_entry.html',
5 context='mypackage.resources.Blog',
6 permission='add')
等效视图注册包括 add
权限名称可以通过 @view_config
装饰者:
1from pyramid.view import view_config
2from resources import Blog
3
4@view_config(context=Blog, name='add_entry.html', permission='add')
5def blog_entry_add_view(request):
6 """ Add blog entry code goes here """
7 pass
由于这些不同的视图配置语句中的任何一个,如果在正常应用程序操作期间发现视图可调用时有安全策略,则将查询该安全策略以查看是否允许请求用户使用 add
当前权限内 context . 如果政策允许访问, blog_entry_add_view
将被调用。如果不是的话 Forbidden view 将被调用。
使用安全策略允许和拒绝访问¶
要确定是否允许使用附加权限访问视图,Pyramid调用 permits
安全策略的方法。 permits
应该返回 pyramid.security.Allowed
或 pyramid.security.Denied
. 两个类都接受一个字符串作为参数,这应该详细说明为什么允许或拒绝访问。
简单的 permits
基于用户角色授予访问权限的实现可能如下所示:
1from pyramid.security import Allowed, Denied
2
3class SecurityPolicy:
4 def permits(self, request, context, permission):
5 identity = self.identity(request)
6
7 if identity is None:
8 return Denied('User is not signed in.')
9 if identity.role == 'admin':
10 allowed = ['read', 'write', 'delete']
11 elif identity.role == 'editor':
12 allowed = ['read', 'write']
13 else:
14 allowed = ['read']
15
16 if permission in allowed:
17 return Allowed(
18 'Access granted for user %s with role %s.',
19 identity,
20 identity.role,
21 )
22 else:
23 return Denied(
24 'Access denied for user %s with role %s.',
25 identity,
26 identity.role,
27 )
设置默认权限¶
如果没有向视图配置提供权限,则注册的视图将始终由完全匿名的用户执行:任何有效的安全策略都将被忽略。
为了便于配置“默认安全”的应用程序, Pyramid 允许您配置 违约 许可。如果提供了默认权限,则默认权限将用作所有视图注册的权限字符串,而这些注册不是以其他方式命名的 permission
参数。
这个 pyramid.config.Configurator.set_default_permission()
方法支持为应用程序配置默认权限。
注册默认权限时:
如果视图配置将显式
permission
,将忽略该视图注册的默认权限,并使用名为权限的视图配置。如果视图配置命名权限
pyramid.security.NO_PERMISSION_REQUIRED
,将忽略默认权限,并注册视图 没有 一个权限(使其对所有呼叫者都可用,而不管其凭据如何)。
警告
注册默认权限时, all 观点(甚至) exception view 视图)受权限保护。对于所有真正打算匿名访问的视图,您需要将视图的配置与 pyramid.security.NO_PERMISSION_REQUIRED
许可。
ACL的元素¶
下面是一个示例acl:
1from pyramid.authorization import Allow
2from pyramid.authorization import Everyone
3
4__acl__ = [
5 (Allow, Everyone, 'view'),
6 (Allow, 'group:editors', 'add'),
7 (Allow, 'group:editors', 'edit'),
8]
示例acl指示 pyramid.authorization.Everyone
principal——一个特殊的系统定义的principal,字面意思是,每个人都可以查看博客,并且 group:editors
主体可以添加到日志并对其进行编辑。
acl的每个元素都是 ACE 或访问控制项。例如,在上面的代码块中,有三个ace: (Allow, Everyone, 'view')
, (Allow, 'group:editors', 'add')
和 (Allow, 'group:editors', 'edit')
.
任何ace的第一个元素都是 pyramid.authorization.Allow
或 pyramid.authorization.Deny
,表示在ACE匹配时要采取的操作。第二个元素是 principal . 第三个参数是权限或权限名称序列。
主体通常是用户id,但是如果您的身份验证系统提供组信息,它也可能是组id。
ACL中的每个ACE都由ACL helper处理 按照acl指定的顺序 . 因此,如果您有这样的ACL:
1from pyramid.authorization import Allow
2from pyramid.authorization import Deny
3from pyramid.authorization import Everyone
4
5__acl__ = [
6 (Allow, Everyone, 'view'),
7 (Deny, Everyone, 'view'),
8]
ACL帮助程序将 允许 每个人都有视图权限,即使稍后在ACL中,您有一个拒绝所有人视图权限的ACE。另一方面,如果您有这样的ACL:
1from pyramid.authorization import Everyone
2from pyramid.authorization import Allow
3from pyramid.authorization import Deny
4
5__acl__ = [
6 (Deny, Everyone, 'view'),
7 (Allow, Everyone, 'view'),
8]
ACL helper将拒绝每个人查看权限,即使稍后在ACL中,有一个ACE允许所有人。
ACE中的第三个参数也可以是权限名序列,而不是单个权限名。因此,不要创建多个ace来表示对单个ace的多个不同权限授予 group:editors
分组,我们可以将其折叠成一个ACE,如下所示。
1from pyramid.authorization import Allow
2from pyramid.authorization import Everyone
3
4__acl__ = [
5 (Allow, Everyone, 'view'),
6 (Allow, 'group:editors', ('add', 'edit')),
7]
特殊主体名称¶
特殊主体名称存在于 pyramid.authorization
模块。它们可以导入以在您自己的代码中用于填充ACL,例如, pyramid.authorization.Everyone
.
pyramid.authorization.Everyone
从字面上说,每个人,无论什么。这个物体实际上是引擎盖下面的一根绳子 (
system.Everyone
)每个用户 is 在每个请求期间,即使安全策略不在使用中,主体也会将其命名为“Everyone”。
pyramid.authorization.Authenticated
具有由当前安全策略确定的凭据的任何用户。您可能会认为它是任何“登录”的用户。这个物体实际上是引擎盖下面的一根绳子 (
system.Authenticated
)
特殊权限¶
特殊权限名称存在于 pyramid.authorization
模块。这些可以导入以在ACL中使用。
pyramid.authorization.ALL_PERMISSIONS
一个物体,字面上表示, all 权限。在像这样的ACL中很有用:
(Allow, 'fred', ALL_PERMISSIONS)
. 这个ALL_PERMISSIONS
对象实际上是具有__contains__
始终返回的方法True
,对于所有已知的授权策略,它具有指示给定主体具有系统请求的任何权限的效果。
特殊王牌¶
方便 ACE 定义为对中所有权限的所有人表示拒绝 pyramid.authorization.DENY_ALL
. 此ace通常用作 last acl的ace显式地导致继承授权策略“停止查找遍历树”(有效地破坏任何继承)。例如,允许 only fred
特定资源的查看权限(不管继承的ACL可能会说什么)可能如下所示:
1from pyramid.authorization import Allow
2from pyramid.authorization import DENY_ALL
3
4__acl__ = [ (Allow, 'fred', 'view'), DENY_ALL ]
在引擎盖下面, pyramid.authorization.DENY_ALL
ace等于以下值:
1from pyramid.authorization import ALL_PERMISSIONS
2__acl__ = [ (Deny, Everyone, ALL_PERMISSIONS) ]
ACL继承和位置感知¶
当ACL helper就位时,如果资源对象作为上下文时没有ACL,则其 起源 为ACL咨询。如果该对象没有ACL, its 在我们到达根目录并且没有更多的父目录之前,将为acl(无限)咨询父目录。
为了允许安全机制执行ACL继承,资源对象必须提供 location-awareness . 提供 location-awareness 意味着两件事:资源树中的根对象必须具有 __name__
属性与A __parent__
属性。
1class Blog(object):
2 __name__ = ''
3 __parent__ = None
带有 __parent__
属性与A __name__
属性被称为 location-aware . 位置感知对象定义 __parent__
指向其父对象的属性。根对象的 __parent__
是 None
.
参见
也见 pyramid.location 用于记录使用位置感知的功能。
参见
也见 位置感知资源 .
更改禁止的视图¶
什么时候? Pyramid 由于授权拒绝,拒绝视图调用 forbidden
调用视图。开箱即用,这张禁止观看的照片非常清晰。见 更改禁止的视图 在内部 使用钩子 有关如何创建自定义禁止视图以及如何安排在拒绝视图授权时调用该视图的说明。
反对秘密分享的告诫¶
金字塔的各个组成部分都需要一个“秘密”。例如,下面的helper可能用于安全策略并使用机密值 seekrit
::
helper = AuthTktCookieHelper('seekrit')
A session factory 还需要一个秘密:
my_session_factory = SignedCookieSessionFactory('itsaseekreet')
对于多个金字塔子系统,使用相同的秘密是很有诱惑力的。例如,您可能会尝试使用该值 seekrit
作为上面定义的helper和会话工厂的秘密。这是个坏主意,因为在这两种情况下,这些秘密都被用来签署数据的有效载荷。
如果您将同一个秘密用于应用程序的两个不同部分进行签名,则可能会允许攻击者对所选明文进行签名,从而允许攻击者控制有效负载的内容。在两个不同的子系统中重新使用秘密可能会使签名的安全性降低到零。在攻击者可能提供所选纯文本的不同上下文中,不应重复使用密钥。
防止跨站点请求伪造攻击¶
Cross-site request forgery 攻击是一种现象,登录到您的网站的用户可能会无意中加载一个URL,因为它是从攻击者的网站链接或嵌入的。如果URL是一个可以修改或删除数据的URL,其后果可能是可怕的。
通过向浏览器发出一个唯一的令牌,然后要求它出现在所有潜在的不安全请求中,可以避免大多数攻击。 Pyramid 提供创建和检查CSRF令牌的工具。
默认情况下 Pyramid 带有基于会话的CSRF实现 pyramid.csrf.SessionCSRFStoragePolicy
. 要使用它,必须首先启用 session factory 如上所述 使用默认会话工厂 或 使用备用会话工厂 . 或者,您可以使用基于cookie的实现 pyramid.csrf.CookieCSRFStoragePolicy
这给了一些额外的灵活性,因为它不需要为每个用户提供会话。您还可以定义自己的 pyramid.interfaces.ICSRFStoragePolicy
并将其注册到 pyramid.config.Configurator.set_csrf_storage_policy()
指令。
例如:
from pyramid.config import Configurator
config = Configurator()
config.set_csrf_storage_policy(MyCustomCSRFPolicy())
使用 csrf.get_csrf_token
方法¶
要获取当前的CSRF令牌,请使用 pyramid.csrf.get_csrf_token
方法。
from pyramid.csrf import get_csrf_token
token = get_csrf_token(request)
这个 get_csrf_token()
方法接受单个参数:请求。它返回一个CSRF 令牌 字符串。如果 get_csrf_token()
或 new_csrf_token()
以前为此用户调用过,则将返回现有令牌。如果此用户以前没有CSRF令牌,那么将在会话中设置一个新令牌并返回。新创建的令牌将是不透明的和随机的。
使用 get_csrf_token
模板中的全局¶
模板具有 get_csrf_token()
方法插入到它们的全局中,这允许您在不修改视图代码的情况下获取当前标记。此方法不接受任何参数并返回CSRF令牌字符串。您可以使用返回的令牌作为表单中隐藏字段的值,该表单向需要提升权限的方法发送消息,或者在Ajax请求中将其作为请求头提供。
例如,将CSRF令牌包含为隐藏字段:
<form method="post" action="/myview">
<input type="hidden" name="csrf_token" value="${get_csrf_token()}">
<input type="submit" value="Delete Everything">
</form>
或者将其作为头包含在jquery ajax请求中:
var csrfToken = "${get_csrf_token()}";
$.ajax({
type: "POST",
url: "/myview",
headers: { 'X-CSRF-Token': csrfToken }
}).done(function() {
alert("Deleted");
});
然后,接收请求的URL的处理程序应要求提供正确的CSRF令牌。
使用 csrf.new_csrf_token
方法¶
要显式创建新的CSRF令牌,请使用 csrf.new_csrf_token()
方法。这与 csrf.get_csrf_token()
因为它清除任何现有的CSRF令牌,创建一个新的CSRF令牌,将令牌设置为用户,并返回令牌。
from pyramid.csrf import new_csrf_token
token = new_csrf_token(request)
备注
无法从模板强制新的CSRF令牌。如果要重新生成CSRF令牌,请在视图代码中执行该操作,并将新令牌作为上下文的一部分返回。
手动检查CSRF令牌¶
在请求处理代码中,您可以使用 pyramid.csrf.check_csrf_token()
. 如果令牌有效,它将返回 True
,否则会升高 HTTPBadRequest
. 或者,您可以指定 raises=False
把支票退了 False
而不是提出例外。
默认情况下,它检查名为 csrf_token
或一个名为 X-CSRF-Token
.
from pyramid.csrf import check_csrf_token
def myview(request):
# Require CSRF Token
check_csrf_token(request)
# ...
自动检查CSRF令牌¶
在 1.7 版本加入.
Pyramid 支持使用RFC2616定义的不安全方法对请求自动检查CSRF令牌。可以手动检查任何其他请求。对于使用 pyramid.config.Configurator.set_default_csrf_options()
指令。例如:
from pyramid.config import Configurator
config = Configurator()
config.set_default_csrf_options(require_csrf=True)
可以使用 require_csrf
查看选项。一个值 True
或 False
将覆盖默认设置 set_default_csrf_options
. 例如:
@view_config(route_name='hello', require_csrf=False)
def myview(request):
# ...
当CSRF检查激活时,用于查找提供的CSRF令牌的令牌和头将 csrf_token
和 X-CSRF-Token
,分别,除非另有规定 set_default_csrf_options
. 将根据中的值检查令牌 request.POST
这是提交的表单主体。如果此值不存在,则将检查标题。
除了基于令牌的CSRF检查之外,如果请求使用HTTPS,那么自动CSRF检查还将检查请求的引用,以确保它与受信任的来源之一匹配。默认情况下,唯一受信任的源是当前主机,但是可以通过设置配置其他源 pyramid.csrf_trusted_origins
to a list of domain names (and ports if they are non-standard). If a host in the list of domains starts with a .
这样就允许所有子域以及没有 .
. 如果没有 Referer
或 Origin
HTTPS请求中存在标头,则CSRF检查将失败,除非 allow_no_origin
已设置。特别的 Origin: null
可以通过添加 null
到 pyramid.csrf_trusted_origins
名单。
可以选择不检查原点 check_origin=False
. 如果 CSRF storage policy 已知是安全的,因此攻击者无法轻易使用令牌。
如果CSRF检查失败,则 pyramid.exceptions.BadCSRFToken
或 pyramid.exceptions.BadCSRFOrigin
将引发异常。此异常可能由 exception view 但是,默认情况下,会导致 400 Bad Request
正在向客户端发送响应。