Django -mongoDB(mongoengine)-自定义用户认证登录

公司的一个Django项目用的是mongoDB,所以Django自带的那一套权限不能使用,我们就自定义了一套,在每个重写的地方都附带了源码地址,帮助理解

request.user

  1. 从setting中获取AUTHENTICATION_BACKENDS的元组, 默认情况下是django.contrib.auth.backends.ModelBackend.
  2. 遍历这整个元组,然后调用每一个Backend的authenticate方法,而每个Backend.authenticate会有一个登陆逻辑(自定义后面会提及),如ModelBackend.authenticate它拿传过来的账号密码的与数据库user model的username与password进行验证。
  3. 如果Backend.authenticate验证通过,就会返回一个user对象,然后auth.authenticate将不再进行遍历,return user对象。如果返回None,就继续调用下一个Backend.authenticate。如果全部都返回None,那么验证不通过。
  4. 一个 AUTH_USER_MODEL 类型的对象,表示当前登录的用户,存在于上下文,可以使用,配合LoginRequiredMixin使用
  5. 如果用户当前没有登录,user 将设置为 django.contrib.auth.models.AnonymousUser 的一个实例。你可以通过 is_authenticated() 区分它们
  6. request.user传给前端的时候,前端可以通过 {%  if request.user.is_authenticated  %}判断用户时候登录

通过authenticate()返回一个user对象

user = authenticate(email=email, password=secret_password)

#logi()接受一个 HttpRequest 对象和一个 User 对象作为参数并使用Django的会话( session )框架把用户的ID保存在该会话中

login(request, user)

AUTHENTICATION_BACKENDS

检查身份验证。当有人调用时 django.contrib.auth.authenticate()- 如如何记录用户中所述 - Django尝试对其所有身份验证后端进行身份验证。如果第一个身份验证方法失败,Django会尝试第二个身份验证方法,依此类推,直到尝试了所有后端。

在settings.py 配置

AUTHENTICATION_BACKENDS = ['persona.apps.users.backends.DocumentBackend']

persona.apps.users.backends.DocumentBackend 

https://docs.djangoproject.com/en/2.0/topics/auth/customizing/

from persona.apps.users.models import Users
# from django.contrib.auth.backends import ModelBackend

class DocumentBackend:
    #从数据库中匹配用户
    #get_by_id()自定义匹配用户的方法

    def get_user(self, user_id):
        return Users().get_by_id(user_id)  
    
    #自定义的authenticate

    def authenticate(self, request, email=None, password=None):

        #如果账号密码正确,返回user对象
        #check_password(email, password),自定义的用户认证

        if Users().check_password(email, password):
            #get_by_email()自定义
            return Users().get_by_email(email)
        return None

from django.contrib import auth,重写了其中的login,logout,get_user

login()

  1. login()接受一个 HttpRequest 对象和一个 User 对象作为参数并使用Django的会话( session )框架把用户的ID保存在该会话中
  2. 生成一个新的session_id 放到request.session中(这个session在返回时会被session的中间件进行处理,将session_id放入到cookie当中)
  3. 将这个user放到request.user中

logout()

  1. 将request.session清除
  2. request.user = AnonymousUser()将用户设置与匿名用户

https://docs.djangoproject.com/en/2.1/topics/auth/default/#auth-web-requests

from django.contrib.auth import *
from .models import *
from persona.apps.users.backends import DocumentBackend


SESSION_KEY = '_auth_user_id'
BACKEND_SESSION_KEY = '_auth_user_backend'
HASH_SESSION_KEY = '_auth_user_hash'
REDIRECT_FIELD_NAME = 'next'

def login(request, user, backend=None):
    """
    Persist a user id and a backend in the request. This way a user doesn't
    have to reauthenticate on every request. Note that data set during
    the anonymous session is retained when the user logs in.
    """
    session_auth_hash = ''
    if user is None:
        user = request.user
    if hasattr(user, 'get_session_auth_hash'):
        session_auth_hash = user.get_session_auth_hash()
    request.session[SESSION_KEY] = user.id_to_string()
    request.session[HASH_SESSION_KEY] = session_auth_hash
    request.user = user
    rotate_token(request)
    user_logged_in.send(sender=user.__class__, request=request, user=user)  


