Python Pyramid - 安全

Pyramid 的声明式安全系统确定当前用户的身份并验证用户是否有权访问某些资源。 安全策略可以阻止用户调用视图。 在调用任何视图之前,授权系统使用请求中的凭据来确定是否允许访问。

安全策略被定义为一个类,它借助 pyramid.security 模块中定义的以下方法来控制用户访问 −

  • forget(request) − 此方法返回适用于"forgetting(忘记)"当前经过身份验证的用户拥有的凭据集的标头元组。 它通常用在视图函数的主体中。

  • remember(request, userid) − 此方法在请求的响应中返回一系列标头元组。 它们适用于使用当前安全策略"remembering(记住)"一组凭据,例如 userid。 在视图函数的主体中,常见用法可能看起来像这样。

通过身份验证的用户的访问由该模块中的AllowedDenied 类的对象控制。

为了实现身份、记忆和遗忘机制的功能,Pyramid 提供了在 pyramid.authentication 模块中定义的以下 helper 类 −

  • SessionAuthenticationHelper − 将用户 ID 存储在会话中。

  • AuthTktCookieHelper − 使用"auth ticket"cookie 存储用户 ID。

我们还可以使用 extract_http_basic_credentials() 函数通过 HTTP Basic Auth 检索用户凭证。

要在 WSGI 环境中从 REMOTE_USER 检索用户 ID,可以使用 request.environ.get('REMOTE_USER')


示例

现在让我们学习如何借助以下示例来实施安全策略。 本例的"development.ini"如下 −

[app:main]
use = egg:tutorial
pyramid.reload_templates = true
pyramid.includes = pyramid_debugtoolbar
hello.secret = a12b

[server:main]
use = egg:waitress#main
listen = localhost:6543

然后我们将安全策略类写在下面保存为security.py的Python代码中 −

from pyramid.authentication import AuthTktCookieHelper
USERS = {'admin': 'admin', 'manager': 'manager'}
class SecurityPolicy:
   def __init__(self, secret):
      self.authtkt = AuthTktCookieHelper(secret=secret)
   def identity(self, request):
      identity = self.authtkt.identify(request)
      if identity is not None and identity['userid'] in USERS:
      return identity
   def authenticated_userid(self, request):
      identity = self.identity(request)
      if identity is not None:
         return identity['userid']
   def remember(self, request, userid, **kw):
      return self.authtkt.remember(request, userid, **kw)
   def forget(self, request, **kw):
      return self.authtkt.forget(request, **kw)

包文件夹中的 __init__.py 文件定义了以下配置。 上面定义的安全策略类通过Configurator类的set_security_policy()方法添加到配置中。 三个路由——home、login 和 logout——被添加到配置中。

from pyramid.config import Configurator
from .security import SecurityPolicy

def main(global_config, **settings):
   config = Configurator(settings=settings)
   config.include('pyramid_chameleon')
   config.set_security_policy(
      SecurityPolicy(
         secret=settings['hello.secret'],
      ),
   )
   config.add_route('home', '/')
   config.add_route('login', '/login')
   config.add_route('logout', '/logout')
   config.scan('.views')
   return config.make_wsgi_app()

views.py中定义了上述路由对应的三个视图。

from pyramid.httpexceptions import HTTPFound
from pyramid.security import remember, forget

from pyramid.view import view_config, view_defaults
from .security import USERS

@view_defaults(renderer='home.pt')
class HelloViews:
   def __init__(self, request):
      self.request = request
      self.logged_in = request.authenticated_userid
   @view_config(route_name='home')
   def home(self):
      return {'name': 'Welcome'}
   @view_config(route_name='login', renderer='login.pt')
   def login(self):
      request = self.request
      login_url = request.route_url('login')
      referrer = request.url
      if referrer == login_url:
         referrer = '/'
      came_from = request.params.get('came_from', referrer)
      message = ''
      login = ''
      password = ''
      if 'form.submitted' in request.params:
         login = request.params['login']
         password = request.params['password']
         pw = USERS.get(login)
         if pw == password:
            headers = remember(request, login)
            return HTTPFound(location=came_from, headers=headers)
         message = 'Failed login'
         return dict(
            name='Login', message=message,
            url=request.application_url + '/login',
            came_from=came_from,
            login=login, password=password,)
            
   @view_config(route_name='logout')
   def logout(self):
      request = self.request
      headers = forget(request)
      url = request.route_url('home')
      return HTTPFound(location=url, headers=headers)

登录视图呈现登录表单。 当用户输入的用户 ID 和密码根据用户列表进行验证时,详细信息将被"remembered(记住)"。 另一方面,注销视图通过"forgetting"来释放这些细节。

主页视图呈现以下 chameleon 模板 - home.pt

<!DOCTYPE html>
<html lang="en">
<body>
   <div>
      <a tal:condition="view.logged_in is None" href="${request.application_url}/login">Log In</a>
      <a tal:condition="view.logged_in is not None" href="${request.application_url}/logout">Logout</a>
   </div>
   <h1>Hello. ${name}</h1>
</body>
</html>

以下是用于登录视图的 chameleon 模板login.pt

<!DOCTYPE html>
<html lang="en">
<body>
   <h1>Login</h1>
   <span tal:replace="message"/>

   <form action="${url}" method="post">
      <input type="hidden" name="came_from" value="${came_from}"/>
      <label for="login">Username</label>
      <input type="text" id="login" name="login" value="${login}"/><br/>
      <label for="password">Password</label>
      <input type="password" id="password" name="password" value="${password}"/><br/>
      <input type="submit" name="form.submitted" value="Log In"/>
   </form>
</body>
</html>

development.ini 和setup.py 放在外层工程文件夹中,__init__.py, views.py, security.py 和模板 home.pt 以及login.pt 应该保存在名为hello 的包文件夹下。

使用以下命令安装包 −

Env\hello>pip3 install -e.

使用 pserve 实用程序启动服务器。

pserve development.ini

输出

打开浏览器并访问http://localhost:6543/链接。

Welcome

点击"Log In"链接打开登录表单 −

Login

主视图页面返回时链接更改为注销,因为凭据已被记住。

Hello

单击"logout"注销链接将导致忘记凭据并显示默认主页。