[Mobilar] 06 - DRF: Token

一些复杂的页面,还是使用vue, react开发会好一些,但会涉及到一个permission的问题,也就是接下来我们要关注的部分。

Goto: Django Rest-framework

 

 

一、基础回顾

Ref: [Django] 02 - Django REST Framework (DRF) 【打基础】

 

 

二、Token

Ref: REST API Token Authentication for Mobile Apps

Ref: Register a New User (Django Rest framework)

REST API 的代码单独放在了 ./api 文件夹中。

 

  • 模型 account/

文件 Models.py

class Account(AbstractBaseUser):
email
= models.EmailField(verbose_name="email", max_length=60, unique=True) username = models.CharField(max_length=30, unique=True) date_joined = models.DateTimeField(verbose_name='date joined', auto_now_add=True) last_login = models.DateTimeField(verbose_name='last login', auto_now=True) is_admin = models.BooleanField(default=False) is_active = models.BooleanField(default=True) is_staff = models.BooleanField(default=False) is_superuser = models.BooleanField(default=False) USERNAME_FIELD = 'email' REQUIRED_FIELDS = ['username'] objects = MyAccountManager()  # ----> def __str__(self): return self.email # For checking permissions. to keep it simple all admin have ALL permissons def has_perm(self, perm, obj=None): return self.is_admin # Does this user have permission to view this app? (ALWAYS YES FOR SIMPLICITY) def has_module_perms(self, app_label): return True

 

文件 modes.py

class MyAccountManager(BaseUserManager):
    #
    # 创建普通用户
    #
    def create_user(self, email, username, password=None):
        if not email:
            raise ValueError('Users must have an email address')
        if not username:
            raise ValueError('Users must have a username')

        user = self.model(
            email=self.normalize_email(email),
            username=username,
        )

        user.set_password(password)
        user.save(using=self._db)
        return user

    #
    # 创建超级用户
    #
    def create_superuser(self, email, username, password):
        user = self.create_user(
            email=self.normalize_email(email),
            password=password,
            username=username,
        )
        user.is_admin = True
        user.is_staff = True
        user.is_superuser = True
        user.save(using=self._db)
        return user

 

文件 modes.py

@receiver(post_save, sender=settings.AUTH_USER_MODEL)
def create_auth_token(sender, instance=None, created=False, **kwargs):
    if created:
        Token.objects.create(user=instance)    # <---- 对应了一个db中单独的一个表。

 

数据存储的方式,样例如下。

 

Ref: https://www.django-rest-framework.org/api-guide/authentication/#tokenauthentication

根据链接中内容,修改authentication的方式。

REST_FRAMEWORK = {
   # 'DEFAULT_AUTHENTICATION_CLASSES': [
        # 'rest_framework.authentication.BasicAuthentication',
        # 'rest_framework.authentication.SessionAuthentication',
   # ],
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework.authentication.TokenAuthentication',
    ),
    'DEFAULT_PERMISSION_CLASSES': (
        'rest_framework.permissions.IsAuthenticated',
    ),
    'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
    'PAGE_SIZE': 10,
}

 

  • REST API account/api/

文件 api/urls.py

urlpatterns = [
    path('check_if_account_exists/', does_account_exist_view,       name="check_if_account_exists"),
    path('change_password/',         ChangePasswordView.as_view(),  name="change_password"),
    path('properties',               account_properties_view,       name="properties"),
    path('properties/update',        update_account_view,           name="update"),
    path('login',                    ObtainAuthTokenView.as_view(), name="login"), 
    path('register',                 registration_view,             name="register"),  # <----
]

 

文件 api/views.py

@api_view(['POST', ])
@permission_classes([])
@authentication_classes([])
def registration_view(request):

    if request.method == 'POST':
        data = {}
        email = request.data.get('email', '0').lower()
        if validate_email(email) != None:
            data['error_message'] = 'That email is already in use.'
            data['response'] = 'Error'
            return Response(data)

        username = request.data.get('username', '0')
        if validate_username(username) != None:
            data['error_message'] = 'That username is already in use.'
            data['response'] = 'Error'
            return Response(data)

        # ??
        serializer = RegistrationSerializer(data=request.data)
        
        if serializer.is_valid():
            #
            # {
            #     "response": "successfully registered new user.",
            #     "email": "test1223@tabian.ca",
            #     "username": "test1232",
            #     "pk": 1,
            #     "token": "c2f020e5d8a88d888d2da67e08098f5113f753a5"
            # }
            #
            account = serializer.save()
            data['response'] = 'successfully registered new user.'
            data['email']    = account.email
            data['username'] = account.username
            data['pk']       = account.pk
            data['token']    = Token.objects.get(user=account).key  # <---- 将要返回给 client 的token
        else:
            data = serializer.errors
        return Response(data)

 

文件 api/serializers.py

from rest_framework import serializers
from account.models import Account


class
RegistrationSerializer(serializers.ModelSerializer): password2 = serializers.CharField(style={'input_type': 'password'}, write_only=True) class Meta: model = Account fields = ['email', 'username', 'password', 'password2'] extra_kwargs = { 'password': {'write_only': True}, } def save(self): account = Account( email=self.validated_data['email'], username=self.validated_data['username'] ) password = self.validated_data['password'] password2 = self.validated_data['password2'] if password != password2: raise serializers.ValidationError({'password': 'Passwords must match.'})
account.set_password(password) account.save()
return account

 

 

三、客户端的 Token

 直接在 request 中包含 token 发送出去,安全嚒?

 Token 是浏览器共享么?

 

  • 服务端 token 验证

加一个装饰器,就自动验证了呢。查看 username 与 当前的 token 是否都回答正确。但token是无状态的。

文件 views.py

@api_view(['GET', ])
@permission_classes((IsAuthenticated, ))
def account_properties_view(request):

    try:
        account = request.user
    except Account.DoesNotExist:
        return Response(status=status.HTTP_404_NOT_FOUND)

    if request.method == 'GET':
        serializer = AccountPropertiesSerializer(account)
        return Response(serializer.data)

  

  • token 和 session 的区别

详见: [Wagtail] Topic: REST API Token

 

End.

posted @ 2021-02-10 14:14  郝壹贰叁  阅读(88)  评论(0编辑  收藏  举报