自定义web token验证

自定义web token验证

pip install itsdangerous

一 itsdangerous

一、给字符串添加签名:

  发送方和接收方拥有相同的密钥--"secret-key",发送方使用密钥对发送内容进行签名,接收方使用相同的密钥对接收到的内容进行验证,看是否是发送方发送的内容

>>> from itsdangerous import Signer
>>> s = Signer('secret-key')
>>> s.sign('my string, ssssssssss,dddddddddddddlsd')
'my string, ssssssssss,dddddddddddddlsd.nSXTxgO_UMN4gkLZcFCioa-dZSo'
>>>
>>> s.unsign('my string, ssssssssss,dddddddddddddlsd.nSXTxgO_UMN4gkLZcFCioa-dZSo')
'my string, ssssssssss,dddddddddddddlsd'
>>> s.unsign('my string, ssss.nSXTxgO_UMN4gkLZcFCioa-dZSo')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/local/lib/python2.7/site-packages/itsdangerous.py", line 374, in unsign
    payload=value)
itsdangerous.BadSignature: Signature 'nSXTxgO_UMN4gkLZcFCioa-dZSo' does not match
>>> s.unsign('my string, ssssssssss,dddddddddddddlsd.nSXTxgO_UMN4gkLZcFCioa-dZSP')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/local/lib/python2.7/site-packages/itsdangerous.py", line 374, in unsign
    payload=value)
itsdangerous.BadSignature: Signature 'nSXTxgO_UMN4gkLZcFCioa-dZSP' does not match
>>>

二、带时间戳的签名:

  签名有一定的时效性,发送方发送时,带上时间信息,接收方判断多长时间内是否失效

>>> from itsdangerous import TimestampSigner
>>> s = TimestampSigner('secret-key')
>>> string = s.sign('foo')
>>> s.unsign(string, max_age=5)
foo
>>> s.unsign(string, max_age=5)
Traceback (most recent call last):
  ...
itsdangerous.SignatureExpired: Signature age 15 > 5 seconds

三、序列化

>>> from itsdangerous import Serializer
>>> s = Serializer('secret-key')
>>> s.dumps([1, 2, 3, 4])
'[1, 2, 3, 4].r7R9RhGgDPvvWl3iNzLuIIfELmo'
And it can of course also load:

>>> s.loads('[1, 2, 3, 4].r7R9RhGgDPvvWl3iNzLuIIfELmo')
[1, 2, 3, 4]
If you want to have the timestamp attached you can use the TimedSerializer.

四、带时间戳的序列化

>>> from itsdangerous import TimedSerializer
>>> s=TimedSerializer('secret-key')
>>> s.dumps([1,2,3,4])
'[1, 2, 3, 4].DI7WHQ.yVOjwQWau5mVRGuVkoqa7654VXc'
>>> s.loads('[1, 2, 3, 4].DI7WHQ.yVOjwQWau5mVRGuVkoqa7654VXc')
[1, 2, 3, 4]
>>> s.loads('[1, 2, 3, 4].DI7WHQ.yVOjwQWau5mVRGuVkoqa7654VXc',max_age=10)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/local/lib/python2.7/site-packages/itsdangerous.py", line 643, in loads
    .unsign(s, max_age, return_timestamp=True)
  File "/usr/local/lib/python2.7/site-packages/itsdangerous.py", line 463, in unsign
    date_signed=self.timestamp_to_datetime(timestamp))
itsdangerous.SignatureExpired: Signature age 28 > 10 seconds
>>> s.loads('[1, 2, 3, 4].DI7WHQ.yVOjwQWau5mVRGuVkoqa7654VXc',max_age=40)
[1, 2, 3, 4]
>>>

五、URL安全序列化

对于限定字符串的场景,你可以使用URL安全序列化

>>> from itsdangerous import URLSafeSerializer
>>> s = URLSafeSerializer('secret-key')
>>> s.dumps([1, 2, 3, 4])
'WzEsMiwzLDRd.wSPHqC0gR7VUqivlSukJ0IeTDgo'
>>> s.loads('WzEsMiwzLDRd.wSPHqC0gR7VUqivlSukJ0IeTDgo')
[1, 2, 3, 4]

六、JSON Web签名

JSON Web Signatures

Starting with “itsdangerous” 0.18 JSON Web Signatures are also supported. They generally work very similar to the already existing URL safe serializer but will emit headers according to the current draft (10) of the JSON Web Signature (JWS) [draft-ietf-jose-json-web-signature].

