Json Web Token(JWT)
JWT 是一个开放标准(RFC 7519),它定义了一种用于简洁,自包含的用于通信双方之间以 JSON 对象的形式安全传递信息的方法。JWT 可以使用 HMAC 算法或者是 RSA 的公钥密钥对进行签名。它具备两个特点:
可以通过URL, POST 参数或者在 HTTP header 发送,因为数据量小,传输速度快
负载中包含了所有用户所需要的信息,避免了多次查询数据库
详情请查看这篇文章
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....",
}
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
)