最近項目中使用django-rest-framework作為后台框架,給客戶端返回json結果。
在用戶驗證方面用到token驗證,這是一種安卓/iso/..手機客戶端常用的,方便的驗證方式。
原理是客戶端給我發一段字符串,這段字符串是用戶在注冊,登入的時候、服務器生成的,並關聯到用戶。保存到數據庫,然后返回給客戶端,客戶端之后呢,就可以憑借這個字符串來確認“我是我,不是別人”。而不用每次驗證都要通過賬號密碼。 _ _ _
django-rest-framework 有一套默認的token驗證機制dfs token驗證 具體用法不再細講了,官方文檔寫得很清楚。
但是筆者發現一個問題,這個token驗證機制存的token,一旦生成就保持不變。這樣就引發一些問題,萬一某人拿到你的token不就為所欲為了嗎,就像別人拿到你的密碼一樣。
解決方案: 給token設置過期時間,超過存活時間,這段token不再具有驗證功能,每次用戶重新登入,刷新token(這段新token的有存活時間)。這樣,重新登入后,你的token更新了,某些居心不良的人即便拿着之前搶來的token也沒用。stackoverflow上已經有了token過期時間的討論。 參考他們的代碼我這樣寫。
改進
#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('認證失敗') 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('認證信息過期') def authenticate_header(self, request): return 'Token'
還要配置settings文件
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': ( 'yourmodule.auth.ExpiringTokenAuthentication', ), }
再改進 使用了cache緩存對token和關聯的用戶進行了緩存,因為token驗證經常需要從數據庫讀取,加入緩存,大幅提高速度。
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('認證失敗') if not token.user.is_active: raise exceptions.AuthenticationFailed('用戶被禁止') utc_now = datetime.datetime.utcnow() if token.created < utc_now - datetime.timedelta(hours=24 * 14): # 設定存活時間 14天 raise exceptions.AuthenticationFailed('認證信息過期') if token: token_cache = 'token_' + key cache.set(token_cache, token.user, 24 * 7 * 60 * 60) # 添加 token_xxx 到緩存 return (token.user, token)
我的login函數是這樣寫的
@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, })
作者:不_一
鏈接:https://www.jianshu.com/p/e0a206212df4
來源:簡書
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。