def logout(request):
    """
    Remove the authenticated user's ID from the request and flush their session
    data.
    """
    # Dispatch the signal before the user is logged out so the receivers have a
    # chance to find out *who* logged out.
    user = getattr(request, 'user', None)
    if not getattr(user, 'is_authenticated', True):
        user = None
    user_logged_out.send(sender=user.__class__, request=request, user=user)

    # remember language choice saved to session
    language = request.session.get(LANGUAGE_SESSION_KEY)

    request.session.flush()

    if language is not None:
        request.session[LANGUAGE_SESSION_KEY] = language

    if hasattr(request, 'user'):
        from django.contrib.auth.models import AnonymousUser
        request.user = AnonymousUser()


#LoginRequiredMixin会调用这里

def get_user(request):
    #如果是没有登录的用户,则设置为匿名用户

    from django.contrib.auth.models import AnonymousUser
    user = None
    if request.session.__contains__(SESSION_KEY):
        user_id = request.session[SESSION_KEY]
 
        user =  DocumentBackend().get_user(user_id)

    #返回user 或者 匿名用户
    return user or AnonymousUser()

from django.contrib.auth.middleware import AuthenticationMiddleware,重写了AuthenticationMiddleware,并且在setting中配置

用户每次发起请求的时候,被AuthenticationMiddleware中间件处理,会去调用auth模块,auth的get_user方法会返回当前user,如果是没有登录验证的用户,则会返回一个匿名用户,将user表示当前登录用户的属性添加到每个传入HttpRequest对象,也就是request.user

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    # 'django.contrib.auth.middleware.AuthenticationMiddleware',
    'persona.apps.users.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

 persona.apps.users.middleware.AuthenticationMiddleware

from django.utils.deprecation import MiddlewareMixin
from persona.apps.users import auth
from django.contrib.auth import authenticate
# from django.contrib import auth
# from django.contrib.auth.middleware import AuthenticationMiddleware

#这一段源码在django.contrib.auth.middleware中
def get_user(request):
    if not hasattr(request, '_cached_user'):
        request._cached_user = auth.get_user(request)
    return request._cached_user

class AuthenticationMiddleware(MiddlewareMixin):
    def process_request(self, request):
        request.user = get_user(request)

https://docs.djangoproject.com/en/2.1/ref/middleware/

Django用户发起访问流程

用户发起访问流程,会先经过中间件,中间件调用auth.get_user(),调用auth则会调用AUTHENTICATION_BACKENDS验证方法,返回一个user对象(如果是没有登录的用户则返回一个匿名user对象,在类视图中继承了LoginRequiredMixin,如果是具体的user对象,则会分发请求,如果是匿名的user对象,则返回登录),将user表示当前登录用户的属性添加到每个传入HttpRequest对象,也就是request.user。

调用login,logout(django.contrib import auth中的方法)等都会调用到AUTHENTICATION_BACKENDS

另外,还自定义了一些验证方法

from datetime import datetime, timedelta
from django.contrib.auth.base_user import AbstractBaseUser
from django.contrib.auth.hashers import make_password
from django.db import models
from mongoengine import *
# from persona.apps.users.backends import DocumentBackend
from persona.settings import DBNAME
# Create your models here.

connect(DBNAME)


class Users(Document):
    """用户数据库"""
    account = EmailField(max_length=40)
    password = StringField(max_length=150)
    stars = IntField(max_value=5)
    signUpDate = DateTimeField()
    lastSignIn = DateTimeField()
    profile = DictField()                   # 基本资料
    friends = ListField()                   # 好友
    created = ListField()                   # 创建的
    liked = ListField()                     # 收藏
    labeled = DictField()                   # 标签'LABEL': [PERSONA_ID, PERSONA_ID, PERSONA_ID],
    browsed = ListField()                   # 最近浏览的
    point = IntField(default=0)             # 积分
    invcode = IntField(null=False)          # 邀请码
    vip = BooleanField(default=False)       # 会员
    duetime = StringField(null=True)        # 到期时间
    rookie = BooleanField(default=True)     # 是否为新手
    is_authenticated = True


    def id_to_string(self):
        '''session_key'''
        result = 0
        try:
            result = str(self.id)
        except:
            pass
        return result

    # 通过id 匹配 user
    def get_by_id(self, user_id):
        try:
            return Users.objects.get(id=user_id)
        except DoesNotExist:
            return None

    # 通过email 匹配 user
    def get_by_email(self, user_email):
        try:
            return Users.objects.get(account=user_email)
        except DoesNotExist:
            return None

    #检查账户密码
    def check_password(self, email, password):
        try:
            user = Users.objects.get(account=email, password=password)
            if user is not None:
                return True
            return False
        except DoesNotExist:
            return False

猜你喜欢

转载自blog.csdn.net/qq_42684157/article/details/83580863