Python實戰-編寫web-app-day10-用戶註冊和登錄頁面

註冊頁面

用戶管理是絕大部分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>



分享到:


相關文章: