1. 程式人生 > >token的使用基於Python的登入註冊功能

token的使用基於Python的登入註冊功能

使用基於 Token 的身份驗證方法,在服務端不需要儲存使用者的登入記錄。大概的流程是這樣的:

  • 客戶端使用使用者名稱跟密碼請求登入
  • 服務端收到請求,去驗證使用者名稱與密碼
  • 驗證成功後,服務端會簽發一個 Token,再把這個 Token 傳送給客戶端
  • 客戶端收到 Token 以後可以把它儲存起來,比如放在 Cookie 裡或者 Local Storage 裡
  • 客戶端每次向服務端請求資源的時候需要帶著服務端簽發的 Token
  • 服務端收到請求,然後去驗證客戶端請求裡面帶著的 Token,如果驗證成功,就向客戶端返回請求的資料

1.jwt的資料返回設定

重寫jwt_response_payload_handler 可以實現 返回token和其他資料

登入成功後 檢視除錯工具中Resource 中 localstorage ,其中會存放token


    ''' utils/users.py '''
    
    # user 就是我們認證之後的user

    def jwt_response_payload_handler(token, user=None, request=None):

        return {
            'token':token,
            'username':user.username,
            'user_id':user.id
        }
''' settings.py''' REST_FRAMEWORK = { 'DEFAULT_AUTHENTICATION_CLASSES': ( 'rest_framework_jwt.authentication.JSONWebTokenAuthentication', 'rest_framework.authentication.SessionAuthentication', 'rest_framework.authentication.BasicAuthentication', )
, } # 設定jwt 返回資料的函式 JWT_AUTH = { 'JWT_RESPONSE_PAYLOAD_HANDLER':'utils.user.jwt_response_payload_handler' }

2.註冊成功之後返回token

筆者出現過的bug:在create方法中沒有給user物件新增token欄位時,註冊賬號,之後在create方法中新增token欄位後,用該賬號登入時,其物件並沒有token欄位, 所以會請求不到token。

確認註冊完成後 生成token
即在資料入庫後 註冊完成 生成token
點選註冊完成後 觸發的一系列事件
其中在create中 save入庫後 即註冊完成

生成token 的程式碼:


    def create():

        ...

        # 此處注意導包  很容易匯入錯誤的包
        from rest_framework_jwt.settings import api_settings

        # 需要獲取2個方法
        jwt_payload_handler = api.settings.JWT_PAYLOAD_HANDLER
        jwt_encode_handler = api.settings.JWT_ENCODE_HANDLER

        # 1.jwt_payload_handler需要將使用者的資訊傳遞給這個函式,函式自己會獲取user中的資料
        #   獲取之後,會生成一個字串
        payload = jwt_payload_handler(user)
        # 2.需要對payload 進行編碼  編碼之後的才是token
        token = jwt_encode_handler(payload)

        # 序列化的時候返回token
        # 序列化器中有該欄位  但模型類中沒有這個欄位
        # 所以動態新增屬性
        user.token = token


        return user


    ######針對上面  user.token = token  舉的例子   #########

    def Person(object):
        name = ''
    
    p = Person()
    p.name = 'it'
    print(p.name)

    # 這個可以列印  動態新增屬性  只對當前物件起作用
    p.age = 20
    print(p.age)

    # 這個不能列印
    p2 = Person()
    print(p2.age)






    
    ''' serializer.py  ''''

    class RegisterCreateSerializer(serializers.ModelSerializer):

        ...

        # 新增新欄位
        token = serializers.CharField(label='token',read_only=True)
        # token 只是應用於 序列化(物件轉換為JSON)操作的時候使用,所以只能讀  需要新增read_only
        # 不加read_only會報錯  提示 token欄位為必填項
        # read_only 表示只在序列化的時候該欄位起作用
        class Meta:
                model = Users
                fields = ('id', 'username', 'password', 'password2', 'sms_code', 'mobile', 'allow', 'token')  # 增加token
       


	''' js/register.js '''

