本教程将介绍JSON Web令牌(JWT)以及如何在Django中实现JWT身份验证。

什么是JWT?

JWT是一个编码的JSON字符串,该字符串在标头中传递以验证请求。 通常是通过使用密钥对JSON数据进行散列来获得的。 这意味着服务器不需要每次都查询数据库来检索与给定令牌关联的用户。

JSON Web令牌如何工作

当用户使用其凭据成功登录时,将获取JSON Web令牌并将其保存在本地存储中。 每当用户想要访问受保护的URL时,都会在请求的标头中发送令牌。 然后,服务器在Authorization标头中检查有效的JWT,如果找到该JWT,将允许该用户访问。

典型的内容标头如下所示:

Authorization: Bearer eyJhbGciOiJIUzI1NiIsI

下图显示了此过程:

JSON Web令牌如何工作

认证和授权的概念

身份验证是标识已登录用户的过程,而授权是标识特定用户是否有权访问Web资源的过程。

API范例

在本教程中,我们将使用JWT作为身份验证机制在Django中构建一个简单的用户身份验证系统。

要求

  • Django的
  • Python

让我们开始吧。

创建一个目录,您将在其中保留您的项目,并在其中创建一个虚拟环境来安装项目依赖项。

mkdir myprojects

cd myprojects

virtual venv

激活虚拟环境:

source venv/bin/activate

创建一个Django项目。

django-admin startproject django_auth

使用pip安装DRF和django-rest-framework-jwt。

pip install djangorestframework
pip install djangorestframework-jwt
pip install django

让我们继续并将DRF添加到settings.py文件中已安装应用程序的列表中。

配置JWT设置

为了使用JWT,我们需要配置django-rest-framework权限以接受JSON Web令牌。

settings.py文件中,添加以下配置:

REST_FRAMEWORK = {
  'DEFAULT_AUTHENTICATION_CLASSES': (
    'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
  ),
}

创建一个名为“用户”的新应用,该应用将处理用户身份验证和管理。

cd django-auth
django-admin.py startapp users

将用户应用程序添加到settings.py文件中已安装应用程序的列表中。

设置数据库

我们将使用PostgreSQL数据库,因为它更加稳定和强大。

创建auth数据库并分配用户。

通过输入以下命令切换到计算机上的Postgres帐户:

sudo su postgres

访问Postgres提示并创建数据库:

psql
postgres=# CREATE DATABASE auth;

创建一个角色:

postgres=# CREATE ROLE django_auth WITH LOGIN PASSWORD 'asdfgh';

向用户授予数据库访问权限:

postgres=# GRANT ALL PRIVILEGES ON DATABASE auth TO django_auth;

安装psycopg2软件包,这将使我们能够使用配置的数据库:

pip install psycopg2

编辑当前配置的SQLite数据库并使用Postgres数据库。

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql_psycopg2',
        'NAME': 'auth',
        'USER': 'django_auth',
        'PASSWORD': 'asdfgh',
        'HOST': 'localhost',
        'PORT': '',
    }
}

创建模型

Django附带了一个非常复杂的内置身份验证系统,但是有时我们需要进行调整,因此我们需要创建一个自定义的用户身份验证系统。 我们的用户模型将从django.contrib.auth.models提供的AbstractBaseUser类继承。

在users / models.py中,我们首先创建User模型来存储用户详细信息。

# 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_usercreatesuperuser方法的类。 该类应该位于我们上面定义的AbstractBaseUser类之前。 让我们继续进行定义。

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)
移居

迁移提供了一种在每次模型更改时更新数据库架构的方法,而不会丢失数据。

为我们的用户模型创建初始迁移,并首次同步数据库。

python manage.py make migrations users

python manage.py migrate
创建一个超级用户

通过运行以下命令来创建超级用户:

python manage.py createsuperuser

创建新用户

让我们创建一个端点来启用新用户的注册。 我们将从序列化User model字段开始。 序列化器提供了一种将数据更改为更易于理解的形式(如JSON或XML)的方法。 反序列化则相反,将数据转换为可以保存到数据库的形式。

创建users / serializers.py并添加以下代码。

# 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中,添加以下内容:

# 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

创建一个文件users/urls.py并添加URL以匹配我们创建的视图。 还要添加以下代码。

# 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函数,所以不要忘记导入它。

# 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进行测试。 如果您不熟悉Postman,则它是一个提供友好的GUI的工具,用于构造请求和读取响应。

配置URL

如您在上面看到的,端点按预期方式工作。

验证用户

我们将使用在本教程开始时安装的Django-REST Framework JWT Python模块。 它为Django Rest Framework应用添加了JWT身份验证支持。

但首先,让我们为令牌定义一些配置参数,以及如何在settings.py文件中生成它们。

# 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 :需要与令牌一起发送的Authorization标头值前缀。 我们将其设置为Bearer ,默认值为JWT

users/views.py ,添加以下代码。

@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

我们还将使用此代码存储用户的上次登录时间。

user_logged_in.send(sender=user.__class__,
                                    request=request, user=user)

每次用户想要发出API请求时,他们都必须在Auth Headers中发送令牌以对请求进行身份验证。

让我们用Postman测试这个端点。 打开Postman并使用该请求向您先前创建的用户之一进行身份验证。 如果登录尝试成功,则响应将如下所示:

样本回应
检索和更新用户

到目前为止,用户可以注册并进行身份验证。 但是,他们还需要一种检索和更新其信息的方法。 让我们实现它。

users.views.py ,添加以下代码。

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 `User` object 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以如下定义端点。

users/urls.py
from .views import CreateUserAPIView, UserRetrieveUpdateAPIView

urlpatterns = [
    
    url(r'^update/$', UserRetrieveUpdateAPIView.as_view()),
]

为了使请求成功,标头应包含JWT令牌,如下所示。

具有JWT令牌的标头

如果尝试请求不带身份验证头的资源,则会收到以下错误。

错误示例

如果用户在没有提出请求的情况下停留在JWT_EXPIRATION_DELTA指定的时间之外,则令牌将过期,并且他们将不得不请求另一个令牌。 这也在下面说明。

JWT_EXPIRATION_DELTA示例

结论

本教程介绍了使用JSON Web令牌成功构建可靠的后端身份验证系统所必需的内容。

翻译自: https://code.tutsplus.com/tutorials/how-to-authenticate-with-jwt-in-django--cms-30460

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