django rest framework jwt 不使用django user来生成和认证token的方法

简单介绍一下jwt

Json Web Token(JWT)
JWT 是一个开放标准(RFC 7519),它定义了一种用于简洁,自包含的用于通信双方之间以 JSON 对象的形式安全传递信息的方法。JWT 可以使用 HMAC 算法或者是 RSA 的公钥密钥对进行签名。它具备两个特点:

  • 简洁(Compact)

可以通过URL, POST 参数或者在 HTTP header 发送,因为数据量小,传输速度快

  • 自包含(Self-contained)

负载中包含了所有用户所需要的信息,避免了多次查询数据库

详情请查看这篇文章
https://www.jianshu.com/p/180a870a308a

基本配置

django rest framework jwt 需要配合 rest framework来使用

settings.py

REST_FRAMEWORK = {
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.IsAuthenticated',
    ],
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
    ],
}

urls.py

from rest_framework_jwt.views import obtain_jwt_token, verify_jwt_token, refresh_jwt_token
urlpatterns = [
    url(r'^api/v1/login/$', obtain_jwt_token, name='login'),
]

使用创建一个用户进行测试

python manage.py createsuperuser

发送json

{
    "username": "admin",
    "password": "admin"
}

返回如下格式的json

{
    "token": "XXXX....",
}

修改使用其它的model认证

  • 思路
    先跟着obtain_jwt_token进到rest_framework_jwt的views.py下,再跟进ObtainJSONWebToken这个类里
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 = JSONWebTokenSerializer

然后再进入这个JSONWebTokenSerializer序列器里看看,看注释Serializer class used to validate a username and password.这样就明白了,然后先观察一下需要改的地方,当然不是在原来的地方改动了,我们可以新建一个utils.py文件,然后继承Serializer类,就可以了,需要注意的还有一个地方:

在生成payload的地方

payload = jwt_payload_handler(user)

我们来跟一下

jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER

进入rest_framework_jwt下的settings.py文件,可以看到

    'JWT_PAYLOAD_HANDLER':
    'rest_framework_jwt.utils.jwt_payload_handler',

跟进utils.py里jwt_payload_handler这个方法,可以看到,里面默认把传进来的model当成是django User的,而且里面的一些字段,我们可能会用的是别的,对吧,所以准备将这个方法重写吧

下面贴出一些修改提供参考

class MRCUserSerializer(Serializer):
    def __init__(self, *args, **kwargs):
        """
        Dynamically add the USERNAME_FIELD to self.fields.
        """
        super(MRCUserSerializer, self).__init__(*args, **kwargs)
        self.fields["phone"] = serializers.CharField()  # 手机号
        self.fields['sms_code'] = serializers.CharField()  # 验证码
        self.fields['remember'] = serializers.IntegerField(default=60, allow_null=True)  # 过期时间

    def validate(self, attrs):

        credentials = {
            'phone': attrs.get('phone'),
            'sms_code': attrs.get('sms_code'),
            'remember': attrs.get('remember'),
        }

        if all(credentials.values()):
            try:
                user = User.objects.get(phone=credentials.get('phone')) # 自己新建的model,不是django里的User
            except User.DoesNotExist:
                msg = _('the validate code is error')
                raise serializers.ValidationError(msg)

            if user:
                exp = datetime.utcnow() + timedelta(seconds=credentials.get('remember'))  # 过期时间
                payload = jwt_payload_handler(user, exp)

                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)
def jwt_payload_handler(user, exp=datetime.utcnow() + api_settings.JWT_EXPIRATION_DELTA):
    username_field = get_username_field()
    username = get_username(user)

    # warnings.warn(
    #     'The following fields will be removed in the future: '
    #     '`email` and `user_id`. ',
    #     DeprecationWarning
    # )
    payload = {
        'user_id': user.pk,
        'username': username,
        'phone': user.phone,
        'exp': exp
    }
    # if hasattr(user, 'email'):
    #     payload['email'] = user.email
    if isinstance(user.pk, uuid.UUID):
        payload['user_id'] = str(user.pk)

    payload[username_field] = username

    # Include original issued at time for a brand new token,
    # to allow token refresh
    if api_settings.JWT_ALLOW_REFRESH:
        payload['orig_iat'] = timegm(
            datetime.utcnow().utctimetuple()
        )

    if api_settings.JWT_AUDIENCE is not None:
        payload['aud'] = api_settings.JWT_AUDIENCE

    if api_settings.JWT_ISSUER is not None:
        payload['iss'] = api_settings.JWT_ISSUER

    return payload

接收三个自定义参数进行认证,过期时间可以自定义,这样就不会受到限制,warnings这里如果不注释掉有些ide会有警告,当然里面的内容自己根据业务进行修改就好了,主要的思路是不变的;

