day78:luffy:前端對於token的認證&滑動驗證碼的實現

目錄

1.前端對於token的認證

2.滑動驗證碼

  1.滑動驗證碼實現的原理

  2.滑動驗證碼的代碼實現

    1.配置文件

    2.前端實現:Login.vue

    3.後端實現:改寫jwt代碼

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),

]

2.在dev.py中可以設置token的過期時間

可以用來測試如果token過期是否還能登錄

import datetime
JWT_AUTH = {
    'JWT_EXPIRATION_DELTA': datetime.timedelta(days=1),
}

3.drf測試

POST /users/login 輸入用戶名和密碼,獲取到token值

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