>>> from itsdangerous import JSONWebSignatureSerializer
>>> s = JSONWebSignatureSerializer('secret-key')
>>> s.dumps({'x': 42})
'eyJhbGciOiJIUzI1NiJ9.eyJ4Ijo0Mn0.ZdTn1YyGz9Yx5B5wNpWRL221G1WpVE5fPCPKNuc6UAo'

When loading the value back the header will not be returned by default like with the other serializers. However it is possible to also ask for the header by passing return_header=True. Custom header fields can be provided upon serialization:

>>> s.dumps(0, header_fields={'v': 1})
'eyJhbGciOiJIUzI1NiIsInYiOjF9.MA.wT-RZI9YU06R919VBdAfTLn82_iIQD70J_j-3F4z_aM'
>>> s.loads('eyJhbGciOiJIUzI1NiIsInYiOjF9.MA.wT-RZI9YU06R919VBdAf'
...         'TLn82_iIQD70J_j-3F4z_aM', return_header=True)
...
(0, {u'alg': u'HS256', u'v': 1})

“itsdangerous” only provides HMAC SHA derivatives and the none algorithm at the moment and does not support the ECC based ones. The algorithm in the header is checked against the one of the serializer and on a mismatch a BadSignatureexception is raised.

七、带时间戳的JSON Web签名

from itsdangerous import TimedJSONWebSignatureSerializer as Serializer 
s = Serializer('secret-key', expires_in=60)
s.dumps({'id': user.id}) # user为model中封装过的对象

八、盐值

这里的盐值和加密算法里的盐值概念不一样,这里的盐值(salt)可以应用到上面所有情形中,不同的盐值,生成的签名或者序列化的数值不一样

>>> s1 = URLSafeSerializer('secret-key', salt='activate-salt')
>>> s1.dumps(42)
'NDI.kubVFOOugP5PAIfEqLJbXQbfTxs'
>>> s2 = URLSafeSerializer('secret-key', salt='upgrade-salt')
>>> s2.dumps(42)
'NDI.7lx-N1P-z2veJ7nT1_2bnTkjGTE'
>>> s2.loads(s1.dumps(42))
Traceback (most recent call last):
  ...
itsdangerous.BadSignature: Signature "kubVFOOugP5PAIfEqLJbXQbfTxs" does not match
Only the serializer with the same salt can load the value:

>>> s2.loads(s2.dumps(42))
42

二 django中使用itsdangerous自定义token

settings/auth.py

#----自定义token设置-----
TOKEN_SOLT = TOKEN_SOLT # 指定自己的盐
TOKENID_PREFIX = 'user_token_'

utils/auth.py

import time
from functools import wraps
from itsdangerous import URLSafeSerializer,constant_time_compare # itsdangerous的版本低于1
# ----djangotoken--
from django.core.cache import cache
# from . import cache
from .response import APIResponse
from .logging import logger
from apps.user.models import User
from .tool import create_identifier
from django.conf import settings
#----------end-----

'''
前端接受到token然后保存在cookies里
然后每次请求都带着


一共两种情况
token 内容 user_id password client_inf lifetime create_time
1 不记住密码  token7天有效
token永久有效,然后发送给前端,redis缓存7天,返回给前端拿到了永久token。
    1 但是如果3天内不访问服务器,token在redis中被清除。
    2 如果3天内访问了服务器,token则在redis中延期3天(刷新了token)
    缓存中存的token形式:  'user_token_2'=token 过期时间

2 记住密码 ,token2个月有效
    1 token在redis中2个月有效
        2 如果2个月内不访问服务器,则清除token
        3 如果在2个月内访问服务器,则刷新token
    缓存中存的token形式:  'user_token_2'=token 过期时间
'''

def create_token(request,user_id, password, lifetime):
    """
    创建用户TOKEN
    :param user_id: 用户ID
    :param password: 用户hash密码
    :param lifetime: TOKEN有效时间,默认0永久有效
    :param client_info: 客户端信息
    :return: TOKEN
    """
    user_ident = create_identifier(request)
    token_serializer = URLSafeSerializer(settings.TOKEN_SOLT)
    # user_ident = create_identifier(request,client_info) #待定
    create_time = int(time.time())
    return token_serializer.dumps((user_id, password, user_ident, lifetime, create_time))


def cache_token(request,user_id, password,is_remember):
    """
    生成并缓存TOKEN
    :param user_id:  缓存TOKEN的ID
    :param password: 用户密码
    :param client_info: 客户端信息
    :return: TOKEN
    """
    # token_lifetime = current_app.config.get("TOKEN_LIFETIME", 7 * 24 * 60 * 60)
    if is_remember:
        token_lifetime = 120 * 24 * 60 * 60
    token_lifetime = 7 * 24 * 60 * 60
    token = create_token(request,user_id, password, token_lifetime)
    cache.set("{0}{1}".format(settings.TOKENID_PREFIX, user_id), token, timeout=token_lifetime)
    return token