在前端檔案中增加儲存token


	var vm = new Vue({
	    ...
	    methods: {
	        on_submit: function(){
	            axios.post(...)
	                .then(response => {
	                    // 記錄使用者的登入狀態
	                    sessionStorage.clear();
	                    localStorage.clear();
	                    localStorage.token = response.data.token;
	                    localStorage.username = response.data.username;
	                    localStorage.user_id = response.data.id;
	                    location.href = '/index.html';
	                })
	                .catch(...)
	        }
	    }
	})

3.多賬號登入 認證

增加支援使用者名稱與手機號均可作為登入賬號

JWT擴充套件的登入檢視,在收到使用者名稱與密碼時,也是呼叫Django的認證系統中提供的authenticate()來檢查使用者名稱與密碼是否正確。

我們可以通過修改Django認證系統的認證後端(主要是authenticate方法)來支援登入賬號既可以是使用者名稱也可以是手機號。

修改Django認證系統的認證後端需要繼承django.contrib.auth.backends.ModelBackend,並重寫authenticate方法。

authenticate(self, request, username=None, password=None, **kwargs)方法的引數說明:

request 本次認證的請求獨享
username 本次認證提供的使用者賬號
password 本次認證提供的密碼
我們想要讓使用者既可以以使用者名稱登入,也可以以手機號登入,那麼對於authenticate方法而言,username引數即表示使用者名稱或者手機號。

重寫authenticate方法的思路:

根據username引數查詢使用者User物件,username引數可能是使用者名稱,也可能是手機號

若查詢到User物件,呼叫User物件的check_password方法檢查密碼是否正確


    ''' utils/users.py '''

    def get_user_by_account(username):
        # 根據使用者輸入的資訊  判斷使用者輸入的是手機號還是使用者名稱
        try:
            if re.match('1[3-9]\d{9}',username):
                # 使用者名稱滿足手機號的規則
                user = Users.objects.get(mobile=username)
            else:
                user = Users.objects.get(username=username)
        except Users.DoesNotExist:
            user = None
        
        return user

    class UsernameMobileAuthModelBackend(ModelBackend):

        def authenticate(self,request,username=None,password=None,**kwargs):
            
            # 1. 先判斷輸入型別  再進行查詢
            user = get_user_by_account(username)

            # 2. 校驗
            if user is not None and user.check_password(password):

                return user
            
            return None

    class SettingsBackend(object):
        """
        Authenticate against the settings ADMIN_LOGIN and ADMIN_PASSWORD.

        Use the login name and a hash of the password. For example:

        ADMIN_LOGIN = 'admin'
        ADMIN_PASSWORD = 'pbkdf2_sha256$30000$Vo0VlMnkR4Bk$qEvtdyZRWTcOsCnI/oQ7fVOu1XAURIZYoOZ3iq8Dr4M='
        """

        def authenticate(self, request, username=None, password=None):
            # 1. 根據使用者在使用者名稱的地方輸入的內容進行判斷,如果使用者輸入的是手機號,我們就根據手機號查詢使用者
            # 否則就根據 使用者名稱進行查詢
            user = get_user_by_account(username)

            # 2如果使用者查詢出來,我們再校驗密碼
            if user is not None and user.check_password(password):
                return user
            return None

        def get_user(self, user_id):
            try:
                return Users.objects.get(pk=user_id)
            except Users.DoesNotExist:
                return None

需要改變認證後端的類
在配置檔案中告知Django使用我們自定義的認證後端


    '''settings.py '''

    # 改變認證後端的類
    AUTHENTICATION_BACKENDS = [
        # 'utils.users.UsernameMobileAuthModelBackend',
        'utils.users.SettingsBackend'
    ]

        # 不加 這個 會報 
        # {
        #   "non_field_errors": [
        #     "無法使用提供的認證資訊登入。"
        #   ]
        # }

Django REST framework JWT提供了登入獲取token的檢視,可以直接使用,在users應用中的urls新增路由資訊

  
    ''' urls.py '''
    
    from rest_framework_jwt.views import obtain_jwt_token
  
    urlpatterns = [
        #新增登入認證
        url(r'^auths/',obtain_jwt_token),
    ]