最近項目中使用django-rest-framework作為后臺框架溯警,給客戶端返回json結(jié)果。
在用戶驗證方面用到token驗證蝴罪,這是一種安卓/iso/..手機(jī)客戶端常用的,方便的驗證方式。
原理是客戶端給我發(fā)一段字符串,這段字符串是用戶在注冊驴党,登入的時候、服務(wù)器生成的获茬,并關(guān)聯(lián)到用戶港庄。保存到數(shù)據(jù)庫,然后返回給客戶端恕曲,客戶端之后呢鹏氧,就可以憑借這個字符串來確認(rèn)“我是我,不是別人”佩谣。而不用每次驗證都要通過賬號密碼把还。 _ _ _
django-rest-framework 有一套默認(rèn)的token驗證機(jī)制dfs token驗證 具體用法不再細(xì)講了,官方文檔寫得很清楚茸俭。
但是筆者發(fā)現(xiàn)一個問題吊履,這個token驗證機(jī)制存的token,一旦生成就保持不變调鬓。這樣就引發(fā)一些問題艇炎,萬一某人拿到你的token不就為所欲為了嗎,就像別人拿到你的密碼一樣腾窝。
解決方案: 給token設(shè)置過期時間缀踪,超過存活時間居砖,這段token不再具有驗證功能,每次用戶重新登入驴娃,刷新token(這段新token的有存活時間)奏候。這樣,重新登入后托慨,你的token更新了鼻由,某些居心不良的人即便拿著之前搶來的token也沒用。stackoverflow上已經(jīng)有了token過期時間的討論厚棵。 參考他們的代碼我這樣寫蕉世。
改進(jìn)
#coding=utf-8 auth.py
from django.utils.translation import ugettext_lazy as _
from django.core.cache import cache
import datetime
from rest_framework.authentication import BaseAuthentication
from rest_framework import exceptions
from account.models import Token
from rest_framework import HTTP_HEADER_ENCODING
def get_authorization_header(request):
"""
Return request's 'Authorization:' header, as a bytestring.
Hide some test client ickyness where the header can be unicode.
"""
auth = request.META.get('HTTP_AUTHORIZATION', b'')
if isinstance(auth, type('')):
# Work around django test client oddness
auth = auth.encode(HTTP_HEADER_ENCODING)
return auth
class ExpiringTokenAuthentication(BaseAuthentication):
model = Token
def authenticate(self, request):
auth = get_authorization_header(request)
if not auth:
return None
try:
token = auth.decode()
except UnicodeError:
msg = _('Invalid token header. Token string should not contain invalid characters.')
raise exceptions.AuthenticationFailed(msg)
return self.authenticate_credentials(token)
def authenticate_credentials(self, key):
try:
token = self.model.objects.get(key=key)
except self.model.DoesNotExist:
raise exceptions.AuthenticationFailed('認(rèn)證失敗')
if not token.user.is_active:
raise exceptions.AuthenticationFailed('用戶被禁止')
utc_now = datetime.datetime.utcnow()
if token.created < utc_now - datetime.timedelta(hours=24 * 14):
raise exceptions.AuthenticationFailed('認(rèn)證信息過期')
def authenticate_header(self, request):
return 'Token'
還要配置settings文件
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'yourmodule.auth.ExpiringTokenAuthentication',
),
}
再改進(jìn) 使用了cache緩存對token和關(guān)聯(lián)的用戶進(jìn)行了緩存,因為token驗證經(jīng)常需要從數(shù)據(jù)庫讀取婆硬,加入緩存狠轻,大幅提高速度。
def authenticate_credentials(self, key):
token_cache = 'token_' + key
cache_user = cache.get(token_cache)
if cache_user:
return cache_user # 首先查看token是否在緩存中彬犯,若存在向楼,直接返回用戶
try:
token = self.model.objects.get(key=key)
except self.model.DoesNotExist:
raise exceptions.AuthenticationFailed('認(rèn)證失敗')
if not token.user.is_active:
raise exceptions.AuthenticationFailed('用戶被禁止')
utc_now = datetime.datetime.utcnow()
if token.created < utc_now - datetime.timedelta(hours=24 * 14): # 設(shè)定存活時間 14天
raise exceptions.AuthenticationFailed('認(rèn)證信息過期')
if token:
token_cache = 'token_' + key
cache.set(token_cache, token.user, 24 * 7 * 60 * 60) # 添加 token_xxx 到緩存
return (token.user, token)
我的login函數(shù)是這樣寫的
@api_view(['POST'])
def login_views(request):
receive = request.data
if request.method == 'POST':
username = receive['username']
password = receive['password']
user = auth.authenticate(username=username, password=password)
if user is not None and user.is_active:
# update the token
token = Token.objects.get(user=user)
token.delete()
token = Token.objects.create(user=user)
user_info = UserInfo.objects.get(user=user)
serializer = UserInfoSerializer(user_info)
response = serializer.data
response['token'] = token.key
return json_response({
"result": 1,
"user_info":response, # response contain user_info and token
})
else:
try:
User.objects.get(username=username)
cause = u'密碼錯誤'
except User.DoesNotExist:
cause = u'用戶不存在'
return json_response({
"result": 0,
"cause":cause,
})