10- vue django restful framework 打造生鲜超市 -用户登录和手机注册(中)
Vue+Django REST framework實(shí)戰(zhàn)
搭建一個(gè)前后端分離的生鮮超市網(wǎng)站
Django rtf 完成 手機(jī)注冊和用戶登錄(中)
Json Web Token的原理
因?yàn)槲覀兊膁rf 的token auth有它的缺點(diǎn)。所以最常用的還是JWT的方式
以下內(nèi)容轉(zhuǎn)載于: http://lion1ou.win/2017/01/18/
http是一種無狀態(tài)的協(xié)議,前后兩次請(qǐng)求它會(huì)不知道這是從同一個(gè)人還是不同的人發(fā)的。
傳統(tǒng)方式: 采用session和cookie結(jié)合的方式
前后端分離的傳統(tǒng): 用戶信息生成token token 和對(duì)于的用戶id保存到數(shù)據(jù)庫或session中。我們的drf 的 token auth 就是這種。
接著把token傳給用戶,存入瀏覽器cookie。之后的請(qǐng)求帶上這個(gè)cookie,后端根據(jù)這個(gè)cookie值查詢用戶,驗(yàn)證過期的邏輯需要表里多一個(gè)字段,以及后端的邏輯驗(yàn)證。
問題: xss漏洞: cookie可以被js讀取。作為后端識(shí)別用戶的標(biāo)識(shí),cookie的泄露意味著用戶信息不再安全。特別是drf我們的token auth沒有過期時(shí)間
設(shè)置cookie時(shí)兩個(gè)更安全的選項(xiàng): httpOnly以及secure項(xiàng).
- httponly的不能被js讀取,瀏覽器會(huì)自動(dòng)加在請(qǐng)求header中
- secure就只通過https
httponly 問題。很容易被xsrf攻擊,因?yàn)閏ookie會(huì)默認(rèn)發(fā)出去。
如果將驗(yàn)證信息保存數(shù)據(jù)庫。每次都要查詢。保存session,加大了服務(wù)器端存儲(chǔ)壓力。
那我們可以不要服務(wù)器去查詢呢?
只要我們生成的token遵循一定的規(guī)律,比如使用對(duì)稱加密算法來加密id 形成token。
服務(wù)端只需要解密token 就能知道id。
- 這樣做的問題在于如果他知道你是怎么加密的。那么它可以通過這種加密方式偽造token
此時(shí)我們使用非對(duì)稱加密算法。
對(duì)稱加密,加密和解密使用的是同一個(gè)密鑰。服務(wù)器把token傳給用戶,以及用戶拿著token來服務(wù)器進(jìn)行解密。加密解密都在服務(wù)器端。
JWT 是一個(gè)開放標(biāo)準(zhǔn)(RFC 7519),它定義了一種用于簡潔,自包含的用于通信雙方之間以 JSON 對(duì)象的形式安全傳遞信息的方法。JWT 可以使用 HMAC 算法或者是 RSA 的公鑰密鑰對(duì)進(jìn)行簽名。它具備兩個(gè)特點(diǎn)
- 簡潔(Compact)
可以通過URL, POST 參數(shù)或者在 HTTP header 發(fā)送,因?yàn)閿?shù)據(jù)量小,傳輸速度快
- 自包含(Self-contained)
負(fù)載中包含了所有用戶所需要的信息,避免了多次查詢數(shù)據(jù)庫
mark頭部包含兩部分,token和加密算法。會(huì)進(jìn)行Base64加密。但是會(huì)很容易反解出來
- Header 頭部
頭部包含了兩部分,token 類型和采用的加密算法
{"alg": "HS256","typ": "JWT" }它會(huì)使用 Base64 編碼組成 JWT 結(jié)構(gòu)的第一部分。
- Payload 負(fù)載
這部分就是我們存放信息的地方了,你可以把用戶 ID 等信息放在這里,JWT 規(guī)范里面對(duì)這部分有進(jìn)行了比較詳細(xì)的介紹,常用的由 iss(簽發(fā)者),exp(過期時(shí)間),sub(面向的用戶),aud(接收方),iat(簽發(fā)時(shí)間)。
{"iss": "lion1ou JWT","iat": 1441593502,"exp": 1441594722,"aud": "www.example.com","sub": "lion1ou@163.com" }同樣的,它會(huì)使用 Base64 編碼組成 JWT 結(jié)構(gòu)的第二部分
- Signature 簽名
前面兩部分都是使用 Base64 進(jìn)行編碼的,即前端可以解開知道里面的信息。Signature 需要使用編碼后的 header 和 payload 以及我們提供的一個(gè)密鑰,然后使用 header 中指定的簽名算法(HS256)進(jìn)行簽名。簽名的作用是保證 JWT 沒有被篡改過。
可以解開就是指前端可以通過token拿到用戶的一部分非敏感信息。
JWT不會(huì)再保存數(shù)據(jù)了。a關(guān)注b,發(fā)郵件給b。b直接點(diǎn)擊帶回來串和action。就不需要登錄了。
設(shè)計(jì)用戶認(rèn)證和授權(quán)系統(tǒng)(獨(dú)立),以及單點(diǎn)登錄。
mark單點(diǎn)登錄,多個(gè)子域名通過一個(gè)統(tǒng)一的授權(quán)認(rèn)證接口進(jìn)行登錄。
https://github.com/GetBlimp/django-rest-framework-jwt
首先要安裝
pip install djangorestframework-jwt使用:
需要將jsonWebAuth加入到drf 的default auth class中
'rest_framework_jwt.authentication.JSONWebTokenAuthentication',對(duì)于用戶post過來的token進(jìn)行驗(yàn)證,將user取出來
- path中的配置
配置path中的jwt
# jwt的token認(rèn)證path('jwt-auth/', obtain_jwt_token ) mark mark curl -H "Authorization: JWT <your_token>" http://localhost:8000/protected-url/ markjwt的加密認(rèn)證方式可以參照jwt的源碼進(jìn)行學(xué)習(xí)。
vue和jwt接口調(diào)試
- vue中登錄的邏輯
vue中的登錄post到login接口
要么改前端,要么改后端,畢竟我們現(xiàn)在在寫后端。改后端url為login
# jwt的token認(rèn)證path('login/', obtain_jwt_token )前往login.vue中查看登錄的具體邏輯
login({username:this.userName, //當(dāng)前頁碼password:this.parseWord}).then((response)=> {console.log(response);//本地存儲(chǔ)用戶信息cookie.setCookie('name',this.userName,7);cookie.setCookie('token',response.data.token,7)//存儲(chǔ)在store// 更新store數(shù)據(jù)that.$store.dispatch('setInfo');//跳轉(zhuǎn)到首頁頁面this.$router.push({ name: 'index'})})獲取到當(dāng)前的用戶名和密碼 這個(gè)用戶名和密碼來自當(dāng)前的data()中
data中的值又通過v-model進(jìn)行了與輸入框中值的綁定(我猜的啊)
本地存儲(chǔ)設(shè)置了cookie的名字和值,token和值。并設(shè)置了7天過期
mark可以看到我們的vue數(shù)據(jù)中已經(jīng)有了name 和 token
setInfo會(huì)進(jìn)行實(shí)時(shí)數(shù)據(jù)同步更新的操作
[types.SET_INFO] (state) {state.userInfo = {name:cookie.getCookie('name'),token:cookie.getCookie('token')}console.log(state.userInfo);},我們的jwt 調(diào)用的是django自帶的auth與userProfile中數(shù)據(jù)進(jìn)行對(duì)比。而我們?nèi)绻褂檬謾C(jī)注冊,就會(huì)導(dǎo)致驗(yàn)證失敗。因?yàn)槟J(rèn)是用用戶名和密碼去查的。
自定義django用戶認(rèn)證函數(shù)
通過斷點(diǎn)測試可以成功的進(jìn)入了我們的這段邏輯。
JWT的過期時(shí)間設(shè)置
# 與drf的jwt相關(guān)的設(shè)置 JWT_AUTH = {'JWT_EXPIRATION_DELTA': datetime.timedelta(seconds=20),'JWT_AUTH_HEADER_PREFIX': 'JWT', }這里的header前綴我們要保持與前端的一致。
src/axios/index.js:
config.headers.Authorization = `Bearer ${store.state.userInfo.token}`;云片網(wǎng)發(fā)送短信驗(yàn)證碼
注冊會(huì)用到一些高級(jí)的Serializer。
寫一個(gè)接口:發(fā)送短信
form的驗(yàn)證。
可以有很多子賬號(hào),每個(gè)子賬號(hào)都會(huì)有一個(gè)api key 這個(gè)api key就會(huì)很重要。
發(fā)送短信驗(yàn)證碼這個(gè)key是必須要用到的。
文本短信 & 語音短信
發(fā)送國內(nèi)短信申請(qǐng)簽名。短信模板。
- 新建簽名(審核), 新建模板(可包含變量,審核)
api文檔中使用說明。
國內(nèi)短信api文檔: https://www.yunpian.com/doc/zh_CN/domestic/list.html
單條發(fā)送,批量發(fā)送(相同內(nèi)容,不同內(nèi)容)
https://www.yunpian.com/doc/zh_CN/domestic/single_send.html
參數(shù)。必填字段填過來。示例代碼
utils下yunpian.py
線上部署時(shí)一定要將自己服務(wù)器的ip加入ip白名單中。測試時(shí)搜索本機(jī)ip地址。
# encoding: utf-8 __author__ = 'mtianyan' __date__ = '2018/3/8 0008 09:28' import json import requestsclass YunPian(object):def __init__(self, api_key):self.api_key = api_keyself.single_send_url = "https://sms.yunpian.com/v2/sms/single_send.json"def send_sms(self, code, mobile):parmas = {"apikey": self.api_key,"mobile": mobile,"text": "【慕學(xué)生鮮】您的驗(yàn)證碼是{code}。如非本人操作,請(qǐng)忽略本短信".format(code=code)}response = requests.post(self.single_send_url, data=parmas)re_dict = json.loads(response.text)return re_dictif __name__ == "__main__":yun_pian = YunPian("apikey的值")yun_pian.send_sms("2017", "手機(jī)號(hào)碼")注意text內(nèi)容必須要與后臺(tái)已申請(qǐng)過簽名并審核通過的模板保持一致
drf實(shí)現(xiàn)發(fā)送短信驗(yàn)證碼接口
# 發(fā)送驗(yàn)證碼是創(chuàng)建model中一條記錄的操作 from rest_framework.mixins import CreateModelMixin用戶傳過來的手機(jī)號(hào)碼我們要進(jìn)行兩次驗(yàn)證:
- 是否有效
- 有沒有被注冊過
Serializer和django里的form modelform 是一樣的,所以這個(gè)驗(yàn)證我們把它放到我們的Serializer里面來做。
- 為什么不像goods中一樣使用serializers.ModelSerializer
因?yàn)槲覀僲odel中的code也是必填項(xiàng),而我們擁有的只有手機(jī)號(hào),所以會(huì)導(dǎo)致驗(yàn)證失敗
setting.py中
# 手機(jī)號(hào)碼正則表達(dá)式 REGEX_MOBILE = "^1[358]\d{9}$|^147\d{8}$|^176\d{8}$"users/serializers.py:
# encoding: utf-8 __author__ = 'mtianyan' __date__ = '2018/3/8 0008 09:41' import re from datetime import datetime, timedelta from VueDjangoFrameWorkShop.settings import REGEX_MOBILE from users.models import VerifyCode from rest_framework import serializers from django.contrib.auth import get_user_model User = get_user_model()class SmsSerializer(serializers.Serializer):mobile = serializers.CharField(max_length=11)def validate_mobile(self, mobile):"""驗(yàn)證手機(jī)號(hào)碼(函數(shù)名稱必須為validate_ + 字段名)"""# 手機(jī)是否注冊if User.objects.filter(mobile=mobile).count():raise serializers.ValidationError("用戶已經(jīng)存在")# 驗(yàn)證手機(jī)號(hào)碼是否合法if not re.match(REGEX_MOBILE, mobile):raise serializers.ValidationError("手機(jī)號(hào)碼非法")# 驗(yàn)證碼發(fā)送頻率one_mintes_ago = datetime.now() - timedelta(hours=0, minutes=1, seconds=0)# 添加時(shí)間大于一分鐘以前。也就是距離現(xiàn)在還不足一分鐘if VerifyCode.objects.filter(add_time__gt=one_mintes_ago, mobile=mobile).count():raise serializers.ValidationError("距離上一次發(fā)送未超過60s")return mobile然后views中class SmsCodeViewset(CreateModelMixin, viewsets.GenericViewSet):
重寫CreateModelMixin中的create方法
原本的create方法
def create(self, request, *args, **kwargs):serializer = self.get_serializer(data=request.data)serializer.is_valid(raise_exception=True)self.perform_create(serializer)headers = self.get_success_headers(serializer.data)return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)serializer.is_valid(raise_exception=True)有效性驗(yàn)證失敗會(huì)直接拋異常。
被drf捕捉到返回400狀態(tài)碼。
其中的APIKEY需要我們添加到setting.py中
# 云片網(wǎng)設(shè)置 APIKEY = 'apikey值'生成四位數(shù)的驗(yàn)證碼值
def generate_code(self):"""生成四位數(shù)字的驗(yàn)證碼"""seeds = "1234567890"random_str = []for i in range(4):random_str.append(choice(seeds))return "".join(random_str)改寫后的自定義方法:
def create(self, request, *args, **kwargs):serializer = self.get_serializer(data=request.data)serializer.is_valid(raise_exception=True)mobile = serializer.validated_data["mobile"]yun_pian = YunPian(APIKEY)code = self.generate_code()sms_status = yun_pian.send_sms(code=code, mobile=mobile)if sms_status["code"] != 0:return Response({"mobile":sms_status["msg"]}, status=status.HTTP_400_BAD_REQUEST)else:code_record = VerifyCode(code=code, mobile=mobile)code_record.save()return Response({"mobile":mobile}, status=status.HTTP_201_CREATED) mark mark將返回的json在yunpian中l(wèi)oads成dict
然后取出dict中的code和msg進(jìn)行判斷與返回。我們不需要向前端返回status。而是遵循restful api的規(guī)范。http狀態(tài)碼即可區(qū)分成功或失敗。消息并不代表。
發(fā)送成功之后再保存驗(yàn)證碼
調(diào)試是否正確
調(diào)試之前配置好對(duì)應(yīng)的url
from users.views import SmsCodeViewset # 配置codes的url router.register(r'codes', SmsCodeViewset, base_name="codes")http://127.0.0.1:8000/codes/
mark mark返回中既設(shè)置了400的http code 又有和form類似的字段信息錯(cuò)誤
字段名稱 : 數(shù)組(告訴你該字段的錯(cuò)誤)
發(fā)送成功(201)
mark總結(jié)
以上是生活随笔為你收集整理的10- vue django restful framework 打造生鲜超市 -用户登录和手机注册(中)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Windows Vista是什么
- 下一篇: 【雕爷学编程】Arduino动手做(2)