【Django进阶】djangorestframework-jwt使用

发布时间 2023-12-28 14:53:56作者: 小C学安全

简介

Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519).该token被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景。
JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密。

简单一句话理解就是:由服务器经过加密后生成的一张令牌

token的认证和传统的session认证的区别

1. 传统的session认证

我们知道,http协议本身是一种无状态的协议,而这就意味着如果用户向我们的应用提供了用户名和密码来进行用户认证,那么下一次请求时,用户还要再一次进行用户认证才行,因为根据http协议,我们并不能知道是哪个用户发出的请求,所以为了让我们的应用能识别是哪个用户发出的请求,我们只能在服务器存储一份用户登录的信息,这份登录信息会在响应时传递给浏览器,告诉其保存为cookie,以便下次请求时发送给我们的应用,这样我们的应用就能识别请求来自哪个用户了,这就是传统的基于session认证。

但是这种基于session的认证使应用本身很难得到扩展,随着不同客户端用户的增加,独立的服务器已无法承载更多的用户,而这时候基于session认证应用的问题就会暴露出来.

2. 基于session认证所显露的问题

Session:
每个用户经过我们的应用认证之后,我们的应用都要在服务端做一次记录,以方便用户下次请求的鉴别,通常而言session都是保存在内存中,而随着认证用户的增多,服务端的开销会明显增大。
扩展性:
用户认证之后,服务端做认证记录,如果认证的记录被保存在内存中的话,这意味着用户下次请求还必须要请求在这台服务器上,这样才能拿到授权的资源,这样在分布式的应用上,相应的限制了负载均衡器的能力。这也意味着限制了应用的扩展能力。
CSRF:
因为是基于cookie来进行用户识别的, cookie如果被截获,用户就会很容易受到跨站请求伪造的攻击。

3. 基于token的鉴权机制

基于token的鉴权机制类似于http协议也是无状态的,它不需要在服务端去保留用户的认证信息或者会话信息。这就意味着基于token认证机制的应用不需要去考虑用户在哪一台服务器登录了,这就为应用的扩展提供了便利。

JWT配置

安装

pip install djangorestframework
pip install djangorestframework-jwt
INSTALLED_APPS = (
    ...
    'rest_framework',
    'rest_framework_jwt',
    ...
)

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
        ...
    ),
}

JWT_AUTH = {
    'JWT_EXPIRATION_DELTA': datetime.timedelta(days=7),
    'JWT_ALLOW_REFRESH': True,
}

扩展配置

JWT_AUTH = {
    'JWT_SECRET_KEY': 'your_secret_key',
    'JWT_ALGORITHM': 'HS256',
    'JWT_VERIFY': True,
    'JWT_VERIFY_EXPIRATION': True,
    'JWT_EXPIRATION_DELTA': datetime.timedelta(seconds=3600),
    'JWT_ALLOW_REFRESH': True,
    'JWT_REFRESH_EXPIRATION_DELTA': datetime.timedelta(days=7),
    'JWT_AUTH_HEADER_PREFIX': 'JWT',
}

重写自定义视图函数

from rest_framework_jwt.serializers import JSONWebTokenSerializer
from django.contrib.auth import authenticate
from django.utils.translation import ugettext as _
from rest_framework import serializers
from rest_framework_jwt.settings import api_settings
jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER

class TokenSerializer(JSONWebTokenSerializer):
	# 重写JSONWebTokenSerializer的validate函数
    def validate(self, attrs):
        credentials = {
            self.username_field: attrs.get(self.username_field),
            'password': attrs.get('password')
        }

        if all(credentials.values()):
            user = authenticate(**credentials)

            if user:
                if not user.is_active:
                    msg = _('User account is disabled.')
                    raise serializers.ValidationError(msg)

                ###################################
                # 添加一个认证
                if not user.is_staff:
                    msg = _('User account is disabled.')
                    raise serializers.ValidationError(msg)
                ##################################

                payload = jwt_payload_handler(user)
				
                return {
                    'token': jwt_encode_handler(payload),
                    'user': user
                }
            else:
                msg = _('Unable to log in with provided credentials.')
                raise serializers.ValidationError(msg)
        else:
            msg = _('Must include "{username_field}" and "password".')
            msg = msg.format(username_field=self.username_field)
            raise serializers.ValidationError(msg)



########################################
from rest_framework_jwt.views import JSONWebTokenAPIView

class ObtainJSONWebToken(JSONWebTokenAPIView):
    """
    API View that receives a POST with a user's username and password.

    Returns a JSON Web Token that can be used for authenticated requests.
    """
    serializer_class = TokenSerializer

内置接口

from rest_framework_jwt.views import obtain_jwt_token, refresh_jwt_token, verify_jwt_token

urlpatterns = [
    url(r'^auth/token/$', obtain_jwt_token), #获取令牌
    url(r'^auth/token/refresh/$', refresh_jwt_token),  #刷新令牌
    url(r'^auth/token/verify/$', verify_jwt_token),  #验证令牌
]

成功配置返回结果

自定义成功

def jwt_response_payload_handler(token, user=None, request=None):
    """为返回的结果添加用户相关信息"""
    # print(user, request.headers)
    return {
        "status": 200,
        "message": "登录成功",
        "data": {'token': token,
                 'email': user.email,
                 'user_id': user.id,
                 'username': user.username}

    }

setting中配置
jwt载荷中的有效期设置

JWT_AUTH = {
    'JWT_AUTH_HEADER_PREFIX': 'JWT',
    # token 有效期
    'JWT_EXPIRATION_DELTA': datetime.timedelta(seconds=3),
    'JWT_ALLOW_REFRESH': True,
    # 续期有效期(该设置可在24小时内带未失效的token 进行续期)
    'JWT_REFRESH_EXPIRATION_DELTA': datetime.timedelta(hours=24),
    # 自定义返回格式,需要手工创建
    'JWT_RESPONSE_PAYLOAD_HANDLER': 'apps.users.views.jwt_response_payload_handler',
    # 'JWT_RESPONSE_PAYLOAD_ERROR_HANDLER': 'apps.users.views.jwt_response_payload_error_handler',
}

失败自定义返回

/rest_framework_jwt/settings.py在IMPORT_STRINGS中添加

"JWT_RESPONSE_PAYLOAD_ERROR_HANDLER":
    'rest_framework_jwt.utils.jwt_response_payload_handler',


'JWT_RESPONSE_PAYLOAD_ERROR_HANDLER'

rest_framework_jwt/views.py中修改

def jwt_response_payload_error_handler(serializer, request = None):
    return {
        "msg": "用户名或者密码错误",
        "status": 400,
        "detail": serializer.errors
    }

setting配置

jwt_token配置

JWT_AUTH = {
    'JWT_EXPIRATION_DELTA': datetime.timedelta(seconds=6000),
    # 登陆成功自定义 的返回结构
    'JWT_RESPONSE_PAYLOAD_HANDLER': 'Users.views.jwt_response_payload_handler',
    # 登陆失败时自定义的返回结构
    'JWT_RESPONSE_PAYLOAD_ERROR_HANDLER': 'Users.views.jwt_response_payload_error_handler',
}