Django,flask的jwt的token认证

token是计算机术语令牌,令牌是一种能够控制站点占有媒体的特殊帧,以区别数据帧及其他控制帧;token其实说的更通俗点可以叫暗号,在一些数据传输之前,要先进行暗号的核对,不同的暗号被授权不同的数据操作。

前言

小编一直使用的是服务器的session,最近的项目,小编做成了前后端分离,遇到了一些问题:

  1. 前后端项目分属于两个域名,如果要使用session,则需要解决跨域问题
  2. 如果接口存在多个服务器的话,session小编就不建议使用了

所以这时候,小编就想到了使用token,来验证用户的合法性。

Token和session的区别

  1. 跨域问题:token没有跨域问题,session有跨域问题;
  2. 容易扩展:token不储存于服务器中,适用于服务器的分布式应用;
  3. CSRF:不依赖与cookie,不会受到跨站请求伪造的攻击;
  4. 性能:相对于session,少了一次sessionid的计算;

以上是小编认为Token和session之间的一些不同之处,所以小编觉得使用token比较方便。

JWT Token(JSON WEB TOKEN)

小编对于token的使用,是基于JWT的,因为这个标准也是比较主流的一种使用规范。JWT主要由三部分组成:

  1. 头部(header)
  2. 载荷(payload)
  3. 签证(sign)

头部(header)

JWT的头部主要由两部分信息组成:

  1. 声明类型,这里上jwt
  2. 声明加密算法,如:HMAC、SHA256、HS256

下边的json,就是一个完整的头部信息:

{
	"typ": "JWT",
	"alg": "HS256"
}

我们将这个头部信息使用base64加密,就得到了我们JWT的第一部分信息了:

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9

载荷(payload)

主要有三部分组成:标准中注册的声明、公共的声明、私有的声明。

标准中注册的声明:

  • iss:jwt签发者
  • sub:jwt所面向的用户
  • aud:接收jwt的一方
  • exp:jwt的过期时间,这个过期时间必须大于签发时间
  • nbf:定义在什么时间之前,该token都是不可用的
  • iat:jwt的签发时间
  • jti:jwt的唯一身份标识,避免重复

自定义的声明

用户自己添加的一些信息,比如用户姓名、手机号等一些不敏感信息。

我们来看看一个完整的载荷(payload)的json数据:

{
 "iss": 'jwt'
 "sub": "13011912019",
 "exp": "1530000000",
 "iat": "1529000000",
 "jti": "638069ab7a97771edcb91180f491d01e",
 "nickname": "kafei",
 "avatar": "http://oy98jbaeo.bkt.clouddn.com/avatar-1532401501"
}

我们继续将这个载荷(payload)信息使用base64加密,就得到了我们JWT的第二部分信息了:

eyJpc3MiOiJqd3QiLCJzdWIiOiIxMzAxMTkxMjAxOSIsImV4cCI6IjE1MzAwMDAwMDAiLCJpYXQiOiIxNTI5MDAwMDAwIiwianRpIjoiNjM4MDY5YWI3YTk3NzcxZWRjYjkxMTgwZjQ5MWQwMWUiLCJuaWNrbmFtZSI6ImthZmVpIiwiYXZhdGFyIjoiaHR0cDovL295OThqYmFlby5ia3QuY2xvdWRkbi5jb20vYXZhdGFyLTE1MzI0MDE1MDEifQ

签证(sign)

前边我们已经获取到了头部信息的base64和载荷的base64的信息了,现在我们需要用这两个信息,生成一个签证(sign),这就是我们需要的第三部分信息了:


最后,我们将这三部分连接起来,就是我们需要的JWT了:

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJqd3QiLCJzdWIiOiIxMzAxMTkxMjAxOSIsImV4cCI6IjE1MzAwMDAwMDAiLCJpYXQiOiIxNTI5MDAwMDAwIiwianRpIjoiNjM4MDY5YWI3YTk3NzcxZWRjYjkxMTgwZjQ5MWQwMWUiLCJuaWNrbmFtZSI6ImthZmVpIiwiYXZhdGFyIjoiaHR0cDovL295OThqYmFlby5ia3QuY2xvdWRkbi5jb20vYXZhdGFyLTE1MzI0MDE1MDEifQ.eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9

前端请求

将token放入到header里边的Authorization中,作为验证,如:

fetch('api/user/1', {
 headers: {
 'Authorization': 'Bearer ' + token
 }
})

优点

  • 不需要储存在服务器的session中,更容易扩展服务器
  • 由于用户信息可以放入token中,所以可以少了一次数据库 / 缓存的查询操作,有更好的性能
  • 不需要预防CSRF的攻击

不足

  • token一经泄露或者被盗取,将会暴露该用户

建议

  • 设置token的过期时间,不宜过长
  • 非常重要的操作,需要手机验证码,支付密码等二次验证作为保险
  • 尽量使用 https

以上参考所写,参考网址什么是token机制?(JWT主要由三部分组成) - 秦志强笔记_网络新媒体营销策划、运营、推广知识分享