如果要对返回的json或者是request的数据提前做一些处理(验证码之类的业务),可以继承JSONWebTokenAPIView,重写post方法就可以了

修改认证

从工程settings.py可以看到我们之前添加的jwt的配置,会从rest_framework_jwt.authentication.JSONWebTokenAuthentication这里开始进行认证,认证通过后再往下进行。当然这里默认认证的对象依然是django的User;

我们新建文件authentication.py,然后继承BaseAuthentication,进行重写
下面是我重写的一些内容,可以参考一下

class BaseJSONWebTokenAuthentication(BaseAuthentication):
    """
    Token based authentication using the JSON Web Token standard.
    """
    def get_jwt_value(self, request):
        pass

    def authenticate(self, request):
        """
        Returns a two-tuple of `User` and token if a valid signature has been
        supplied using JWT-based authentication.  Otherwise returns `None`.
        """
        jwt_value = self.get_jwt_value(request)
        if jwt_value is None:
            return None

        try:
            payload = jwt_decode_handler(jwt_value)
        except jwt.ExpiredSignature:
            msg = _('Signature has expired.')
            raise exceptions.AuthenticationFailed(msg)
        except jwt.DecodeError:
            msg = _('Error decoding signature.')
            raise exceptions.AuthenticationFailed(msg)
        except jwt.InvalidTokenError:
            raise exceptions.AuthenticationFailed()

        user = self.authenticate_credentials(payload)

        return (user, jwt_value)

    def authenticate_credentials(self, payload):
        """
        Returns an active user that matches the payload's user id and email.
        """
        # User = get_user_model()
        # username = jwt_get_username_from_payload(payload)
        phone = payload.get('phone')        # 如果更新用户信息时更改到手机号,则要重新登录获取新的token

        if not phone:
            msg = _('Invalid payload.')
            raise exceptions.AuthenticationFailed(msg)

        try:
            # user = User.objects.get_by_natural_key(username)
            user = User.objects.get(phone=phone)
        except User.DoesNotExist:
            msg = _('Invalid signature.')
            raise exceptions.AuthenticationFailed(msg)

        # if not user.is_active:
        #     msg = _('User account is disabled.')
        #     raise exceptions.AuthenticationFailed(msg)

        return user

class JSONWebTokenAuthentication(BaseJSONWebTokenAuthentication):
    """
    Clients should authenticate by passing the token key in the "Authorization"
    HTTP header, prepended with the string specified in the setting
    `JWT_AUTH_HEADER_PREFIX`. For example:

        Authorization: JWT eyJhbGciOiAiSFMyNTYiLCAidHlwIj
    """
    www_authenticate_realm = 'api'

    def get_jwt_value(self, request):
        auth = get_authorization_header(request).split()
        auth_header_prefix = api_settings.JWT_AUTH_HEADER_PREFIX.lower()

        if not auth:
            if api_settings.JWT_AUTH_COOKIE:
                return request.COOKIES.get(api_settings.JWT_AUTH_COOKIE)
            return None

        if smart_text(auth[0].lower()) != auth_header_prefix:
            return None

        if len(auth) == 1:
            msg = _('Invalid Authorization header. No credentials provided.')
            raise exceptions.AuthenticationFailed(msg)
        elif len(auth) > 2:
            msg = _('Invalid Authorization header. Credentials string '
                    'should not contain spaces.')
            raise exceptions.AuthenticationFailed(msg)

        return auth[1]

    def authenticate_header(self, request):
        """
        Return a string to be used as the value of the `WWW-Authenticate`
        header in a `401 Unauthenticated` response, or `None` if the
        authentication scheme should return `403 Permission Denied` responses.
        """
        return '{0} realm="{1}"'.format(api_settings.JWT_AUTH_HEADER_PREFIX, self.www_authenticate_realm)

然后修改工程settings.py文件

'DEFAULT_AUTHENTICATION_CLASSES': [
    'youApp.authentication.JSONWebTokenAuthentication',
],

修改权限

新建文件permissions.py,并继承BasePermission类
例如:

class ShareAppIsAuthenticated(BasePermission):
    """
    Allows access only to authenticated users.(自定义对所有的访问进行认证)
    """

    def has_permission(self, request, view):
        return False if request.user.username == '' else True


class ShareAppIsAuthenticatedOrReadOnly(BasePermission):
    """
    The request is authenticated as a user, or is a read-only request.(对GET方法放过处理,其它进行认证)
    """

    def has_permission(self, request, view):
        return (
            request.method in SAFE_METHODS or
            False if request.user.username == '' else True
        )
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值