python笔记一 django搭建服务器全栈开发
python笔记二 django自带后台管理系统、模版渲染以及使用mysql数据库
python笔记三 react + django 实现前后端分离
python笔记四 REST Framework 实现 restful api
python笔记五 django headers带jwt实现自动登录,密码加密存储
依旧是react + django,脚手架怎么用就不说了,看一下初始化之后的结构。

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

settings.json
添加服务端接口的url
,内容如下:
{
"url": "http://127.0.0.1:8000/"
}
在src
目录下新建一个server.js
,将所有交互和token的操作封装在这里,如果交互较多的话可以把axios
单独封装,里边进行token的存储与读取,这里只做交互。
import axios from 'axios';
import store from 'store'; //用于本地存储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
,删除无用代码,写两个输入框用于输入账号密码,三个按钮,注册登录和一个获取所有用户的按钮,用来验证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跑起来,测试一下,分别点一下三个按钮。

没有问题,前端部分除了token已经做好了,下一步是python部分。
二、服务端
settings.py


models.py
新建一个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
目录下新建一个序列化器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')
同步数据库
python manage.py makemigrations
python manage.py migrate
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 #密码存储加密和校验
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)
# 注册用户名校验
user = Users.objects.filter(username = data['username'])
if len(user):
res = {
'success': False,
'mess': '用户名已注册'
}
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': '用户名未注册'
}
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': '密码错误'
}
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))
]
测试一下

注册一个用户,返回
true
,重复注册返回false

登录

获取所有用户列表

接下来是重点,生成token,并且添加到request和response的headers里
这里用到PyJWT
,github地址:https://github.com/jpadilla/pyjwt
PyJWT
有两个方法encode
生成token,decode
token解析成payload
server
目录下新建一个token.py
,用来封装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 #过期时间60s
}
#secret自己设定,加密字符串,放在服务器
token = jwt.encode(payload, 'secret', algorithm = 'HS256')
return token
def verify_token (token):
try:
payload = jwt.decode(token, 'secret', algorithms = ['HS256'])
#decode成payload,用payload和当前时间戳重新生成一个token并返回
token = create_token(payload)
return token
except:
return False
封装好后在views.py
调用
这里需要注意一下,response需要添加Access-Control-Expose-Headers
,否则在客户端network里可以看到headers带了token,但是response拿不到。
用request.META.get(key)
从request里拿token,拿到的token全大写,中划线变为下划线,key之前加HTTP_
,例如在headers里加了auth
,在request里取的时候需要用request.META.get('HTTP_AUTH')
,取不到会抛出异常。
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 #密码存储加密和校验
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)
# 注册用户名校验
user = Users.objects.filter(username = data['username'])
if len(user):
res = {
'success': False,
'mess': '用户名已注册'
}
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': '用户名未注册'
}
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': '密码错误'
}
return Response(res)
res = {
'success': True,
'data': user
}
#密码校验之后生成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):
#先做登录校验,从headers拿token,如果没有HTTP_AUTH会进入except
try:
token = request.META.get('HTTP_AUTH') #从request的headers里获取token
token = verify_token(token) #校验并生成新的token,如果校验失败,返回false
if not token:
res = {
'success': False,
'mess': '请重新登录'
}
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': '请登录'
}
return Response(res)
我们先登录看一下headers里是否带了token

可以看见headers里带了token,并且调用
all_users
接口时由于没有在request headers
里带token, 提示重新登录。
接下来是前端token本地存储,并将token添加到请求头。
site > src > server.js
import axios from 'axios';
import store from 'store'; //用于本地存储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存储到本地
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); //刷新本地存储的token
return res.data;
}
}
export {
registerServer,
loginServer,
allUsersServer
}
接下来测试一下,用之前注册的账号密码或者新注册一个账号密码进行登录、获取所有用户的操作,刷新浏览器获取所有用户,隔一分钟,等token过期,再次获取所有用户。

到这里已经实现了
jwt
自动登录,基本的需求已经满足,个别地方如果项目复杂的话还需要进行封装,这里就不做了。
网友评论