Django

#settings.py中配置redis库
# 配置redis库
CACHES = {
    "default": {
        "BACKEND": "django_redis.cache.RedisCache",
        "LOCATION": "redis://:[email protected]:6379/0",
        "OPTIONS": {
            "CLIENT_CLASS": "django_redis.client.DefaultClient",
            "PASSWORD": "mysecret"
        }
    },
    "session": {
        "BACKEND": "django_redis.cache.RedisCache",
        "LOCATION": "redis://:[email protected]:6379/1",
        "OPTIONS": {
            "CLIENT_CLASS": "django_redis.client.DefaultClient",
            "PASSWORD": "mysecret"
        }
    },
 # 验证码信息: 存到 2 号库
    "verify_code": {
        "BACKEND": "django_redis.cache.RedisCache",
        "LOCATION": "redis://:[email protected]:6379/2",
        "OPTIONS": {
            "CLIENT_CLASS": "django_redis.client.DefaultClient",
        }
    },
}
SESSION_ENGINE = "django.contrib.sessions.backends.cache"
SESSION_CACHE_ALIAS = "session"

#utils/jwt_utils

import time
from django.core import signing
import hashlib
from django.core.cache import cache

HEADER = {'typ': 'JWP', 'alg': 'default'}
KEY = 'CHEN_FENG_YAO' #
SALT = 'www.lanou3g.com' #
TIME_OUT = 30 * 60  # 30min


def encrypt(obj):
    """加密"""
    value = signing.dumps(obj, key=KEY, salt=SALT)
    value = signing.b64_encode(value.encode()).decode()
    return value


def decrypt(src):
    """解密"""
    src = signing.b64_decode(src.encode()).decode()
    raw = signing.loads(src, key=KEY, salt=SALT)
    print(type(raw))
    return raw


def create_token(username,user_id):
    """生成token信息"""
    # 1. 加密头信息
    header = encrypt(HEADER)
    # 2. 构造Payload
    payload = {"username": username,'user_id':user_id, "iat": time.time()}
    payload = encrypt(payload)
    # 3. 生成签名
    md5 = hashlib.md5()
    md5.update(("%s.%s" % (header, payload)).encode())
    signature = md5.hexdigest()
    token = "%s.%s.%s" % (header, payload, signature)
    # 存储到缓存中
    cache.set(username, token, TIME_OUT)
    return token


def get_payload(token):
    """获取有效载荷"""
    payload = str(token).split('.')[1]
    payload = decrypt(payload)
    return payload


# 通过token获取用户名
def get_username(token):
    payload = get_payload(token)
    return payload['username']
    pass


def check_token(token):
    username = get_username(token)
    last_token = cache.get(username)
    if last_token:
        return last_token == token
    return False


#view
调用
from .utils.jwt_utils import create_token,get_payload

token = create_token(user.username,user.id)











Flask


utils/jwt_util

import traceback
from datetime import datetime, timedelta
from flask import g #临时存储对象
import jwt #下载pip install pyjwt
from flask import current_app #当前应用程序文件的程序实例


#  生成token
def generate_jwt(payload, expiry, secret=None):
    """
    :param payload: dict 载荷
    :param expiry: datetime 有效期
    :param secret:  盐
    :return:  token
    """
    _payload = {
        'exp': expiry
    }
    try:
        _payload.update(payload)
        if not secret:
            #获取盐
            secret = current_app.config['JWT_SECRET']
        token = jwt.encode(_payload, secret, algorithm='HS256')
    except:
        error = traceback.format_exc()
        print('error......', error)
        return {'msg': 'not value'}
    return token


def verify_jwt(token, secret=None):
    """
    校验jwt
    :param token: token值
    :param secret: 盐
    :return: payload 载荷
    """
    if not secret:
        secret = current_app.config['JWT_SECRET']
    try:
        payload = jwt.decode(token, secret, algorithms='HS256')
    except:
        error = traceback.format_exc()
        print('error..', error)
        payload = None
    return payload


# 更新token 接口实现:
def _generate_token(account, user_id, refresh=True):
    """
    生成token
    :param self:
    :param account:
    :param refresh:
    :return:
    """
    # 获取盐
    secret = current_app.config.get('JWT_SECRET')
    # 定义过期时间
    expiry = datetime.utcnow() + timedelta(hours=2)  # 过期时间设置2小时
    # 生成token
    token = 'Bearer ' + generate_jwt({'account': account, 'user_id': user_id}, expiry, secret)
    if refresh:
        expiry = datetime.utcnow() + timedelta(days=15)  # 过期时间重设为15天
        # is_refresh作为更新token的信号
        refresh_token = 'Bearer' + generate_jwt({'account': account, 'user_id': user_id, 'is_refresh': True}, expiry,
                                                secret)
    else:
        refresh_token = None
    return token, refresh_token


# (4).实现put刷新接口
def refresh_token():
    """
    刷新token
    """
    if g.account is not None and g.is_refresh is True:
        token, refresh_token = _generate_token(g.account, g.user_id, refresh=False)
        return {'message': 'ok', 'data': {'token': token}, 'code': 200}
    else:
        return {'message': 'Invalid refresh token', 'code': 500}

