python筆記五 django headers帶jwt實(shí)現(xiàn)自動(dòng)登錄,密碼加密存儲(chǔ)

python筆記一 django搭建服務(wù)器全棧開(kāi)發(fā)
python筆記二 django自帶后臺(tái)管理系統(tǒng)、模版渲染以及使用mysql數(shù)據(jù)庫(kù)
python筆記三 react + django 實(shí)現(xiàn)前后端分離
python筆記四 REST Framework 實(shí)現(xiàn) restful api
python筆記五 django headers帶jwt實(shí)現(xiàn)自動(dòng)登錄,密碼加密存儲(chǔ)

依舊是react + django,腳手架怎么用就不說(shuō)了,看一下初始化之后的結(jié)構(gòu)。


image.png
需要安裝的依賴

python:

rest_framework
django-cors-headers
PyJWT

react:

axios
store

自行安裝

一、前端部分

src目錄下新建etc > settings.json

image.png

settings.json添加服務(wù)端接口的url,內(nèi)容如下:

{
  "url": "http://127.0.0.1:8000/"
}

src目錄下新建一個(gè)server.js,將所有交互和token的操作封裝在這里,如果交互較多的話可以把axios單獨(dú)封裝,里邊進(jìn)行token的存儲(chǔ)與讀取,這里只做交互。

import axios from 'axios';
import store from 'store';   //用于本地存儲(chǔ)token
import settings from './etc/settings';

const Url = settings.url;

const registerServer = async (data) => {
  let res = await axios.post (`${Url}/register/`, data);
  console.log(res);
  return res.data;
}

const loginServer = async (data) => {
  let res = await axios.post (`${Url}/login/`, data);
  console.log(res);
  return res.data;
}

const allUsersServer = async () => {
  let res = await axios.get (`${Url}/all_users/`);
  console.log(res);
  return res.data;
}

export {
  registerServer,
  loginServer,
  allUsersServer
}

修改App.js,刪除無(wú)用代碼,寫(xiě)兩個(gè)輸入框用于輸入賬號(hào)密碼,三個(gè)按鈕,注冊(cè)登錄和一個(gè)獲取所有用戶的按鈕,用來(lái)驗(yàn)證token。

import React, { Component } from 'react';
import './App.css';

import {
  registerServer,
  loginServer,
  allUsersServer
} from './server';

class App extends Component {
  constructor (props) {
    super(props);

    this.state = {
      username: "",
      password: "",
      users: []
    }

    this.handlerChange = this.handlerChange.bind(this);
    this.register = this.register.bind(this);
    this.login = this.login.bind(this);
    this.allUsers = this.allUsers.bind(this);
  }

  handlerChange (k, e) {
    this.setState({
      [k]: e.target.value
    });
  }

  async register () {
    let data = {
      username: this.state.username,
      password: this.state.password
    }
    let res = await registerServer(data);
    console.log(res);
  }

  async login () {
    let data = {
      username: this.state.username,
      password: this.state.password
    }
    let res = await loginServer(data);
    console.log(res);
  }

  async allUsers () {
    let res = await allUsersServer();
    console.log(res);
  }

  render() {
    return (
      <div className="App">
        <div>
          <input onChange={(e) => {this.handlerChange('username', e)}} placeholder="username" />
        </div>
        <div>
          <input onChange={(e) => {this.handlerChange('password', e)}} placeholder="password" />
        </div>
        <div>
          <button onClick={this.register}>register</button>
          <button onClick={this.login}>login</button>
        </div>
        <div>
          <button onClick={this.allUsers}>all users</button>
        </div>
      </div>
    );
  }
}

export default App;

python跑起來(lái),測(cè)試一下,分別點(diǎn)一下三個(gè)按鈕。

image.png

沒(méi)有問(wèn)題,前端部分除了token已經(jīng)做好了,下一步是python部分。
二、服務(wù)端
settings.py
image.png

image.png

models.py新建一個(gè)Users

from django.db import models
import uuid

# Create your models here.
class Users(models.Model):
    id = models.UUIDField(primary_key = True, default = uuid.uuid1(), editable = False, null = False)
    username = models.CharField(max_length = 10, null = False)
    password = models.CharField(max_length = 70, null = False)
    time = models.DateTimeField(auto_now = True, null = False)

server目錄下新建一個(gè)序列化器serializers.py

from rest_framework import serializers
from server.models import Users

