Django中的JWT认证
本教程将介绍JSONWeb令牌(JWT)以及如何在Django中实现JWT身份验证。
JWT是什么?
JWT是一个编码的JSON字符串,它在头中传递以验证请求。它通常是通过使用秘密密钥散列JSON数据来获得的。这意味着服务器不需要每次查询数据库来检索与给定令牌关联的用户。
JSONWeb令牌是如何工作的
当用户成功地使用他们的凭据登录时,将获得一个JSONWeb令牌并保存在本地存储中。每当用户想要访问受保护的URL时,都会在请求的标题中发送令牌。然后,服务器在授权头中检查有效的JWT,如果找到,用户将被允许访问。
典型的内容标题如下所示:
Authorization: Bearer eyJhbGciOiJIUzI1NiIsI
下面是显示此过程的图表:
[图片上传失败...(image-f4070b-1572571821531)]
广告
认证与授权的概念
身份验证是标识登录用户的过程,而授权则是确定某个用户是否有权访问Web资源的过程。
API示例
在本教程中,我们将使用JWT作为身份验证机制,在Django中构建一个简单的用户身份验证系统。
所需
- 姜戈
- Python
我们开始吧。
创建一个将保留项目的目录,并创建一个安装项目依赖关系的虚拟环境。
|
1
2
3
4
5
|
mkdir
myprojects
cd
myprojects
virtual venv
|
激活虚拟环境:
|
1
|
source
venv``/bin/activate
|
创建Django项目。
|
1
|
django-admin startproject django_auth
|
使用pip安装DRF和Django-REST框架-JWT。
|
1
2
3
|
pip ``install
djangorestframework
pip ``install
djangorestframework-jwt
pip ``install
django
|
让我们继续将DRF添加到已安装的应用程序列表中settings.py
档案。
配置JWT设置
为了使用JWT,我们需要配置Django-REST框架权限以接受JSONWeb令牌。
在settings.py
文件中,添加以下配置:
|
1
2
3
4
5
|
REST_FRAMEWORK ``=
{
'DEFAULT_AUTHENTICATION_CLASSES'``: (
'rest_framework_jwt.authentication.JSONWebTokenAuthentication'``,
),
}
|
创建一个名为User的新应用程序,它将处理用户身份验证和管理。
|
1
2
|
cd django``-``auth
django``-``admin.py startapp users
|
将用户应用程序添加到已安装的应用程序列表中。settings.py
档案。
建立数据库
我们将使用PostgreSQL数据库,因为它更稳定和健壮。
创建auth
数据库和分配用户。
通过键入以下命令,切换到计算机上的Postgres帐户:
|
1
|
sudo
su
postgres
|
访问Postgres提示符并创建数据库:
|
1
2
|
psql
postgres=``# CREATE DATABASE auth;
|
创建一个角色:
|
1
|
postgres=``# CREATE ROLE django_auth WITH LOGIN PASSWORD 'asdfgh';
|
授予用户数据库访问权限:
|
1
|
postgres=``# GRANT ALL PRIVILEGES ON DATABASE auth TO django_auth;
|
安装心理学2包,它将允许我们使用我们配置的数据库:
|
1
|
pip ``install
psycopg2
|
编辑当前配置的SQLite数据库并使用Postgres数据库。
|
01
02
03
04
05
06
07
08
09
10
|
DATABASES = {
'default'``: {
'ENGINE'``: ``'django.db.backends.postgresql_psycopg2'``,
'NAME'``: ``'auth'``,
'USER'``: ``'django_auth'``,
'PASSWORD'``: ``'asdfgh'``,
'HOST'``: ``'localhost'``,
'PORT'``: ``''``,
}
}
|
创建模型
Django附带了一个内置的身份验证系统,非常详细,但有时我们需要进行调整,因此我们需要创建一个自定义的用户身份验证系统。我们的用户模型将从AbstractBaseUser
提供的类django.contrib.auth.models
.
在用户/Models.py中,我们首先创建用户模型来存储用户详细信息。
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
# users/models.py
from
__future__ ``import
unicode_literals
from
django.db ``import
models
from
django.utils ``import
timezone
from
django.contrib.auth.models ``import
(
AbstractBaseUser, PermissionsMixin
)
class
User(AbstractBaseUser, PermissionsMixin):
"""
An abstract base class implementing a fully featured User model with
admin-compliant permissions.
"""
email ``=
models.EmailField(max_length``=``40``, unique``=``True``)
first_name ``=
models.CharField(max_length``=``30``, blank``=``True``)
last_name ``=
models.CharField(max_length``=``30``, blank``=``True``)
is_active ``=
models.BooleanField(default``=``True``)
is_staff ``=
models.BooleanField(default``=``False``)
date_joined ``=
models.DateTimeField(default``=``timezone.now)
objects ``=
UserManager()
USERNAME_FIELD ``=
'email'
REQUIRED_FIELDS ``=
[``'first_name'``, ``'last_name'``]
def
save(``self``, ``*``args, ``*``*``kwargs):
super``(User, ``self``).save(``*``args, ``*``*``kwargs)
return
self
|
REQUIRED_FIELDS
包含用户模型上的所有必需字段,但用户名字段和密码除外,因为这些字段将始终被提示输入。
UserManager
是定义create_user
和createsuperuser
方法。这个类应该在AbstractBaseUser
类。让我们继续给它下定义。
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
from
django.contrib.auth.models ``import
(
AbstractBaseUser, PermissionsMixin, BaseUserManager
)
class
UserManager(BaseUserManager):
def
_create_user(``self``, email, password, ``*``*``extra_fields):
"""
Creates and saves a User with the given email,and password.
"""
if
not
email:
raise
ValueError(``'The given email must be set'``)
try``:
with transaction.atomic():
user ``=
self``.model(email``=``email, ``*``*``extra_fields)
user.set_password(password)
user.save(using``=``self``._db)
return
user
except``:
raise
def
create_user(``self``, email, password``=``None``, ``*``*``extra_fields):
extra_fields.setdefault(``'is_staff'``, ``False``)
extra_fields.setdefault(``'is_superuser'``, ``False``)
return
self``._create_user(email, password, ``*``*``extra_fields)
def
create_superuser(``self``, email, password, ``*``*``extra_fields):
extra_fields.setdefault(``'is_staff'``, ``True``)
extra_fields.setdefault(``'is_superuser'``, ``True``)
return
self``._create_user(email, password``=``password, ``*``*``extra_fields)
|
迁徙
迁移提供了一种在每次模型更改时更新数据库架构的方法,而不会丢失数据。
为用户模型创建初始迁移,并首次同步数据库。
|
1
2
3
|
python manage.py ``make
migrations ``users
python manage.py migrate
|
创建超级用户
通过运行以下命令创建超级用户:
|
1
|
python manage.py createsuperuser
|
创建新用户
让我们创建一个端点来启用新用户的注册。我们将从序列化用户模型字段开始。序列化程序提供了一种将数据更改为更易于理解的表单的方法,比如JSON或XML。反序列化则相反,即将数据转换为可以保存到数据库的表单。
创建用户/erializers.py并添加以下代码。
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
|
# users/serializers.py
from
rest_framework ``import
serializers
from``.models ``import
User
class
UserSerializer(serializers.ModelSerializer):
date_joined ``=
serializers.ReadOnlyField()
class
Meta(``object``):
model ``=
User
fields ``=
(``'id'``, ``'email'``, ``'first_name'``, ``'last_name'``,
'date_joined'``, ``'password'``)
extra_kwargs ``=
{``'password'``: {``'write_only'``: ``True``}}
|
CreateUserAPIView
接下来,我们希望创建一个视图,这样客户端将有一个URL来创建新用户。
在users.views.py中,添加以下内容:
|
01
02
03
04
05
06
07
08
09
10
11
|
# users/views.py
class
CreateUserAPIView(APIView):
# Allow any user (authenticated or not) to access this url
permission_classes ``=
(AllowAny,)
def
post(``self``, request):
user ``=
request.data
serializer ``=
UserSerializer(data``=``user)
serializer.is_valid(raise_exception``=``True``)
serializer.save()
return
Response(serializer.data, status``=``status.HTTP_201_CREATED)
|
我们permission_classes
到(AllowAny,)
允许任何用户(已验证或未验证)访问此URL。
配置URL
创建一个文件users/urls.py
并添加与我们创建的视图相匹配的URL。还添加以下代码。
|
1
2
3
4
5
6
7
8
|
# users/urls.py
from
django.conf.urls ``import
url, patterns
from
.views ``import
CreateUserAPIView
urlpatterns ``=
[
url(r``'^create/$'``, CreateUserAPIView.as_view()),
]
|
我们还需要将URL从用户应用程序导入到django_auth/urls.py
档案。那就去做吧。我们使用的是include
函数,所以不要忘记导入它。
|
1
2
3
4
5
6
7
8
9
|
# django_auth/urls.py
from
django.conf.urls ``import
url, include
from
django.contrib ``import
admin
urlpatterns ``=
[
url(r``'^admin/'``, admin.site.urls),
url(r``'^user/'``, include(``'users.urls'``, namespace``=``'users'``)),
]
|
现在,我们已经完成了创建端点的工作,让我们做一个测试,看看我们是否已经走上正轨。我们将用邮差来做测试。如果您不熟悉Postman,它是一个提供友好GUI的工具,用于构建请求和读取响应。
[图片上传失败...(image-e3fd1d-1572571821530)]
正如您在上面看到的,端点正在按预期工作。
认证用户
我们将使用我们在本教程开始时安装的Django-REST框架JWT Python模块。它为Django REST Framework应用程序添加了JWT身份验证支持。
但是首先,让我们为我们的令牌定义一些配置参数,以及它们是如何在setings.py文件中生成的。
|
01
02
03
04
05
06
07
08
09
10
|
# settings.py
import
datetime
JWT_AUTH ``=
{
'JWT_VERIFY'``: ``True``,
'JWT_VERIFY_EXPIRATION'``: ``True``,
'JWT_EXPIRATION_DELTA'``: datetime.timedelta(seconds``=``3000``),
'JWT_AUTH_HEADER_PREFIX'``: ``'Bearer'``,
}
|
-
JWT_VERIFY
::如果秘密是错误的,它会引起jwt.decodeError。 -
JWT_VERIFY_EXPIRATION
::将过期设置为True,意味着令牌将在一段时间后过期。默认时间是五分钟。 -
JWT_AUTH_HEADER_PREFIX
::与令牌一起发送所需的授权头值前缀。我们把它设为Bearer
,缺省值为JWT
.
在……里面users/views.py
,添加以下代码。
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
@api_view``([``'POST'``])
@permission_classes``([AllowAny, ])
def
authenticate_user(request):
try``:
email ``=
request.data[``'email'``]
password ``=
request.data[``'password'``]
user ``=
User.objects.get(email``=``email, password``=``password)
if
user:
try``:
payload ``=
jwt_payload_handler(user)
token ``=
jwt.encode(payload, settings.SECRET_KEY)
user_details ``=
{}
user_details[``'name'``] ``=
"%s %s"
%
(
user.first_name, user.last_name)
user_details[``'token'``] ``=
token
user_logged_in.send(sender``=``user.__class__,
request``=``request, user``=``user)
return
Response(user_details, status``=``status.HTTP_200_OK)
except
Exception as e:
raise
e
else``:
res ``=
{
'error'``: ``'can not authenticate with the given credentials or the account has been deactivated'``}
return
Response(res, status``=``status.HTTP_403_FORBIDDEN)
except
KeyError:
res ``=
{``'error'``: ``'please provide a email and a password'``}
return
Response(res)
|
在上面的代码中,登录视图以用户名和密码作为输入,然后创建一个令牌,其中包含与传递的凭据对应的用户信息作为有效负载,并将其返回给浏览器。其他用户详细信息(如名称)也与令牌一起返回到浏览器。此令牌将用于在未来的请求中进行身份验证。
权限类设置为allowAny
因为任何人都可以访问这个端点。
我们还使用此代码存储用户的最后登录时间。
|
1
2
|
user_logged_in.send(sender``=``user.__class__,
request``=``request, user``=``user)
|
每当用户想要发出API请求时,他们都必须在Auth Header中发送令牌,以便对请求进行身份验证。
让我们用Postman测试这个端点。打开Postman并使用请求与您以前创建的用户之一进行身份验证。如果登录尝试成功,响应将如下所示:
[图片上传失败...(image-fd3458-1572571821530)]
检索和更新用户
到目前为止,用户可以注册并验证自己。然而,他们也需要一种方法来检索和更新他们的信息。让我们来实现这个。
在……里面users.views.py
,添加以下代码。
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
class
UserRetrieveUpdateAPIView(RetrieveUpdateAPIView):
# Allow only authenticated users to access this url
permission_classes ``=
(IsAuthenticated,)
serializer_class ``=
UserSerializer
def
get(``self``, request, ``*``args, ``*``*``kwargs):
# serializer to handle turning our
Userobject into something that
# can be JSONified and sent to the client.
serializer ``=
self``.serializer_class(request.user)
return
Response(serializer.data, status``=``status.HTTP_200_OK)
def
put(``self``, request, ``*``args, ``*``*``kwargs):
serializer_data ``=
request.data.get(``'user'``, {})
serializer ``=
UserSerializer(
request.user, data``=``serializer_data, partial``=``True
)
serializer.is_valid(raise_exception``=``True``)
serializer.save()
return
Response(serializer.data, status``=``status.HTTP_200_OK)
|
我们首先定义权限类并设置为IsAuthenticated
因为这是一个受保护的URL,只有经过身份验证的用户才能访问它。
然后,我们定义一个get
方法检索用户详细信息。检索用户详细信息后,经过身份验证的用户将按需要更新他们的详细信息。
更新您的URL以定义端点,如下所示。
|
1
2
3
4
5
6
7
|
users``/``urls.py
from
.views ``import
CreateUserAPIView, UserRetrieveUpdateAPIView
urlpatterns ``=
[
url(r``'^update/$'``, UserRetrieveUpdateAPIView.as_view()),
]
|
为了使请求成功,报头应该包含如下所示的JWT令牌。
[图片上传失败...(image-127fb2-1572571821530)]
如果尝试在没有身份验证头的情况下请求资源,则会得到以下错误。
[图片上传失败...(image-28598d-1572571821530)]
如果用户的停留时间超过了JWT_EXPIRATION_DELTA
如果不发出请求,令牌将过期,他们将不得不请求另一个令牌。下文也说明了这一点。
[图片上传失败...(image-2c45cd-1572571821530)]
结语
本教程介绍了如何使用JSONWeb令牌成功构建可靠的后端身份验证系统所必需的内容。