文章目录
1. JWT
1.1 简介
- 定义:JSON Web Token,目前最流行的跨域身份验证解决方案
- 原理: 服务器身份验证之后,将生成一个JSON对象并将其发送回用户。当用户与服务器进行后续通信时,客户在请求中发回JSON对象。服务器仅依赖于这个JSON对象来标识用户。为了防止用户篡改数据,服务器将在生成对象时添加签名。
- 优点: 服务器不保存任何会话数据,即服务器变为无状态,使其更容易扩展
- 构成: header、playload、signature
- header:声明类型、加密算法(默认HS256)、base64加密(可解密)
- playload:存放过期时间、用户的非敏感信息、base64加密(可解密)
- signature:base64加密后的header + base64加密后的playload + (HS256+secret)加密串
1.2 使用
- 安装jwt模块
pip install pyjwt
- 使用
import jwt
# 第一部分header,不需要指定
# 第二部分payload
payload = {
'username': 'zhangsan',
'password': '123456'
}
# 服务端创建token
token = jwt.encode(payload, key='666')
print(token)
# 服务端解密token
# algorithm:加密算法,字符串类型
data = jwt.decode(token, key='666', algorithms='HS256')
print(data)
2. DRF-jwt
2.1 django-user源码
- Django 自带认证功能auth模块和User对象的基本操作
- user模型需继承django自带的用户认证系统AbstractUser才可以使用
- django
# /site-packages/django/contrib/auth/models.py
class AbstractUser(AbstractBaseUser, PermissionsMixin):
"""
An abstract base class implementing a fully featured User model with
admin-compliant permissions.
Username and password are required. Other fields are optional.
"""
username_validator = UnicodeUsernameValidator()
username = models.CharField(
_('username'),
max_length=150,
unique=True,
help_text=_('Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.'),
validators=[username_validator],
error_messages={
'unique': _("A user with that username already exists."),
},
)
first_name = models.CharField(_('first name'), max_length=150, blank=True)
last_name = models.CharField(_('last name'), max_length=150, blank=True)
email = models.EmailField(_('email address'), blank=True)
is_staff = models.BooleanField(
_('staff status'),
default=False,
help_text=_('Designates whether the user can log into this admin site.'),
)
is_active = models.BooleanField(
_('active'),
default=True,
help_text=_(
'Designates whether this user should be treated as active. '
'Unselect this instead of deleting accounts.'
),
)
date_joined = models.DateTimeField(_('date joined'), default=timezone.now)
objects = UserManager()
EMAIL_FIELD = 'email'
USERNAME_FIELD = 'username'
REQUIRED_FIELDS = ['email']
class Meta:
verbose_name = _('user')
verbose_name_plural = _('users')
abstract = True
def clean(self):
super().clean()
self.email = self.__class__.objects.normalize_email(self.email)
def get_full_name(self):
"""
Return the first_name plus the last_name, with a space in between.
"""
full_name = '%s %s' % (self.first_name, self.last_name)
return full_name.strip()
def get_short_name(self):
"""Return the short name for the user."""
return self.first_name
def email_user(self, subject, message, from_email=None, **kwargs):
"""Send an email to this user."""
send_mail(subject, message, from_email, [self.email], **kwargs)
class User(AbstractUser):
"""
Users within the Django authentication system are represented by this
model.
Username and password are required. Other fields are optional.
"""
class Meta(AbstractUser.Meta):
swappable = 'AUTH_USER_MODEL'
2.2 登陆功能
- DRF具有完善的登陆功能,只需新建
user
子应用,并注册子应用、配置url、修改JWT认证方式即可
- 库安装
pip install djangorestframework_jwt
- url配置
# user/urls.py
from django.urls import path
from rest_framework_jwt.views import obtain_jwt_token
urlpatterns = [
path('login/', obtain_jwt_token)
]
# dev/urls.py
urlpatterns = [
...
path('user/', include('user.urls')),
]
- settings配置
# dev/settings.py
# DRF配置信息
REST_FRAMEWORK = {
...
# 认证类
'DEFAULT_AUTHENTICATION_CLASSES': [
# JWT认证
'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
# 会话认证
'rest_framework.authentication.SessionAuthentication',
# 基本认证(用户名/密码)
'rest_framework.authentication.BasicAuthentication'
],
# 权限类
'DEFAULT_PERMISSION_CLASSES': [
# 不需要登录就拥有任意权限
# 'rest_framework.permissions.AllowAny',
# # 只要登录后,就拥有任意权限
'rest_framework.permissions.IsAuthenticated',
# # 仅管理员用户才有权限
# 'rest_framework.permissions.IsAdminUser',
# 未认证成功只有只读权限,认证成功户拥有任意权限
# 'rest_framework.permissions.IsAuthenticatedOrReadOnly'
],
}
- 访问
使用token访问时,要在header加上
Authorization:"JWT token"
# 登陆,获取token
http :8000/user/login/ username=zy password=123456
http :8000/projects/ Authorization:"JWT token"
# 或者
http -a zy:123456 :8000/projects/
2.3 expires和token设置
- 自定义token返回处理器
- 源码路径:site-packages/rest_framework_jwt/utils.py
# utils/jwt_handler.py
def jwt_response_payload_handler(token, user=None, request=None):
"""
Returns the response data for both the login and refresh views.
Override to return a custom response such as including the
serialized representation of the User.
Example:
def jwt_response_payload_handler(token, user=None, request=None):
return {
'token': token,
'user': UserSerializer(user, context={'request': request}).data
}
"""
return {
'token': token,
'username': user.username,
'user_id': user.id
}
- 配置
# settings.py
import datetime
JWT_AUTH = {
# 设置token过期时间为1天
'JWT_EXPIRATION_DELTA': datetime.timedelta(days=1),
# 修改JWT前缀,默认JWT
'JWT_AUTH_HEADER_PREFIX': 'JWT',
# 修改payload函数,取自定义
'JWT_RESPONSE_PAYLOAD_HANDLER': 'utils.jwt_handler.jwt_response_payload_handler',
}
3. 注册功能
3.1 注册需求
参数 | 输入/出 | 校验/描述 |
---|---|---|
用户名 | 输入/出 | 6-20位,不能重复 |
邮箱 | 输入 | 合法格式,不能重复 |
密码 | 输入 | 6-20位,与确认密码一致 |
确认密码 | 输入 | 6-20位,与确认密码一致 |
token | 输入 | 注册成功之后返回token |
3.2 代码
from rest_framework import serializers
from django.contrib.auth.models import User
from rest_framework.validators import UniqueValidator
from rest_framework_jwt.serializers import jwt_payload_handler, jwt_encode_handler
class RegisterSerializer(serializers.ModelSerializer):
password_confirm = serializers.CharField(label='确认密码', min_length=6, max_length=20, help_text='确认密码',
write_only=True,
error_messages={
'min_length': '仅允许6~20位字符的确认密码',
'max_length': '仅允许6~20位字符的确认密码'
})
token = serializers.CharField(label='token', read_only=True)
class Meta:
model = User
fields = ('id', 'username', 'password', 'email', 'password_confirm', 'token')
extra_kwargs = {
'username': {
'label': '用户名',
'help_text': '用户名',
'min_length': 6,
'max_length': 20,
'error_messages': {
'min_length': '仅允许6-20个字符的用户名',
'max_length': '仅允许6-20个字符的用户名',
}
},
'email': {
'label': '邮箱',
'help_text': '邮箱',
'write_only': True,
# 前端必传
'required': True,
# 添加邮箱重复校验
'validators': [UniqueValidator(queryset=User.objects.all(), message='此邮箱已注册')],
},
'password': {
'label': '密码',
'help_text': '密码',
'write_only': True,
'min_length': 6,
'max_length': 20,
'error_messages': {
'min_length': '仅允许6-20个字符的密码',
'max_length': '仅允许6-20个字符的密码',
}
}
}
def validate(self, attrs):
if attrs.get('password') != attrs.get('password_confirm'):
raise serializers.ValidationError('密码与确认密码不一致')
return attrs
def create(self, validated_data):
validated_data.pop('password_confirm')
# 创建user模型对象
user = User.objects.create_user(**validated_data)
# jwt_payload_handler--对payload加密
payload = jwt_payload_handler(user)
# jwt_encode_handler--对三个部分加密生成token
token = jwt_encode_handler(payload)
user.token = token
return user
# user/urls.py
from django.urls import path, re_path
from rest_framework_jwt.views import obtain_jwt_token
from . import views
urlpatterns = [
path('login/', obtain_jwt_token),
path('register/', views.RegisterView.as_view()),
re_path(r'^(?P<username>\w{6,20})/count/$', views.UsernameIsExistedView.as_view()),
re_path(r'^(?P<email>[A-Za-z0-9\u4e00-\u9fa5]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+)/count/$',
views.EmailIsExistedView.as_view())
]
# user/views.py
from django.contrib.auth.models import User
from rest_framework.generics import CreateAPIView
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from rest_framework import permissions
from . import serializers
# class RegisterView(CreateAPIView):
# serializer_class = serializers.RegisterSerializer
# permission_classes = [permissions.AllowAny]
# 注册
class RegisterView(APIView):
permission_classes = [permissions.AllowAny]
def post(self, request, *args, **kwargs):
serializer = serializers.RegisterSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
# 获取用户名个数
class UsernameIsExistedView(APIView):
permission_classes = [permissions.AllowAny]
def get(self, request, username):
count = User.objects.filter(username=username).count()
data_dict = {
'username': username,
'count': count
}
return Response(data_dict, status=status.HTTP_200_OK)
# 获取邮箱个数
class EmailIsExistedView(APIView):
permission_classes = [permissions.AllowAny]
def get(self, request, email):
data_dict = {
'email': email,
'count': User.objects.filter(email=email).count()
}
return Response(data_dict, status=status.HTTP_200_OK)
4. 重写user-models
- 模型类必须继承自AbstractUser
# user/models.py
class User(AbstractUser):
...
- 必须全局指定自定义的模型
# settings.py
AUTH_USER_MODEL = 'app.user'