utils/login_util.py
# 强制登陆装饰器
from flask import g
from functools import wraps
def login_required(func):
    """
    强制登录的装饰器
    """
    @wraps(func)
    def wrapper(*args, **kwargs):
        # if not getattr(g, 'account'):
        #     return {'code': 401, 'message': 'Invalid token not account'}
        if g.user_id is not None:
            return func(*args, **kwargs)
        return {'code': 401, 'message': 'Invalid token account is none'}
    return wrapper

utils/middleware.py

from flask import request, g
from common.utils.jwt_util import verify_jwt


# def jwt_authentication():
#     g.user_id = None
#     # 获取请求头中的token
#     token = request.headers.get('Authorization')
#     if token is not None and token.startswith('Bearer '):
#         token = token[7:]
#         # 验证token
#         payload = verify_jwt(token)
#         if payload is not None:
#             # 保存到g对象中
#             g.user_id = payload.get('user_id')


# (3).改写验签代码: 将更新token信号放在g对象中

def jwt_authentication():
    g.account = None
    g.user_id = None
    g.is_refresh = False
    # 获取请求头中的token
    token = request.headers.get('Authorization')
    print('token>>>>>>>>>>>>>>>>>', str(token)[4:])
    if token is not None and token.startswith('JWT Bearer '):
        token = token[11:]
        # print(token)
        # 验证token
        payload = verify_jwt(token)
        # print(payload)
        # print('payload', payload)
        if payload is not None:
            # 保存到g对象中
            g.account = payload.get('account')  # 用户名
            g.user_id = payload.get('user_id')  # 用户id
            g.is_refresh = payload.get('is_refresh', False)

#最后配置请求前的钩子函数
# flask_create 项目内层目录
# 4.创建工厂函数封装APP
from flask import Flask
from flask_restful import Api
from flask_cors import CORS

from apscheduler.schedulers.background import BackgroundScheduler  # 调度器
from apscheduler.executors.pool import ThreadPoolExecutor  # 执行器  进程方式
import flask_whooshalchemyplus
from flask_whooshalchemyplus import index_all

from flask_create.resources.bptest.bpt import index_bp
from common.models import db, cache
from flask_create.resources.users.register import register_bp
from flask_create.resources.bptest.cloud_info import cloud_info_bp
from flask_create.news.news import news_bp
from flask_create.account.account_user import account_bp
from flask_create.channels.channel import channel_bp
from common.utils.middleware import jwt_authentication
from common.cron_task.news_task import update_recommend_list
from flask_create.book_manage.book import book_bp
from flask_create.account.user import user_bp
from flask_create.account.super_user import super_bp
from flask_create.course.course import course_bp
from flask_create.account.oauth_user import oauth2_bp
from flask_create.account.VerificationCode import verification_bp
from flask_create.goods.good import good_bp
from flask_create.pay.pay import pay_bp
from flask_create.path.path import path_bp
from flask_create.tests.test import test_bp



# 测试定时任务的函数:
def create_cron_task():
    print('这是测试定时任务>>>>>>')


def create_flask_app(config):
    app = Flask(__name__)
    # 加载配置
    app.config.from_object(config)
    app.before_request(jwt_authentication)  # 保存token
    # db对象绑定app:
    db.init_app(app)
    # 初始化redis缓存:
    cache.init_app(app)
    # 注册蓝图:
    app.register_blueprint(index_bp)
    app.register_blueprint(register_bp)
    app.register_blueprint(cloud_info_bp)
    app.register_blueprint(channel_bp)
    app.register_blueprint(news_bp)
    app.register_blueprint(account_bp)
    app.register_blueprint(book_bp)
    app.register_blueprint(user_bp)
    app.register_blueprint(super_bp)
    app.register_blueprint(course_bp)
    app.register_blueprint(oauth2_bp)
    app.register_blueprint(verification_bp)
    app.register_blueprint(good_bp)
    app.register_blueprint(pay_bp)
    app.register_blueprint(path_bp)
    app.register_blueprint(test_bp)
    # 跨域配置:
    CORS(app, supports_credentials=True)
    # 配置定时任务:
    executors = {
        'default': ThreadPoolExecutor(20)
    }
    app.scheduler = BackgroundScheduler(executors=executors)
    # app.scheduler.add_job(create_cron_task,trigger='interval',seconds=10)  # 定时缓存任务测试
    # trigger是触发的意思,interval是间隔的意思
    app.scheduler.add_job(update_recommend_list, trigger='interval', seconds=10, args=[cache, app])
    app.scheduler.add_job(update_recommend_list, trigger='date', args=[cache, app])  # date的立即执行的意思

    # app.scheduler.start()  # 启动定时任务
    # 创建api对象
    api = Api(app)
    # 配置全文索引
    flask_whooshalchemyplus.init_app(app)
    # with app.app_context():
    #     index_all(app)
    return app

你可能感兴趣的:(flask,django,python)