Django-认证系统
Django验证系统
Django验证同时提供身份验证和授权,通常称为身份验证系统,因为这些功能在某种程度上是耦合的
User对象
User对象是认证系统的核心。
它们通常代表与你网站进行交互的人,并用于启用诸如限制访问、注册用户配置文件、将内容与创建者关联等功能。在 Django 的认证框架中只存在一个用户类,即 'superusers'
或管理员 'staff'
用户只是具有特殊属性设置的用户对象,而不是不同类别的用户对象。
默认用户的主要属性是:
- username
- password
- first_name
- last_name
创建用户
>>> from django.contrib.auth.models import User
>>> user = User.objects.create_user("john", "lennon@thebeatles.com", "johnpassword")
>>> user.last_name = "Lennon"
>>> user.save()
创建超级用户
使用命令创建超级用户
...\> py manage.py createsuperuser --username=joe --email=joe@example.com
修改密码
使用set_password()
方法
>>> from django.contrib.auth.models import User
>>> u = User.objects.get(username="john")
>>> u.set_password("new password")
>>> u.save()
验证用户
-
authenticate(request=None, **credentials)
-
aauthenticate(request=None, **credentials) 异步版本
使用authenticate()
来验证一组凭据,它以关键字参数的形式接受凭据,对于默认情况下是username和password,然后检查它们与每个认证后端进行匹配,如果凭据对某个后端是有效的,则返回一个user对象,否则引发 PermissionDenied
返回None
from django.contrib.auth import authenticate
user = authenticate(request,username="john", password="secret")
if user is not None:
# A backend authenticated the credentials
...
else:
# No backend authenticated the credentials
...
request
是一个可选的 HttpRequest
,它被传递给身份验证后端的 authenticate()
方法。
修改默认的user表和默认的认证系统 (邮箱登录)
把username登录改为用email登录
创建自己的user表
from django.contrib.auth.models import AbstractUser
from django.db import models
# Create your models here.
# 需要继承AbstractUser
class UserProfile(AbstractUser):
nick_name = models.CharField(max_length=50, verbose_name='昵称', default='')
birthday = models.DateField(null=True, blank=True, verbose_name='生日')
gender = models.CharField(max_length=6, choices=(('male', '男'), ('female', '女')), default='female',
verbose_name='性别')
address = models.CharField(max_length=100, default='', verbose_name='地址')
mobile = models.CharField(max_length=11, null=True, blank=True, verbose_name='手机号')
image = models.ImageField(max_length=100, upload_to='image/%Y/%m', default='image?default.png',verbose_name='头像')
class Meta:
verbose_name = '用户信息'
verbose_name_plural = verbose_name
def __str__(self):
return self.username
settings中修改默认的user配置
# UserProfile 覆盖了 django 内置的 user 表
AUTH_USER_MODEL = 'users.UserProfile'
修改认证系统
from django.contrib.auth.backends import ModelBackend
from models import UserProfile
# 让用户可以用邮箱登录
# 确保在 settings 里有对应的配置
class CustomBackend(ModelBackend):
def authenticate(self, username=None, password=None, **kwargs):
try:
# 尝试通过用户名获取用户
user = UserProfile.objects.get(username=username)
except UserProfile.DoesNotExist:
# 如果用用户名没有找到用户,则尝试通过邮箱获取用户
try:
user = UserProfile.objects.get(email=username)
except UserProfile.DoesNotExist:
# 如果通过邮箱也没有找到用户,返回 None
return None
# 如果找到用户,就检查密码
if user.check_password(password):
return user
else:
return None
settings中添加认证系统
# AUTH 方法(支持邮箱登录)
AUTHENTICATION_BACKENDS = ('users.views.CustomBackend',)
JWT认证
jwt身份验证不需要使用数据库来验证令牌,而且可以轻松设置token失效期和刷新token,是API开发中当前最流行的跨域认证解决方案。
djangorestframework-simplejwt
这个第三方包实现JWT认证
什么是Json Web Token
是一种开放标准,它定义了一种紧凑且自包含的方式,用于各方之间安全地将信息以JSON对象传输。由于此信息是经过数字签名的,因此可以被验证和信任。JWT用于为应用程序创建访问token,通常适用于API身份验证和服务器到服务器的授权。那么如何理解紧凑和自包含这两个词的含义呢?
- 紧凑:就是说这个数据量比较少,可以通过url参数,http请求提交的数据以及http header多种方式来传递。
- 自包含:这个字符串可以包含很多信息,比如用户id,用户名,订单号id等,如果其他人拿到该信息,就可以拿到关键业务信息。
那么JWT认证是如何工作的呢? 首先客户端提交用户登录信息验证身份通过后,服务器生成一个用于证明用户身份的令牌(token),也就是一个加密后的长字符串,并将其发送给客户端。在后续请求中,客户端以各种方式(比如通过url参数或者请求头)将这个令牌发送回服务器,服务器就知道请求来自哪个特定身份的用户了。
JSON Web Token由三部分组成,这些部分由点(.)分隔,分别是header(头部),payload(有效负载)和signature(签名)。
- header(头部): 识别以何种算法来生成签名;
- pyload(有效负载): 用来存放实际需要传递的数据;
- signature(签名): 安全验证token有效性,防止数据被篡改。
通过http传输的数据实际上是加密后的JWT,它是由两个点分割的base64-URL长字符串组成,解密后我们可以得到header, payload和signature三部分。我们可以简单的使用 https://jwt.io/ 官网来生成或解析一个JWT
DRF中使用JWT认证
首先安装三方库
pip install djangorestframework-simplejwt
其次,我们需要告诉DRF我们使用jwt认证作为后台认证方案。修改apiproject/settings.py
:
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework_simplejwt.authentication.JWTAuthentication',
],
}
最后,需要提供获取和刷新token的urls地址,这两个urls分别对应TokenObtainPairView
和TokenRefreshView
两个视图。
from django.contrib import admin
from django.urls import path, include
from reviews.views import ProductViewSet, ImageViewSet
from rest_framework.routers import DefaultRouter
from django.conf import settings
from django.conf.urls.static import static
from rest_framework_simplejwt.views import (
TokenObtainPairView,
TokenRefreshView,
)
router = DefaultRouter()
router.register(r'product', ProductViewSet, basename='Product')
router.register(r'image', ImageViewSet, basename='Image')
urlpatterns = [
path('admin/', admin.site.urls),
path('token/', TokenObtainPairView.as_view(), name='token_obtain_pair'),
path('token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
path('', include(router.urls)),
]
if settings.DEBUG:
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
测试
使用postman测试。通过POST方法发送登录请求到/token/, 请求数据包括username和password。如果登录成功,你将得到两个长字符串,一个是access token(访问令牌),还有一个是refresh token(刷新令牌),
如果有一个受保护的视图(比如这里的/image/),权限(permission_classes)是IsAuthenticated,只有验证用户才可访问。
访问这个保护视图时你只需要在请求头的Authorization选项里输入你刚才获取的access token即可
不过这个access token默认只有5分钟有效。5分钟过后,当你再次访问保护视图时,你将得到如下token已失效或过期的提示:
去获取新的access token,你需要使用之前获得的refresh token。你将这个refresh token放到请求的正文(body)里,发送POST请求到/token/refresh/
即可获得刷新后的access token(访问令牌),
那么问题来了,Simple JWT中的access token默认有效期是5分钟,那么refresh token默认有效期是多长呢? 答案是24小时。
Simple JWT的默认设置
Simple JWT的默认设置如下所示:
DEFAULTS = {
'ACCESS_TOKEN_LIFETIME': timedelta(minutes=5),
'REFRESH_TOKEN_LIFETIME': timedelta(days=1),
'ROTATE_REFRESH_TOKENS': False,
'BLACKLIST_AFTER_ROTATION': True,
'ALGORITHM': 'HS256',
'SIGNING_KEY': settings.SECRET_KEY,
'VERIFYING_KEY': None,
'AUDIENCE': None,
'ISSUER': None,
'AUTH_HEADER_TYPES': ('Bearer',),
'USER_ID_FIELD': 'id',
'USER_ID_CLAIM': 'user_id',
'AUTH_TOKEN_CLASSES': ('rest_framework_simplejwt.tokens.AccessToken',),
'TOKEN_TYPE_CLAIM': 'token_type',
'JTI_CLAIM': 'jti',
'SLIDING_TOKEN_REFRESH_EXP_CLAIM': 'refresh_exp',
'SLIDING_TOKEN_LIFETIME': timedelta(minutes=5),
'SLIDING_TOKEN_REFRESH_LIFETIME': timedelta(days=1),
}
如果要覆盖Simple JWT的默认设置,可以修改settings.py
, 如下所示。下例将refresh token的有效期改为了15天。
from datetime import timedelta
SIMPLE_JWT = {
'REFRESH_TOKEN_LIFETIME': timedelta(days=15),
'ROTATE_REFRESH_TOKENS': True,
}
自定义令牌(token)
如果你对Simple JWT返回的access token进行解码,你会发现这个token的payload数据部分包括token类型,token失效时间,jti(一个类似随机字符串)和user_id。如果你希望在payload部分提供更多信息,比如用户的username,这时你就要自定义令牌(token)了。
首先,编写你的myapp/seralizers.py
,添加如下代码。该序列化器继承了TokenObtainPairSerializer
类。
from rest_framework_simplejwt.serializers import TokenObtainPairSerializer
class MyTokenObtainPairSerializer(TokenObtainPairSerializer):
@classmethod
def get_token(cls, user):
token = super(MyTokenObtainPairSerializer, cls).get_token(user)
# 添加额外信息
token['username'] = user.username
return token
其次,不使用Simple JWT提供的默认视图,使用自定义视图。修改 myapp/views.py
, 添加如下代码:
from rest_framework_simplejwt.views import TokenObtainPairView
from rest_framework.permissions import AllowAny
from .serializers import MyTokenObtainPairSerializer
class MyObtainTokenPairView(TokenObtainPairView):
permission_classes = (AllowAny,)
serializer_class = MyTokenObtainPairSerializer
最后,修改apiproject/urls.py
, 添加如下代码,将/token/指向新的自定义的视图。注意:本例中的app名为reviews,所以是从reviews.views导入的MyObtainTokenPairView
。
from django.contrib import admin
from django.urls import path, include
from reviews.views import ProductViewSet, ImageViewSet, MyObtainTokenPairView
from rest_framework.routers import DefaultRouter
from django.conf import settings
from django.conf.urls.static import static
from rest_framework_simplejwt.views import TokenRefreshView
router = DefaultRouter()
router.register(r'product', ProductViewSet, basename='Product')
router.register(r'image', ImageViewSet, basename='Image')
urlpatterns = [
path('admin/', admin.site.urls),
path('token/', MyObtainTokenPairView.as_view(), name='token_obtain_pair'),
path('token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
path('', include(router.urls)),
]
if settings.DEBUG:
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
重新发送POST请求到/token/,你将获得新的access token和refresh token
对重新获取的access token进行解码,你将看到payload部分多了username的内容,是不是很酷? 在实际API开发过程中,通过Json Web Token传递更多数据非常有用。
自定义认证后台(Backend)
上面的演示案例是通过用户名和密码登录的,如果我们希望后台同时支持邮箱/密码,手机/密码组合登录怎么办? 这时我们还需要自定义认证后台(Backend)。
首先,修改users/views.py
, 添加如下代码:
from django.contrib.auth.backends import ModelBackend
from django.db.models import Q
from django.contrib.auth import get_user_model
User = get_user_model()
class MyCustomBackend(ModelBackend):
def authenticate(self, request, username=None, password=None, **kwargs):
try:
user = User.objects.get(Q(username=username) | Q(email=username) )
if user.check_password(password):
return user
except Exception as e:
return None
其次,修改settings.py
, 把你自定义的验证方式添加到AUTHENTICATION_BACKENDS里去。
AUTHENTICATION_BACKENDS = (
'users.views.MyCustomBackend',
)
修改好后,你使用postman发送邮箱/密码组合到/token/,将同样可以获得access token和refresh token。