day78:luffy:前端對於token的認證&滑動驗證碼的實現
- 2020 年 10 月 29 日
- 筆記
- PythonS31-筆記, Python全棧31期-筆記
目錄
1.前端對於token的認證
上文我們實現了對於前端能夠通過token是否存在來判斷用戶是否登錄,傳送門: token對於登錄狀態的判斷
對於token,不僅要判斷token是否存在,而且要判斷token是否有效
所以接下來我們做的事情:就是驗證token是否真的有效
驗證token是否有效
1.驗證token有效需要引入verify_jwt_token
users/urls.py
from rest_framework_jwt.views import obtain_jwt_token, verify_jwt_token from . import views from django.urls import path urlpatterns = [ ...... path(r'verify/', verify_jwt_token), ]
可以用來測試如果token過期是否還能登錄
import datetime JWT_AUTH = { 'JWT_EXPIRATION_DELTA': datetime.timedelta(days=1), }
3.drf測試
POST /users/verify 輸入token值,獲取到token值,用戶名,id
4.改寫check_login函數
改寫之前寫的check_login函數,由原來的判斷token是否存在–>校驗token
並且將check_login函數移動至setting.js作為公共函數 ,因為很多組件都需要用到這個功能
setting.js
// 實現思路:獲取token值,並將token值POST提交到/users/verify進行驗證 export default { Host:"//www.lyapi.com:8001",// server address check_login(ths){ let token = localStorage.token || sessionStorage.token; console.log('>>>>>',token); ths.$axios.post(`${this.Host}/users/verify/`,{ token:token, }).then((res)=>{ ths.token = token; }).catch((error)=>{ ths.token = false; }) } }
Vheader.vue
// vheader組件執行一下方法 created(){ this.get_nav_data(); this.$settings.check_login(this); },
2.滑動驗證碼
1.滑動驗證碼實現的原理
前端的驗證碼時如何生成的
其實實際上是前端是需要後端來獲取滑動驗證碼的
2.滑動驗證碼的代碼實現
1.騰訊防水牆的appid和secret key放到dev.py配置文件中
dev.py
# 防水牆配置 FSQ = { 'appid':'2080330111', 'app_serect_key':'07v2KHaK2CMY8tkl_aOrbcA**', }
2.web前端接入騰訊防水牆的js文件
index.html
<!-- index.html --> <script src="//ssl.captcha.qq.com/TCaptcha.js"></script>
3.點擊登錄按鈕,觸發登錄按鈕綁定的LoginHandle事件
// 通過這兩行代碼就可以實現點擊登錄按鈕,出現滑動驗證碼圖片了 var captcha1 = new TencentCaptcha('2080330111',function(res){}); captcha1.show(); // res:滑動成功或者失敗的響應結果 console.log(res) // {appid:xxx,bizState:xxx,randstr:xxx,ret:0,ticket:xxx}
返回結果字段說明如下:
值類型 | 說明 | |
---|---|---|
ret | Int | 驗證結果,0:驗證成功。2:用戶主動關閉驗證碼。 |
ticket | String | 驗證成功的票據,當且僅當 ret = 0 時 ticket 有值。 |
appid | String | 場景 ID。 |
bizState | Any | 自定義透傳參數。 |
randstr | String | 本次驗證的隨機串,請求後台接口時需帶上。 |
4.思考
用戶名和密碼發到後端了,是否代表着滑動驗證就通過了嗎?
並不是,所以驗證碼的數據也要在後台校驗—>ticket值
5.檢查驗證碼票據結果
如何驗證滑動驗證碼是否滑動成功?
也可以同時校驗 用戶名 密碼 滑動驗證碼數據
6.前端滑動驗證碼的代碼實現
methods:{ loginHandle(){ var captcha1 = new TencentCaptcha('2080330111', (res) =>{ if (res.ret === 0){ // 滑動成功後才能夠發post請求 this.$axios.post(`${this.$settings.Host}/users/login/`,{ username:this.username, password:this.password, // 將ticket和randstr也發送到後台去,讓後台去驗證滑動是否成功 ticket:res.ticket, randstr:res.randstr, }).then((res)=>{ console.log(res); // 判斷是臨時登錄還是永久登錄 if (this.remember){ localStorage.token = res.data.token; localStorage.username = res.data.username; localStorage.id = res.data.id; sessionStorage.removeItem('token'); sessionStorage.removeItem('username'); sessionStorage.removeItem('id'); }else { sessionStorage.token = res.data.token; sessionStorage.username = res.data.username; sessionStorage.id = res.data.id; localStorage.removeItem('token'); localStorage.removeItem('username'); localStorage.removeItem('id'); } }).catch((error)=>{ this.$alert('用戶名或者密碼錯誤', '登錄失敗', { confirmButtonText: '確定', }); }) } }); captcha1.show(); // 顯示驗證碼 } },
昨天我們使用的obtain_jwt_token:只能做用戶名和密碼的驗證,無法實現對滑動成功的驗證,
所以我們需要改寫代碼添加字段,讓jwt也能夠實現對滑動成功的驗證
7.重寫jwt代碼來實現對滑動成功的認證
users/urls.py
# users/urls.py from rest_framework_jwt.views import verify_jwt_token from . import views from django.urls import path urlpatterns = [
# 因為我們要改寫jwt了,所以不能繼承原來的obtain_jwt_token了 # 我們要自己改寫這部分的視圖函數來實現對於滑動成功的認證 path(r'login/', views.CustomLoginView.as_view()), path(r'verify/', verify_jwt_token), ]
users/views.py
# users/views.py from django.shortcuts import render from rest_framework_jwt.views import ObtainJSONWebToken from lyapi.apps.users.serializers import CustomeSerializer class CustomLoginView(ObtainJSONWebToken): serializer_class = CustomeSerializer
users/serializers.py
# users/serializers.py from rest_framework_jwt.serializers import JSONWebTokenSerializer from rest_framework import serializers from rest_framework_jwt.compat import get_username_field, PasswordField from django.utils.translation import ugettext as _ from django.contrib.auth import authenticate, get_user_model from rest_framework_jwt.settings import api_settings User = get_user_model() jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER jwt_decode_handler = api_settings.JWT_DECODE_HANDLER jwt_get_username_from_payload = api_settings.JWT_PAYLOAD_GET_USERNAME_HANDLER class CustomeSerializer(JSONWebTokenSerializer): def __init__(self, *args, **kwargs): """ Dynamically add the USERNAME_FIELD to self.fields. """ super(JSONWebTokenSerializer, self).__init__(*args, **kwargs) # 重寫jwt自帶的序列化器,在原來的基礎上添加ticket和randstr self.fields[self.username_field] = serializers.CharField() self.fields['password'] = PasswordField(write_only=True) self.fields['ticket'] = serializers.CharField(write_only=True) self.fields['randstr'] = serializers.CharField(write_only=True) # 全局鉤子函數 def validate(self, attrs): credentials = { self.username_field: attrs.get(self.username_field), 'password': attrs.get('password'), 'ticket': attrs.get('ticket'), 'randstr': attrs.get('randstr'), } if all(credentials.values()): user = authenticate(self.context['request'],**credentials) # self.context['request']當前請求的request對象 if user: if not user.is_active: msg = _('User account is disabled.') raise serializers.ValidationError(msg) payload = jwt_payload_handler(user) return { 'token': jwt_encode_handler(payload), 'user': user } else: msg = _('Unable to log in with provided credentials.') raise serializers.ValidationError(msg) else: msg = _('Must include "{username_field}" and "password".') msg = msg.format(username_field=self.username_field) raise serializers.ValidationError(msg)
users/utils.py
# users/utils.py class CustomeModelBackend(ModelBackend): ''' ' 'ticket': attrs.get('ticket'), 'randstr': attrs.get('randstr'), ''' def authenticate(self, request, username=None, password=None, **kwargs): try: user_obj = get_user_obj(username) ticket = kwargs.get('ticket') userip = request.META['REMOTE_ADDR'] randstr = kwargs.get('randstr') params = { # 騰訊防水牆需要的一些參數 "aid": settings.FSQ.get('appid'), "AppSecretKey": settings.FSQ.get('app_serect_key'), "Ticket": ticket, "Randstr": randstr, "UserIP": userip } params = urlencode(params).encode() # 轉換成bytes類型 url = settings.FSQ.get('URL') f = urlopen(url, params) # 發送請求,將數據發送出去,並返回滑動是否成功數據 content = f.read() # 獲取數據 res = json.loads(content) # json反序列化 print(res) # {'response': '1', 'evil_level': '0', 'err_msg': 'OK'} if res.get('response') != '1': # 如果滑動失敗 return None if user_obj: # 如果用戶名存在 if user_obj.check_password(password): # 如果密碼正確 return user_obj # 返回用戶名對象 else: # 如果用戶名或密碼錯誤 return None except Exception: logger.error('驗證過程代碼有誤,請聯繫管理員') return None