def clear_token(user_id):
    """
    清除token
    :param user_id: 缓存token的user_id
    """
    cache.delete("{0}{1}".format(settings.TOKENID_PREFIX, user_id))

# token校验
def need_token(func):
    """
    校验token合法性,返回用户信息对象。
    :param func: 需要权限验证的视图函数.
    """
    @wraps(func)
    def wrapper(request,*args, **kwargs):
        # token_id = current_app.config.get("TOKEN_NAME", "token")
        token_id = "token"
        # token = request.headers.get(token_id) or g.request_data.get(token_id)
        token = request.headers.get(token_id)
        if token is None:
            # current_app.logger.debug("缺少登录凭证")

            return APIResponse("1", "缺少登录凭证")

        # 判断token字符串是否合法
        key = "hard to guess string"
        try:
            token_serializer = URLSafeSerializer(settings.TOKEN_SOLT)
            user_id, user_pwd_hash, user_ident, token_time, create_time = token_serializer.loads(token)
            # print(user_pwd)
        except Exception as e:
            logger.debug("非法的登录凭证: %s, %s", token, e)
            return APIResponse("1", "非法的登录凭证")
        # 判断用户环境是否改变
        identifier = create_identifier(request)
        if not constant_time_compare(str(user_ident), str(identifier)):
            logger.debug("登录环境改变: user_id:%s", user_id)
            return APIResponse("1", "登录环境改变")

        # 判断token缓存是否有效
        cached_token = cache.get("{0}{1}".format(settings.TOKENID_PREFIX, user_id))
        if cached_token:
            if not constant_time_compare(cached_token, token):
                try:
                    _user_id, _user_pwd_hash, _user_ident, _, _create_time = token_serializer.loads(cached_token)
                except Exception as e:
                    logger.debug("登录凭证已失效: user_id:%s, %s", user_id, e)
                    return APIResponse("1", "登录凭证已失效")
                if user_id != _user_id or user_pwd_hash != _user_pwd_hash:
                    logger.debug("登录凭证无效: user_id:%s", user_id)
                    return APIResponse("1", "登录凭证无效")
                if user_ident != _user_ident:
                    logger.debug("登录环境改变: user_id:%s", user_id)
                    return APIResponse("1", "登录环境改变")
                if int(create_time) < int(_create_time):
                    logger.debug("登录凭证已过期: user_id:%s", user_id)
                    return APIResponse("1", "登录凭证已过期")
        else:
            logger.debug("登录凭证已过期: user_id:%s", user_id)
            return APIResponse("1", "登录凭证已过期")

        # 判断token中user是否有效
        user = User.objects.filter(pk = user_id).first()
        if user is None:
            logger.debug("无效的用户: user_id:%s", user_id)
            return APIResponse("1", "无效的用户")
        if user.is_active != 1:
            logger.debug("账号已禁用: user_id:%s,account:%s,status:%s", user_id, user.account, user.status)
            return APIResponse("1", "账号已禁用")
        # 若存在此用户则判断用户密码是否改变
        if user_pwd_hash != user.password:
            logger.debug("密码已被改变: user_id:%s,account:%s", user_id, user.mobile)
            return APIResponse("1", "密码已被改变")
        # 刷新token时间
        if token_time:
            cache.set("{0}{1}".format(settings.TOKENID_PREFIX, user_id), token, timeout=token_time)
        # user.login_time = create_time
        return func(request,*args, **kwargs)
    return wrapper

views.py

# 测试类视图装饰器token
from utils.auth import cache_token,clear_token,need_token
from django.utils.decorators import method_decorator
from utils.test import my_decorator
# 装饰器验证
class TestTokenAPIView(APIView):

    def get(self,request,*args,**kwargs):
        print('get---我被访问了')
        user = models.User.objects.filter(username='zmy666').first()
        print(user.password)
        token = cache_token(request,user.id,user.password,None)
        return APIResponse(1,'ok',results={"token":token})

    @method_decorator(need_token)
    def post(self,request,*args,**kwargs):
        print('我被访问了')
        return APIResponse(1,'ok')

    @method_decorator(need_token)
    def delete(self,request,*args,**kwargs):
        print('删除token')
        user = models.User.objects.filter(username='zmy666').first()
        # 清除token
        clear_token(user.id)
        return APIResponse(1,'ok')
posted @ 2019-12-14 16:37  张明岩  阅读(539)  评论(0编辑  收藏  举报