20:使用身份验证登录¶
根据用户列表验证用户名和密码的登录视图。
背景¶
大多数Web应用程序都有允许人们通过Web浏览器添加/编辑/删除内容的URL。添加时间 security 应用程序。在第一步中,我们将介绍身份验证。也就是说,登录和注销,使用Pyramid丰富的可插入用户存储设施。
在下一步中,我们将使用授权安全声明介绍对资源的保护。
目标¶
介绍认证的Pyramid概念。
创建登录和注销视图。
步骤¶
我们将使用视图类步骤作为起点:
cd ..; cp -r view_classes authentication; cd authentication
添加
bcrypt
作为一种依附authentication/setup.py
:1from setuptools import setup 2 3# List of dependencies installed via `pip install -e .` 4# by virtue of the Setuptools `install_requires` value below. 5requires = [ 6 'bcrypt', 7 'pyramid', 8 'pyramid_chameleon', 9 'waitress', 10] 11 12# List of dependencies installed via `pip install -e ".[dev]"` 13# by virtue of the Setuptools `extras_require` value in the Python 14# dictionary below. 15dev_requires = [ 16 'pyramid_debugtoolbar', 17 'pytest', 18 'webtest', 19] 20 21setup( 22 name='tutorial', 23 install_requires=requires, 24 extras_require={ 25 'dev': dev_requires, 26 }, 27 entry_points={ 28 'paste.app_factory': [ 29 'main = tutorial:main' 30 ], 31 }, 32)
我们现在可以在开发模式下安装我们的项目:
$VENV/bin/pip install -e .
将安全哈希放入
authentication/development.ini
配置文件为tutorial.secret
而不是将其放入代码中:1[app:main] 2use = egg:tutorial 3pyramid.reload_templates = true 4pyramid.includes = 5 pyramid_debugtoolbar 6tutorial.secret = 98zd 7 8[server:main] 9use = egg:waitress#main 10listen = localhost:6543
创建一个
authentication/tutorial/security.py
模块,它可以通过提供 security policy :1import bcrypt 2from pyramid.authentication import AuthTktCookieHelper 3 4 5def hash_password(pw): 6 pwhash = bcrypt.hashpw(pw.encode('utf8'), bcrypt.gensalt()) 7 return pwhash.decode('utf8') 8 9def check_password(pw, hashed_pw): 10 expected_hash = hashed_pw.encode('utf8') 11 return bcrypt.checkpw(pw.encode('utf8'), expected_hash) 12 13 14USERS = {'editor': hash_password('editor'), 15 'viewer': hash_password('viewer')} 16 17 18class SecurityPolicy: 19 def __init__(self, secret): 20 self.authtkt = AuthTktCookieHelper(secret=secret) 21 22 def identity(self, request): 23 identity = self.authtkt.identify(request) 24 if identity is not None and identity['userid'] in USERS: 25 return identity 26 27 def authenticated_userid(self, request): 28 identity = self.identity(request) 29 if identity is not None: 30 return identity['userid'] 31 32 def remember(self, request, userid, **kw): 33 return self.authtkt.remember(request, userid, **kw) 34 35 def forget(self, request, **kw): 36 return self.authtkt.forget(request, **kw)
注册
SecurityPolicy
与 configurator 在里面authentication/tutorial/__init__.py
:1from pyramid.config import Configurator 2 3from .security import SecurityPolicy 4 5 6def main(global_config, **settings): 7 config = Configurator(settings=settings) 8 config.include('pyramid_chameleon') 9 10 config.set_security_policy( 11 SecurityPolicy( 12 secret=settings['tutorial.secret'], 13 ), 14 ) 15 16 config.add_route('home', '/') 17 config.add_route('hello', '/howdy') 18 config.add_route('login', '/login') 19 config.add_route('logout', '/logout') 20 config.scan('.views') 21 return config.make_wsgi_app()
更新中的视图
authentication/tutorial/views.py
:1from pyramid.httpexceptions import HTTPFound 2from pyramid.security import ( 3 remember, 4 forget, 5 ) 6 7from pyramid.view import ( 8 view_config, 9 view_defaults 10 ) 11 12from .security import ( 13 USERS, 14 check_password 15) 16 17 18@view_defaults(renderer='home.pt') 19class TutorialViews: 20 def __init__(self, request): 21 self.request = request 22 self.logged_in = request.authenticated_userid 23 24 @view_config(route_name='home') 25 def home(self): 26 return {'name': 'Home View'} 27 28 @view_config(route_name='hello') 29 def hello(self): 30 return {'name': 'Hello View'} 31 32 @view_config(route_name='login', renderer='login.pt') 33 def login(self): 34 request = self.request 35 login_url = request.route_url('login') 36 referrer = request.url 37 if referrer == login_url: 38 referrer = '/' # never use login form itself as came_from 39 came_from = request.params.get('came_from', referrer) 40 message = '' 41 login = '' 42 password = '' 43 if 'form.submitted' in request.params: 44 login = request.params['login'] 45 password = request.params['password'] 46 hashed_pw = USERS.get(login) 47 if hashed_pw and check_password(password, hashed_pw): 48 headers = remember(request, login) 49 return HTTPFound(location=came_from, 50 headers=headers) 51 message = 'Failed login' 52 53 return dict( 54 name='Login', 55 message=message, 56 url=request.application_url + '/login', 57 came_from=came_from, 58 login=login, 59 password=password, 60 ) 61 62 @view_config(route_name='logout') 63 def logout(self): 64 request = self.request 65 headers = forget(request) 66 url = request.route_url('home') 67 return HTTPFound(location=url, 68 headers=headers)
在添加登录模板
authentication/tutorial/login.pt
:1<!DOCTYPE html> 2<html lang="en"> 3<head> 4 <title>Quick Tutorial: ${name}</title> 5</head> 6<body> 7<h1>Login</h1> 8<span tal:replace="message"/> 9 10<form action="${url}" method="post"> 11 <input type="hidden" name="came_from" 12 value="${came_from}"/> 13 <label for="login">Username</label> 14 <input type="text" id="login" 15 name="login" 16 value="${login}"/><br/> 17 <label for="password">Password</label> 18 <input type="password" id="password" 19 name="password" 20 value="${password}"/><br/> 21 <input type="submit" name="form.submitted" 22 value="Log In"/> 23</form> 24</body> 25</html>
在中提供登录/注销框
authentication/tutorial/home.pt
:1<!DOCTYPE html> 2<html lang="en"> 3<head> 4 <title>Quick Tutorial: ${name}</title> 5</head> 6<body> 7 8<div> 9 <a tal:condition="view.logged_in is None" 10 href="${request.application_url}/login">Log In</a> 11 <a tal:condition="view.logged_in is not None" 12 href="${request.application_url}/logout">Logout</a> 13</div> 14 15<h1>Hi ${name}</h1> 16<p>Visit <a href="${request.route_url('hello')}">hello</a></p> 17</body> 18</html>
运行 Pyramid 应用程序时使用:
$VENV/bin/pserve development.ini --reload
在浏览器中打开http://localhost:6543/。
单击“登录”链接。
使用用户名提交登录表单
editor
以及密码editor
.请注意,“登录”链接已更改为“注销”。
单击“注销”链接。
分析¶
与许多web框架不同,Pyramid包含一个内置但可选的身份验证和授权安全模型。该安全系统旨在灵活并支持多种需求。在这个安全模型中,身份验证(您是谁)和授权(允许您做什么)是可插入的。为了一步一个脚印地学习,我们提供了一个识别用户并允许他们注销的系统。
在这个例子中,我们选择使用捆绑 pyramid.authentication.AuthTktCookieHelper
帮助程序将用户的登录状态存储在cookie中。我们在配置中启用了它,并在INI文件中提供了票证签名机密。
我们的视图类增加了一个登录视图。当你通过一个 GET
请求,它返回了一个登录表单。到达时通过 POST
,它根据 USERS
数据存储。
函数 hash_password
使用单向哈希算法,在用户密码上添加salt,通过 bcrypt
而不是以纯文本形式存储密码。这被认为是安全方面的“最佳实践”。
备注
还有其他的类库 bcrypt
如果是系统问题。只要确保库使用了一个被批准的安全存储密码的算法。
函数 check_password
将比较提交的密码和存储在数据库中的用户密码的两个哈希值。如果散列值是等效的,那么将对用户进行身份验证,否则身份验证将失败。
假设密码被验证,我们调用 pyramid.security.remember()
生成在响应中设置的cookie。随后的请求返回该cookie并标识用户。
在我们的模板中,我们获取了 logged_in
视图类中的值。我们使用这个来计算登录用户(如果有的话)。在模板中,我们可以选择显示匿名访问者的登录链接或登录用户的注销链接。
额外credit¶
我可以用数据库代替吗
USERS
对用户进行身份验证?一旦我登录,是否有任何以用户为中心的信息被阻塞到每个请求上?使用
import pdb; pdb.set_trace()
回答这个问题。