美文网首页
python笔记五 django headers带jwt实现自动

python笔记五 django headers带jwt实现自动

作者: 风中的猴子 | 来源:发表于2018-09-19 16:43 被阅读0次

python笔记一 django搭建服务器全栈开发
python笔记二 django自带后台管理系统、模版渲染以及使用mysql数据库
python笔记三 react + django 实现前后端分离
python笔记四 REST Framework 实现 restful api
python笔记五 django headers带jwt实现自动登录,密码加密存储

依旧是react + django,脚手架怎么用就不说了,看一下初始化之后的结构。


image.png
需要安装的依赖

python:

rest_framework
django-cors-headers
PyJWT

react:

axios
store

自行安装

一、前端部分

src目录下新建etc > settings.json

image.png
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跑起来,测试一下,分别点一下三个按钮。

image.png
没有问题,前端部分除了token已经做好了,下一步是python部分。
二、服务端
settings.py
image.png
image.png
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))
]

测试一下

image.png
注册一个用户,返回true,重复注册返回false
image.png
登录
image.png
获取所有用户列表
image.png

接下来是重点,生成token,并且添加到request和response的headers里

这里用到PyJWT,github地址:https://github.com/jpadilla/pyjwt
PyJWT有两个方法encode生成token,decodetoken解析成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

image.png
可以看见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过期,再次获取所有用户。

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

相关文章

网友评论

      本文标题:python笔记五 django headers带jwt实现自动

      本文链接:https://www.haomeiwen.com/subject/tcbbnftx.html