註冊頁面
用戶管理是絕大部分Web網站都需要解決的問題。用戶管理涉及到用戶註冊和登錄。用戶註冊相對簡單,我們可以先通過API把用戶註冊這個功能實現了:
<code>## 用戶註冊API @post('/api/users') async def api_register_user(*, email, name, passwd): if not name or not name.strip(): raise APIValueError('name') if not email or not _RE_EMAIL.match(email): raise APIValueError('email') if not passwd or not _RE_SHA1.match(passwd): raise APIValueError('passwd') users = await User.findAll('email=?', [email]) if len(users) > 0: raise APIError('register:failed', 'email', 'Email is already in use.') uid = next_id() sha1_passwd = '%s:%s' % (uid, passwd) user = User(id=uid, name=name.strip(), email=email, passwd=hashlib.sha1(sha1_passwd.encode('utf-8')).hexdigest(), image='http://www.gravatar.com/avatar/%s?d=mm&s=120' % hashlib.md5(email.encode('utf-8')).hexdigest()) await user.save() # make session cookie: r = web.Response() r.set_cookie(COOKIE_NAME, user2cookie(user, 86400), max_age=86400, httponly=True) user.passwd = '******' r.content_type = 'application/json' r.body = json.dumps(user, ensure_ascii=False).encode('utf-8') return r/<code>
注意用戶口令是客戶端傳遞的經過SHA1計算後的40位Hash字符串,所以服務器端並不知道用戶的原始口令。有了父模板,有了用戶註冊和登錄驗證的API,構建註冊和登錄頁面就十分容易了。首先我們來構建用戶註冊頁面register.html(在路徑www/templates下):
<code> {% extends '__base__.html' %} {% block title %}Register/註冊{% endblock %} {% block beforehead %} {% endblock %} {% block content %}REGISTER/歡迎註冊!
NAME/名字:EMAIL/電子郵件:
PASSWORD/輸入口令:
REPEAT PASSWORD/重複口令:
/<code>
用戶登錄比用戶註冊複雜。由於HTTP協議是一種無狀態協議,而服務器要跟蹤用戶狀態,就只能通過cookie實現。大多數Web框架提供了Session功能來封裝保存用戶狀態的cookie。
Session的優點是簡單易用,可以直接從Session中取出用戶登錄信息。
Session的缺點是服務器需要在內存中維護一個映射表來存儲用戶登錄信息,如果有兩臺以上服務器,就需要對Session做集群,因此,使用Session的Web App很難擴展。
我們採用直接讀取cookie的方式來驗證用戶登錄,每次用戶訪問任意URL,都會對cookie進行驗證,這種方式的好處是保證服務器處理任意的URL都是無狀態的,可以擴展到多臺服務器。
由於登錄成功後是由服務器生成一個cookie發送給瀏覽器,所以,要保證這個cookie不會被客戶端偽造出來。
實現防偽造cookie的關鍵是通過一個單向算法(例如SHA1),舉例如下:
當用戶輸入了正確的口令登錄成功後,服務器可以從數據庫取到用戶的id,並按照如下方式計算出一個字符串:
“用戶id” + “過期時間” + SHA1(“用戶id” + “用戶口令” + “過期時間” + “SecretKey”)當瀏覽器發送cookie到服務器端後,服務器可以拿到的信息包括:
用戶id
過期時間
SHA1值
如果未到過期時間,服務器就根據用戶id查找用戶口令,並計算:
SHA1(“用戶id” + “用戶口令” + “過期時間” + “SecretKey”)並與瀏覽器cookie中的哈希進行比較,如果相等,則說明用戶已登錄,否則,cookie就是偽造的。
這個算法的關鍵在於SHA1是一種單向算法,即可以通過原始字符串計算出SHA1結果,但無法通過SHA1結果反推出原始字符串。
所以登錄API可以實現如下:
<code># 用戶登錄驗證API @post('/api/authenticate') async def authenticate(*, email, passwd): if not email: raise APIValueError('email', 'Invalid email.') if not passwd: raise APIValueError('passwd', 'Invalid password.') users = await User.findAll('email=?', [email]) if len(users) == 0: raise APIValueError('email', 'Email not exist.') user = users[0] # check passwd: sha1 = hashlib.sha1() sha1.update(user.id.encode('utf-8')) sha1.update(b':') sha1.update(passwd.encode('utf-8')) if user.passwd != sha1.hexdigest(): raise APIValueError('passwd', 'Invalid password.') # authenticate ok, set cookie: r = web.Response() r.set_cookie(COOKIE_NAME, user2cookie(user, 86400), max_age=86400, httponly=True) user.passwd = '******' r.content_type = 'application/json' r.body = json.dumps(user, ensure_ascii=False).encode('utf-8') return r /<code>
根據api編寫登錄頁面更為簡單,在www/templates下編寫signin.html:
<code> {% extends '__base__.html' %} {% block title %}Signin/登陸{% endblock %} {% block beforehead %} {% endblock %} {% block content %}SIGNIN/歡迎登陸!
EMAIL/電子郵箱:PASSWORD/輸入口令:
/<code>