class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = Users
        fields = ('id', 'username', 'password', 'time')

同步數(shù)據(jù)庫(kù)

python manage.py makemigrations
python manage.py migrate

views.py里新建一個(gè)視圖類,先寫(xiě)好接口,token下邊再做處理。

from django.shortcuts import render
from rest_framework import viewsets
from rest_framework.response import Response
from rest_framework.decorators import list_route
from django.contrib.auth.hashers import make_password, check_password   #密碼存儲(chǔ)加密和校驗(yàn)
from server.models import Users
from server.serializers import UserSerializer
import json
import uuid

# Create your views here.
class UserViewSet(viewsets.ModelViewSet):
    serializer_class = UserSerializer

    @list_route (methods = ['post'])
    def register (self, request):
        data = json.loads(request.body)
        # 注冊(cè)用戶名校驗(yàn)
        user = Users.objects.filter(username = data['username'])
        if len(user):
            res = {
                'success': False,
                'mess': '用戶名已注冊(cè)'
            }
            return Response(res)

        data['id'] = uuid.uuid1()
        data['password'] = make_password(data['password'])
        Users.objects.create(**data)
        res = {
            'success': True
        }
        return Response(res)

    @list_route(methods = ['post'])
    def login (self, request):
        data = json.loads(request.body)
        filter_user = Users.objects.filter(username = data['username'])
        if not len(filter_user):
            res = {
                'success': False,
                'mess': '用戶名未注冊(cè)'
            }
            return Response(res)
        user = UserSerializer(filter_user, many = True).data[0]
        check_pass_result = check_password(data['password'], user['password'])
        if not check_pass_result:
            res = {
                'success': False,
                'mess': '密碼錯(cuò)誤'
            }
            return Response(res)

        res = {
            'success': True,
            'data': user
        }
        return Response(res)

    @list_route(methods = ['get'])
    def all_users (self, request):
        users = UserSerializer(Users.objects.all(), many = True).data
        res = {
            'success': True,
            'data': users
        }
        return Response(res)

urls.py添加路由

from django.contrib import admin
from django.urls import path
from server.views import UserViewSet
from django.conf.urls import url, include
from rest_framework.routers import DefaultRouter

router = DefaultRouter()
router.register(r'', UserViewSet, base_name = 'users')

urlpatterns = [
    path('admin/', admin.site.urls),
    url('', include(router.urls))
]

測(cè)試一下

image.png

注冊(cè)一個(gè)用戶,返回true,重復(fù)注冊(cè)返回false
image.png

登錄
image.png

獲取所有用戶列表
image.png

接下來(lái)是重點(diǎn),生成token,并且添加到request和response的headers里

這里用到PyJWT,github地址:https://github.com/jpadilla/pyjwt
PyJWT有兩個(gè)方法encode生成token,decodetoken解析成payload
server目錄下新建一個(gè)token.py,用來(lái)封裝token。

import jwt
import time

def create_token (user):
    payload = {
        'username': user['username'],
        'id': user['id'],
        'time': user['time'],
        'iat': int(time.time()),
        'exp': int(time.time()) + 60          #過(guò)期時(shí)間60s
    }
    #secret自己設(shè)定,加密字符串,放在服務(wù)器
    token = jwt.encode(payload, 'secret', algorithm = 'HS256')
    return token

def verify_token (token):
    try:
        payload = jwt.decode(token, 'secret', algorithms = ['HS256'])
        #decode成payload,用payload和當(dāng)前時(shí)間戳重新生成一個(gè)token并返回
        token = create_token(payload)
        return token
    except:
        return False

封裝好后在views.py調(diào)用
這里需要注意一下,response需要添加Access-Control-Expose-Headers,否則在客戶端network里可以看到headers帶了token,但是response拿不到。
request.META.get(key)從request里拿token,拿到的token全大寫(xiě),中劃線變?yōu)橄聞澗€,key之前加HTTP_,例如在headers里加了auth,在request里取的時(shí)候需要用request.META.get('HTTP_AUTH'),取不到會(huì)拋出異常。

views.py修改如下,添加token部分。

from django.shortcuts import render
from rest_framework import viewsets
from rest_framework.response import Response
from rest_framework.decorators import list_route
from django.contrib.auth.hashers import make_password, check_password   #密碼存儲(chǔ)加密和校驗(yàn)
from server.models import Users
from server.serializers import UserSerializer
from server.token import create_token, verify_token
import json
import uuid

