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

需要安裝的依賴
python:
rest_framework
django-cors-headers
PyJWT
react:
axios
store
自行安裝
一、前端部分
src目錄下新建etc > settings.json

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è)按鈕。

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

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è)試一下

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

獲取所有用戶列表

接下來(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

可以看見(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ò)期,再次獲取所有用戶。

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