美文网首页Python前端
(三) Django REST实践:WEB登录原理与用户系统实现

(三) Django REST实践:WEB登录原理与用户系统实现

作者: DillionMango | 来源:发表于2018-09-27 15:04 被阅读119次

    本小节大概需要花费30分钟。

    在第二节,我们已经学会了如何写一个最简单的REST API。在这一节,我们将利用这种REST API通讯模型,构建一个用户系统,包括注册、登录、访问控制、登出。


    Web登录原理

    当我们发起一个HTTP请求的时候,服务器如何根据这个请求判断我们是否登录了呢?这就要说到Cookies和Session了。

    Cookies是服务器用来临时在客户端(也就是浏览器)上保存数据的,是一个个的key-value对。当我们向服务器发起一个HTTP请求时,服务器的Response会携带Cookies的信息。这个时候我们的浏览器就会保存这些Cookies的信息,并在下一次发送HTTP请求的时候携带上这些Cookies信息。

    在早先的时候,服务器为了保存某一个会话相关的信息,会把一些比较重要的数据放到用户的Cookies里面。这样做不太安全,于是有人提出了Session。服务器通过给客户端的Cookies设置一个session_id(一个随机字符串)来标识会话,并在服务器端保存和这个session_id相关的数据。这样服务器端既能知道当前是哪个会话,又能知道和这个会话相关的数据都有什么,同时还避免了将一些重要的数据存放到客户端。如下图,是在Chrome里截取的HTTP Request Header,这个Request Header里面的Cookies段里所表示的,就是访问某个Django后端时用到的session_id

    session_id

    在Web服务器后端第一次接到某个请求的时候,会返回一个带session_id的Cookies给浏览器。之后浏览器每次请求,都会带上这个Cookies。为了判断用户是否登录,我们会在后端保存一个和这个session_id相关的字段,如is_logined以及user_id。若is_loginedtrue,则持有这个session_id的请求就判断为登录了,且相关用户的id为user_id,否则判断为未登录。同时,我们可以为session_id这个Cookies加一个超时时间,来控制一次登录操作可以维持多长时间(如一小时、一星期等)。超过这个时间,session_id会失效,并要求用户重新登录。

    Session

    Django中,Session相关的信息是存储在数据库中的。所以在使用Session之前,还需要初始化数据库:

    > python manage.py makemigrations
    No changes detected
    > python manage.py migrate
    Operations to perform:
      Apply all migrations: admin, auth, contenttypes, sessions
    Running migrations:
      Applying contenttypes.0001_initial... OK
      Applying auth.0001_initial... OK
      Applying admin.0001_initial... OK
    ... ...
    

    在Django中操作Session的方法也比较直接:

    def test_session(request):
        print(request.session.get("privacy"))
        request.session["privacy"] = "This message should not be stored in Cookies"
    
        return HttpResponse()
    

    可以看到,可以直接通过request.session来获取和改变和这个session相关的值。

    Cookies

    对于Cookies,可以用如下方法进行访问、修改:

    def test_cookies(request):
        print(request.COOKIES)
        response = HttpResponse()
        response.set_cookie("this_is_key", "this is value")
        return response
    

    在第一次访问这个接口的时候,打印出的Cookies只包含sessionid。之后的请求会打印出如下Cookies:

    {'this_is_key': 'this is value', 'sessionid': '32xgq0cw0bqgnfg0qu81sp19n2el49yl'}
    

    总结一下,针对Cookies的基本操作有以下几种:

    • 可以通过request.COOKIES这个dict直接访问请求中携带的Cookies
    • 可以通过response.set_cookie(<key>, <value>)来设置Cookies
    • 可以通过response.delete_cookie(<key>)来删除某个Cookies

    其中set_cookies函数还可以加入Cookies超时时间、可用域等属性。有兴趣的可以在浏览器里试一下,这些操作会产生怎样的效果。

    注册、登录、访问控制、登出功能

    有了上面的基础,想必你已经想到如何在Django中自己实现一套用户认证模块了:

    • 注册:用户进行注册请求,在数据库中保存其用户名,以及加密后的密码
    • 登录:在用户进行登录请求时,判断其用户名密码是否正确,是的话即将session中的is_logined置为true
    • 访问控制:用户访问一个需要登录的接口时,首先判断request.session中的is_logined是否为true。若为true则继续执行,否则将用户重定向到登录页面
    • 登出:用户登出时,只需要将request.session中的is_logined置为false即可(或将该session_id置为无效)

    但是Django提供了一套比较方便的用户认证模块,其基本原理就像我们上面所说的。何不直接使用它呢?

    准备

    为了不同类型接口之间功能隔离起见,我们在Django中新建一个app:

    > python manage.py startapp account
    

    Django中app主要就是为了起到功能隔离的作用,不同模块之间保持相对独立的状态。这样既有利于模块之间的解耦,也有利于单个模块的复用。比如我们今天做的认证模块,就可以实现成可以被复用的模块。

    可以看到,项目下多了一个account目录。其目录结构和task_platform相似。我们将在account/views.py下填入我们的用户认证模块的逻辑代码。在此之前还需要做几个工作:

    1. account填入task_platform/settings.py里的INSTALLED_APPS中:
    ...
    INSTALLED_APPS = [
        'django.contrib.admin',
        'django.contrib.auth',
        'django.contrib.contenttypes',
        'django.contrib.sessions',
        'django.contrib.messages',
        'django.contrib.staticfiles',
        'account',
    ]
    ...
    
    1. account/urls.py中添加url路由(没有该文件则自己创建一个):
    from django.urls import path
    
    from account import views
    
    urlpatterns = [
        path('login/', views.user_login),
        path('logout/', views.user_logout),
        path('register/', views.user_register),
        path('detail/', views.user_detail),
    ]
    
    1. task_platform/urls.py将与account相关的url代理到account这个app中:
    from django.urls import path, include
    
    urlpatterns = [
        ...
        path('account/', include('account.urls')),
    ]
    

    这段代码的意思是:只要是以account/开头的url,都转移给第一步中我们创建的account.urls进行代理。

    经过这几个设置后,当我们访问account/login/的时候,服务器就会相应的执行account.views.user_login这个函数。

    1. 如果之前没有初始化数据库,则需要初始化数据库,用来存放用户信息,以及Session相关的信息:
    > python manage.py makemigrations
    > python manage.py migrate
    

    下面将在account/views.py中填入具体逻辑。

    注册

    account/views.py前面一段为:

    import json
    
    from django.contrib.auth import authenticate, login, logout
    from django.contrib.auth.decorators import login_required
    from django.contrib.auth.models import User
    from django.http import JsonResponse
    
    def __get_response_json_dict(data, err_code=0, message="Success"):
        ret = {
        'err_code': err_code,
        'message': message,
        'data': data
        }
        return ret
    

    注册具体逻辑如下:

    def user_register(request):
        received_data = json.loads(request.body.decode('utf-8'))
    
        username = received_data["username"]
        password = received_data["password"]
    
        user = User(username=username)
        user.set_password(password)
    
        user.save()
    
        return JsonResponse(__get_response_json_dict(data={}))
    

    其中:

    • User为Django认证模块内置的model,用来存放用户的基本信息,包括用户名以及密码
    • set_password将password进行加密存储
    • save将我们在程序中新建的User对象保存到数据库中

    其请求为:

    {
        "username": "Marry",
        "password": "password"
    }
    

    响应为:

    {"err_code": 0, "message": "Success", "data": {}}
    

    登录

    def user_login(request):
        received_data = json.loads(request.body.decode('utf-8'))
    
        username = received_data["username"]
        password = received_data["password"]
    
        user = authenticate(username=username, password=password)
        if user:
            login(request, user)
            return JsonResponse(__get_response_json_dict(data={}))
        else:
            return JsonResponse(__get_response_json_dict(data={}, err_code=-1, message="Invalid username or password"))
    

    其中:

    • authenticate为Django认证模块函数,用于检查用户名、密码是否正确。若正确,则返回相应的User对象,否则返回None
    • login函数为Django认证模块函数,用于标记用户的登录标志,类似于我们之前说的,将与该session相关的is_logined字段置为true

    其请求为:

    {
        "username": "Marry",
        "password": "password"
    }
    

    响应为:

    {"err_code": 0, "message": "Success", "data": {}}
    

    访问控制

    @login_required
    def user_detail(request):
    
        response_data = {"username": request.user.username}
    
        return JsonResponse(__get_response_json_dict(data=response_data))
    

    其中

    • @login_required为Django认证模块自带的装饰器,用于在进入处理函数之前,对其是否已登录进行检查。其原理类似于检查与该请求session_id相对应的is_logined字段是否为true
    • 如果用户已经登录,则可以直接通过request.user获取到登录用户的User对象,并通过request.user.<属性>来访问该User对象的属性

    其响应为:

    {"err_code": 0, "message": "Success", "data": {"username": "Marry"}}
    

    登出

    def user_logout(request):
        logout(request)
        return JsonResponse(__get_response_json_dict(data={}))
    

    其中:

    • logout为Django认证模块内置函数,用于将用户在会话中登出。其原理类似于将与该session相关的is_logined字段置为false,或者直接将该Cooikes(session_id)置为无效。

    其响应为:

    {"err_code": 0, "message": "Success", "data": {}}
    

    登出后,如果我们再访问account/detail/,则会收到一个code为302的response,将我们重定向到accounts/login/这个url。这是个行为是@login_required的默认行为,用于用户在访问某些需要登录的接口时,直接将其重定向到登录界面。这个重定向到的url可以通过修改task_platform/settings.py内的LOGIN_REDIRECT_URL来该设置。

    总结

    本节中,我们分析了Web应用实现用户认证模块的基本原理(Session,Cookies),并利用Django自带的用户认证模块,实现了一套简单的用户认证REST API,包含注册、登录、访问控制、登出。本文中所罗列的逻辑,只包含了最核心的代码。还有一些异常情况(如注册同用户名账号时产生的异常),需要你自己写代码来处理。当你实现好这些后,几乎可以将account这个模块用于所有需要认证的Django项目中。

    练习

    • 考虑上面所实现的用户认证函数中的需要处理的异常情况,罗列出来,并逐一实现
    • 考虑如何用已有知识,实现二次认证(如邮箱认证)
    • 考虑如何自己实现一个@login_required,以将其在用户未登录时,进行“重定向”这个默认行为,改变为返回一个Json Response,其err_code-1messagePermission Denied

    相关文章

      网友评论

      本文标题:(三) Django REST实践:WEB登录原理与用户系统实现

      本文链接:https://www.haomeiwen.com/subject/tzxiuftx.html