# Create your views here.
class UserViewSet(viewsets.ModelViewSet):
    serializer_class = UserSerializer

    @list_route (methods = ['post'])
    def register (self, request):
        data = json.loads(request.body)
        # 注冊(cè)用戶名校驗(yàn)
        user = Users.objects.filter(username = data['username'])
        if len(user):
            res = {
                'success': False,
                'mess': '用戶名已注冊(cè)'
            }
            return Response(res)

        data['id'] = uuid.uuid1()
        data['password'] = make_password(data['password'])
        Users.objects.create(**data)
        res = {
            'success': True
        }
        return Response(res)

    @list_route(methods = ['post'])
    def login (self, request):
        data = json.loads(request.body)
        filter_user = Users.objects.filter(username = data['username'])
        if not len(filter_user):
            res = {
                'success': False,
                'mess': '用戶名未注冊(cè)'
            }
            return Response(res)
        user = UserSerializer(filter_user, many = True).data[0]
        check_pass_result = check_password(data['password'], user['password'])
        if not check_pass_result:
            res = {
                'success': False,
                'mess': '密碼錯(cuò)誤'
            }
            return Response(res)

        res = {
            'success': True,
            'data': user
        }
        #密碼校驗(yàn)之后生成token并添加到headers
        response = Response(res)
        response['Access-Control-Expose-Headers'] = 'auth'
        response['auth'] = create_token(user)
        return response

    @list_route(methods = ['get'])
    def all_users (self, request):
        #先做登錄校驗(yàn),從headers拿token,如果沒(méi)有HTTP_AUTH會(huì)進(jìn)入except
        try:
            token = request.META.get('HTTP_AUTH')       #從request的headers里獲取token
            token = verify_token(token)                 #校驗(yàn)并生成新的token,如果校驗(yàn)失敗,返回false
            if not token:
                res = {
                    'success': False,
                    'mess': '請(qǐng)重新登錄'
                }
                return Response(res)

            users = UserSerializer(Users.objects.all(), many = True).data
            res = {
                'success': True,
                'data': users
            }
            response = Response(res)
            response['Access-Control-Expose-Headers'] = 'auth'
            response['auth'] = token
            return response
        except:
            res = {
                'success': False,
                'mess': '請(qǐng)登錄'
            }
            return Response(res)

我們先登錄看一下headers里是否帶了token

image.png

可以看見(jiàn)headers里帶了token,并且調(diào)用all_users接口時(shí)由于沒(méi)有在request headers里帶token, 提示重新登錄。

接下來(lái)是前端token本地存儲(chǔ),并將token添加到請(qǐng)求頭。
site > src > server.js

import axios from 'axios';
import store from 'store';   //用于本地存儲(chǔ)token
import settings from './etc/settings';

const Url = settings.url;

const registerServer = async (data) => {
  let res = await axios.post (`${Url}/register/`, data);
  console.log(res);
  return res.data;
}

const loginServer = async (data) => {
  let res = await axios.post (`${Url}/login/`, data);
  if (res.status === 200) {
    let token = res.headers.auth;
    if (token) store.set('django_token', token);  //登錄后從headers獲取token存儲(chǔ)到本地
    return res.data;
  }
}

const allUsersServer = async () => {
  //從本地緩存獲取token添加到headers
  let token = store.get('django_token');
  let headers = {
    auth: token
  }
  let res = await axios.get(`${Url}/all_users/`, {headers});
  if (res.status === 200) {
    let token = res.headers.auth;
    if (token) store.set('django_token', token);    //刷新本地存儲(chǔ)的token
    return res.data;
  }
}

export {
  registerServer,
  loginServer,
  allUsersServer
}

接下來(lái)測(cè)試一下,用之前注冊(cè)的賬號(hào)密碼或者新注冊(cè)一個(gè)賬號(hào)密碼進(jìn)行登錄、獲取所有用戶的操作,刷新瀏覽器獲取所有用戶,隔一分鐘,等token過(guò)期,再次獲取所有用戶。

image.png

到這里已經(jīng)實(shí)現(xiàn)了jwt自動(dòng)登錄,基本的需求已經(jīng)滿足,個(gè)別地方如果項(xiàng)目復(fù)雜的話還需要進(jìn)行封裝,這里就不做了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容