zoukankan      html  css  js  c++  java
  • 学生管理之登录实现

    一、普通登录验证

     众所周知,一个网站涉及到数据的增删改查,所以对于访问网站的用户必须是经过合法验证的,这样才能对其授予权限进行操作。同时,也可以方便对登录的用户行为进行审计。

    一般的登录验证过程:输入用户名和密码,判断用户输入信息是否正确,如果正确,则登录成功,进入主页;如果错误,则提示用户重新输入。

    实现过程如下:

    urls.py文件:

    urlpatterns = [
         url(r'^login.html$', views.login),
       url(r'^index.html$', views.index),
    ]
    

      

    views.py文件,其中index.html为登录成功后的页面,login.html为登录页面。

    def login(request):
        #提示信息
        message = ""
        if request.method == "POST":
            user = request.POST.get('user')
            pwd = request.POST.get('pwd')
            c = models.Administrator.objects.filter(username=user, password=pwd).count()
            if c:
                rep = redirect('/index.html')
                return rep
            else:
                message = "用户名或密码错误"
        obj = render(request,'login.html', {'msg': message})
        return obj
    

      

     

    index.html视图函数如下:

    def index(request):
        return render(request,'index.html')
    

     

    如果验证成功,则跳转到欢迎页,index.html。

    index.html网页内容如下:

    {% extends "layout.html" %}
    
    {% block css %}
    
    {% endblock %}
    
    
    {% block content %}
        <h1>欢迎学生管理中心</h1>
    {% endblock %}
    
    
    {% block js %}
    
    {% endblock %}
    

      

    login.html文件:提交url为login.html,提交方法为POST。

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
        <style>
            label{
                 80px;
                text-align: right;
                display: inline-block;
            }
        </style>
    </head>
    <body>
        <form action="login.html" method="post">
            <div>
                <label for="user">用户名:</label>
                <input id="user" type="text" name="user" />
            </div>
            <div>
                <label for="pwd">密码:</label>
                <input id="pwd" type="password" name="pwd" />
            </div>
            <div>
                <label> </label>
                <input type="submit" value="登录" />
                <span style="color: red;">{{ msg }}</span>
            </div>
        </form>
    </body>
    </html>
    

      

    体验过程如下:

     

    虽然登录达到了目的,也就是成功到达了欢迎页。但是,这里存在一个问题,不知道你是否注意到:欢迎页的右边用户名为空,也就是从login到index进行跳转的过程中,我们怎么将用户登录信息进行携带过来呢?

    你可能会想到,在login的时候,把登录的user变量设置成global,这样不就在任何地方可以使用了么?这样确实可以生效,但是对于客户端,也就是浏览器端没法区分当前登录的是哪个用户。比如是购物网站,当一个用户选了一些商品进入购物车,然后可能因为不小心把网址给关了,当他再次进入网站的时候,发现之前选择的商品全没了,此时他可能就没心思再去选商品,因为还需要花费时间,这样客户就流失了。

    那是否还有更好的方法解决这个问题呢?有,肯定有,而且很早以前人们就想到了这样的情景,且已经很好的解决了。答案就是session。当然cookie也一样,因为cookie属于session的一个变种,不同的是,session的内容是存在服务器端,而cookie的内容存在客户端,用户的浏览器或本地文件内。

    具体cookie和session的介绍可以看之前的文件:十二、Django用户认证之cookie和session

     

    二、带会话的登录验证

     在实现会话的登录验证之前,我们再次来回顾一下session和cookie。

    1、cookie不属于http协议范围,由于http协议无法保持状态,但实际情况,我们却又需要“保持状态”,因此cookie就是在这样一个场景下诞生。

    cookie的工作原理是:由服务器产生内容,浏览器收到请求后保存在本地;当浏览器再次访问时,浏览器会自动带上cookie,这样服务器就能通过cookie的内容来判断这个是“谁”了。

    2、cookie虽然在一定程度上解决了“保持状态”的需求,但是由于cookie本身最大支持4096字节,以及cookie本身保存在客户端,可能被拦截或窃取,因此就需要有一种新的东西,它能支持更多的字节,并且他保存在服务器,有较高的安全性。这就是session。

    问题来了,基于http协议的无状态特征,服务器根本就不知道访问者是“谁”。那么上述的cookie就起到桥接的作用。

    我们可以给每个客户端的cookie分配一个唯一的id,这样用户在访问时,通过cookie,服务器就知道来的人是“谁”。然后我们再根据不同的cookie的id,在服务器上保存一段时间的私密资料,如“账号密码”等等。

    3、总结而言:cookie弥补了http无状态的不足,让服务器知道来的人是“谁”;但是cookie以文本的形式保存在本地,自身安全性较差;所以我们就通过cookie识别不同的用户,对应的在session里保存私密的信息以及超过4096字节的文本。

    4、另外,上述所说的cookie和session其实是共通性的东西,不限于语言和框架

     

    上面我们已经自己写了一个登陆页面,在验证了用户名和密码的正确性后跳转到后台的页面。但是测试后也发现,如果绕过登陆页面。直接输入后台的url地址也可以直接访问的。这个显然是不合理的。其实我们缺失的就是cookie和session配合的验证。有了这个验证过程,我们就可以实现和其他网站一样必须登录才能进入后台页面了。

          先说一下这种认证的机制。每当我们使用一款浏览器访问一个登陆页面的时候,一旦我们通过了认证。服务器端就会发送一组随机唯一的字符串(假设是123abc)到浏览器端,这个被存储在浏览端的东西就叫cookie。而服务器端也会自己存储一下用户当前的状态,比如login=true,username=hahaha之类的用户信息。但是这种存储是以字典形式存储的,字典的唯一key就是刚才发给用户的唯一的cookie值。那么如果在服务器端查看session信息的话,理论上就会看到如下样子的字典

    {'123abc':{'login':true,'username:hahaha'}}

    因为每个cookie都是唯一的,所以我们在电脑上换个浏览器再登陆同一个网站也需要再次验证。那么为什么说我们只是理论上看到这样子的字典呢?因为处于安全性的考虑,其实对于上面那个大字典不光key值123abc是被加密的,value值{'login':true,'username:hahaha'}在服务器端也是一样被加密的。所以我们服务器上就算打开session信息看到的也是类似与以下样子的东西

    {'123abc':dasdasdasd1231231da1231231}

    借用一张别的大神画的图,可以更直观的看出来cookie和session的关系

     

    视图函数中的login:

    def login(request):
        message = ""
        if request.method == "POST":
            user = request.POST.get('user')
            pwd = request.POST.get('pwd')
    
            c = models.Administrator.objects.filter(username=user, password=pwd).count()
            if c:
                request.session['is_login'] = True
                request.session['username'] = user
                rep = redirect('/index.html')
                return rep
            else:
                message = "用户名或密码错误"
        obj = render(request,'login.html', {'msg': message})
        return obj
    

     

    登录成功后,会跳转到欢迎页,index.html,但是这里会有一个问题,我们每个页面都需要用户是经过验证的,不然,没经过验证的用户是可以随意跳转页面的,这肯定是不安全的。因为一个网站里面肯定有很多页面,如果不设定每个页面都需要登录,那么用户可能会跳转到某些管理页面对数据进行随意串改,所以这里必须在每个页面都设定用户必须的登录的状态。

    用户认证成功后,跳转到欢迎页。初步实现如下:

    def index(request):
        current_user = request.session.get('username')
        is_login = request.session.get('is_login')
        if is_login:
                return render(request, 'index.html',{'username': current_user})
            else:
                return redirect('/login.html')
        
    

      如前面所说,每个页面都需要经过验证才能登陆,那么我们每个页面都需要上面这一段代码。而且,更重要的是,我们登陆成功后可能还需要添加其他功能,不止是验证登陆,所以每个页面都需要增加相同的代码。这样,是不是非常麻烦且繁琐?

    其实,我们可以有更优雅的方法解决这个问题,答案就是:装饰器!我们把需要的功能封装成一个装饰器,然后对需要验证的函数进行装饰,同时,你又可以新增任何功能,需要修改的地方非常少,只是修改装饰器!

    实现如下:

    def auth(func):
        def inner(request, *args, **kwargs):
            is_login = request.session.get('is_login')
            if is_login:
                return func(request, *args, **kwargs)
            else:
                return redirect('/login.html')
        return inner
    
    @auth
    def index(request):
        current_user = request.session.get('username')
        return render(request, 'index.html',{'username': current_user})
    

      以上,完美的解决了每个页面需要认证的问题,且你可以随意添加任何功能,被修饰的函数不用动任何代码。

    最后,当用户登出时,我们必须要session清除:

    def logout(request):
        request.session.clear()
        return redirect('/login.html')
    

      至此,带会话的验证登陆已完成。

    上面功能虽然实现了,但是还有缺陷:服务端session的内容仅保存在内存中,用户登出后,session随即消失,而用户再次登入时,服务器又需要重新生成。我们是否考虑让session持久化?保存在缓存或者数据库中,因为session可以设定过期时间expire_time,且Django也有特别的支持。

    django有五种session存储方式:

    1、数据库(database-backed sessions)

    2、缓存(cached sessions)

    3、文件系统(file-based sessions)

    4、缓存+数据库Session

    5、cookie(cookie-based sessions)

     

    1、数据库为缓存

    setttings.py设置如下:

    SESSION_ENGINE = 'django.contrib.sessions.backends.db'  # 引擎(默认)
    
    SESSION_COOKIE_NAME = "sessionid"  # Session的cookie保存在浏览器上时的key,即:sessionid=随机字符串(默认)
    SESSION_COOKIE_PATH = "/"  # Session的cookie保存的路径(默认)
    SESSION_COOKIE_DOMAIN = None  # Session的cookie保存的域名(默认)
    SESSION_COOKIE_SECURE = False  # 是否Https传输cookie(默认)
    SESSION_COOKIE_HTTPONLY = True  # 是否Session的cookie只支持http传输(默认)
    SESSION_COOKIE_AGE = 1209600  # Session的cookie失效日期(2周)(默认)
    SESSION_EXPIRE_AT_BROWSER_CLOSE = False  # 是否关闭浏览器使得Session过期(默认)
    SESSION_SAVE_EVERY_REQUEST = False  # 是否每次请求都保存Session,默认修改之后才保存(默认)
    

      

    程序启动之后,你会发现Django自动生成了一张表,对session进行了保存:

    服务器端:

     客户端浏览器:

     

    session实现过程如下:

     

    2、缓存Session

    setttings.py设置如下:

     SESSION_ENGINE = 'django.contrib.sessions.backends.cache'  # 引擎
        SESSION_CACHE_ALIAS = 'default'                            # 使用的缓存别名(默认内存缓存,也可以是memcache),此处别名依赖缓存的设置
    
        SESSION_COOKIE_NAME = "sessionid"                        # Session的cookie保存在浏览器上时的key,即:sessionid=随机字符串
        SESSION_COOKIE_PATH = "/"                                # Session的cookie保存的路径
        SESSION_COOKIE_DOMAIN = None                              # Session的cookie保存的域名
        SESSION_COOKIE_SECURE = False                             # 是否Https传输cookie
        SESSION_COOKIE_HTTPONLY = True                            # 是否Session的cookie只支持http传输
        SESSION_COOKIE_AGE = 1209600                              # Session的cookie失效日期(2周)
        SESSION_EXPIRE_AT_BROWSER_CLOSE = False                   # 是否关闭浏览器使得Session过期
        SESSION_SAVE_EVERY_REQUEST = False                        # 是否每次请求都保存Session,默认修改之后才保存
    

      

    3、文件Session

    setttings.py设置如下:

     SESSION_ENGINE = 'django.contrib.sessions.backends.file'    # 引擎
        SESSION_FILE_PATH = None                                    # 缓存文件路径,如果为None,则使用tempfile模块获取一个临时地址tempfile.gettempdir()                                                            # 如:/var/folders/d3/j9tj0gz93dg06bmwxmhh6_xm0000gn/T
    
    
        SESSION_COOKIE_NAME = "sessionid"                          # Session的cookie保存在浏览器上时的key,即:sessionid=随机字符串
        SESSION_COOKIE_PATH = "/"                                  # Session的cookie保存的路径
        SESSION_COOKIE_DOMAIN = None                                # Session的cookie保存的域名
        SESSION_COOKIE_SECURE = False                               # 是否Https传输cookie
        SESSION_COOKIE_HTTPONLY = True                              # 是否Session的cookie只支持http传输
        SESSION_COOKIE_AGE = 1209600                                # Session的cookie失效日期(2周)
        SESSION_EXPIRE_AT_BROWSER_CLOSE = False                     # 是否关闭浏览器使得Session过期
        SESSION_SAVE_EVERY_REQUEST = False                          # 是否每次请求都保存Session,默认修改之后才保存
    

      

    4、缓存+数据库Session

    setttings.py设置如下:

    SESSION_ENGINE = 'django.contrib.sessions.backends.cached_db'        # 引擎
    

      

    5、加密cookie Session

    setttings.py设置如下:

    SESSION_ENGINE = 'django.contrib.sessions.backends.signed_cookies'   # 引擎
    

     

    三、FBV和CBV

    FBV(Function Base View)-基于函数的视图

    上面通过函数作为视图函数的方式就叫FBV,下面通过CBV方式实现。

     

    CBV(Class Base View) -基于类的视图

    实现方式1,基于Django自带的method_decorator方法:

    缺点:如果类里面的方法需要统一做某些操作,则需要自己写一个outer装饰器,然后类里面的每个方法都需要使用@method_decorator(outer)来装饰。

    views.py文件:

    from django import views
    from django.utils.decorators import method_decorator
    
    def outer(func):
        def inner(request, *args, **kwargs):
            print(request.method)
            return func(request, *args, **kwargs)
        return inner
    
    # CBV
    class Login(views.View):
    
        @method_decorator(outer)
        def get(self,request, *args, **kwargs):
            print('GET')
            return render(request, 'login.html', {'msg': ''})
    
        @method_decorator(outer)
        def post(self, request, *args, **kwargs):
            print('POST')
            user = request.POST.get('user')
            pwd = request.POST.get('pwd')
            c = models.Administrator.objects.filter(username=user, password=pwd).count()
            if c:
                request.session['is_login'] = True
                request.session['username'] = user
                rep = redirect('/index.html')
                return rep
            else:
                message = "用户名或密码错误"
                return render(request, 'login.html', {'msg': message}) 
    

      

     

    方法2:基于Django的dispatch方法,dispatch会在执行class中的每个方法之前做一些操作。

     因为dispatch方法会对类里面的每个方法都应用,如果类里面的每个方法都需要不同的装饰,则单独装饰类里面的每个方法。

    from django import views
    
    class Login(views.View):
    
        def dispatch(self, request, *args, **kwargs):
            #如果我们希望GET方法不进行装饰,则判断请求是GET即返回。
            #如果类里面的其他方法需要装饰,也可以在这里写。
            #比如 if request.method == 'POST':******
            if  request.method == 'GET':
                return HttpResponse(request,'login.html')
            ret = super(Login, self).dispatch(request, *args, **kwargs)
            return ret
    
        def get(self,request, *args, **kwargs):
            print('GET')
            return render(request, 'login.html', {'msg': ''})
    
        def post(self, request, *args, **kwargs):
            print('POST')
            user = request.POST.get('user')
            pwd = request.POST.get('pwd')
            c = models.Administrator.objects.filter(username=user, password=pwd).count()
            if c:
                request.session['is_login'] = True
                request.session['username'] = user
                rep = redirect('/index.html')
                return rep
            else:
                message = "用户名或密码错误"
                return render(request, 'login.html', {'msg': message})
    

      

      

    方法3:基于Django的dispatch和method_decorator联合装饰class

    同时,我们可以通过method_decorator在类上面装饰,或者针对类里面的某个函数装饰。

    from django import views
    from django.utils.decorators import method_decorator
    
    def outer(func):
        def inner(request, *args, **kwargs):
            print(request.method)
            return func(request, *args, **kwargs)
        return inner
    
    # CBV
    #方式二,指定装饰的方法名为dispatch
     @method_decorator(outer,name='dispatch')
    class Login(views.View):
        
        #方式一,直接在方法上进行装饰
        @method_decorator(outer)
        def dispatch(self, request, *args, **kwargs):
            ret = super(Login, self).dispatch(request, *args, **kwargs)
            return ret
    
        def get(self,request, *args, **kwargs):
            print('GET')
            return render(request, 'login.html', {'msg': ''})
    
       
        def post(self, request, *args, **kwargs):
            print('POST')
            user = request.POST.get('user')
            pwd = request.POST.get('pwd')
            c = models.Administrator.objects.filter(username=user, password=pwd).count()
            if c:
                request.session['is_login'] = True
                request.session['username'] = user
                rep = redirect('/index.html')
                return rep
            else:
                message = "用户名或密码错误"
                return render(request, 'login.html', {'msg': message}) 
    

      

      



    urls.py文件:

    urlpatterns = [
       
        url(r'^login.html$', views.Login.as_view()),
    ]
    

      

    查看views源码:

    class View(object):
        """
        Intentionally simple parent class for all views. Only implements
        dispatch-by-method and simple sanity checking.
        """
    
        http_method_names = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace']
    
        def __init__(self, **kwargs):
            """
            Constructor. Called in the URLconf; can contain helpful extra
            keyword arguments, and other things.
            """
            # Go through keyword arguments, and either save their values to our
            # instance, or raise an error.
            for key, value in six.iteritems(kwargs):
                setattr(self, key, value)
    
        @classonlymethod
        def as_view(cls, **initkwargs):
            """
            Main entry point for a request-response process.
            """
            for key in initkwargs:
                if key in cls.http_method_names:
                    raise TypeError("You tried to pass in the %s method name as a "
                                    "keyword argument to %s(). Don't do that."
                                    % (key, cls.__name__))
                if not hasattr(cls, key):
                    raise TypeError("%s() received an invalid keyword %r. as_view "
                                    "only accepts arguments that are already "
                                    "attributes of the class." % (cls.__name__, key))
    
            def view(request, *args, **kwargs):
                self = cls(**initkwargs)
                if hasattr(self, 'get') and not hasattr(self, 'head'):
                    self.head = self.get
                self.request = request
                self.args = args
                self.kwargs = kwargs
                return self.dispatch(request, *args, **kwargs)
            view.view_class = cls
            view.view_initkwargs = initkwargs
    
            # take name and docstring from class
            update_wrapper(view, cls, updated=())
    
            # and possible attributes set by decorators
            # like csrf_exempt from dispatch
            update_wrapper(view, cls.dispatch, assigned=())
            return view
    
        def dispatch(self, request, *args, **kwargs):
            # Try to dispatch to the right method; if a method doesn't exist,
            # defer to the error handler. Also defer to the error handler if the
            # request method isn't on the approved list.
            if request.method.lower() in self.http_method_names:
                handler = getattr(self, request.method.lower(), self.http_method_not_allowed)
            else:
                handler = self.http_method_not_allowed
            return handler(request, *args, **kwargs)
    
        def http_method_not_allowed(self, request, *args, **kwargs):
            logger.warning(
                'Method Not Allowed (%s): %s', request.method, request.path,
                extra={'status_code': 405, 'request': request}
            )
            return http.HttpResponseNotAllowed(self._allowed_methods())
    
        def options(self, request, *args, **kwargs):
            """
            Handles responding to requests for the OPTIONS HTTP verb.
            """
            response = http.HttpResponse()
            response['Allow'] = ', '.join(self._allowed_methods())
            response['Content-Length'] = '0'
            return response
    
        def _allowed_methods(self):
            return [m.upper() for m in self.http_method_names if hasattr(self, m)]
    

      

    对于视图函数,重点查看dispatch函数:

    def dispatch(self, request, *args, **kwargs):
            # Try to dispatch to the right method; if a method doesn't exist,
            # defer to the error handler. Also defer to the error handler if the
            # request method isn't on the approved list.
            if request.method.lower() in self.http_method_names:
                handler = getattr(self, request.method.lower(), self.http_method_not_allowed)
            else:
                handler = self.http_method_not_allowed
            return handler(request, *args, **kwargs)
    

     

    对于urls处理,重点查看as_view函数:
     @classonlymethod
        def as_view(cls, **initkwargs):
            """
            Main entry point for a request-response process.
            """
            for key in initkwargs:
                if key in cls.http_method_names:
                    raise TypeError("You tried to pass in the %s method name as a "
                                    "keyword argument to %s(). Don't do that."
                                    % (key, cls.__name__))
                if not hasattr(cls, key):
                    raise TypeError("%s() received an invalid keyword %r. as_view "
                                    "only accepts arguments that are already "
                                    "attributes of the class." % (cls.__name__, key))
    
            def view(request, *args, **kwargs):
                self = cls(**initkwargs)
                if hasattr(self, 'get') and not hasattr(self, 'head'):
                    self.head = self.get
                self.request = request
                self.args = args
                self.kwargs = kwargs
                return self.dispatch(request, *args, **kwargs)
            view.view_class = cls
            view.view_initkwargs = initkwargs
    
            # take name and docstring from class
            update_wrapper(view, cls, updated=())
    
            # and possible attributes set by decorators
            # like csrf_exempt from dispatch
            update_wrapper(view, cls.dispatch, assigned=())
            return view
     
    

      

    四、总结

    1、如果我们需要追踪用户信息或者保持用户状态,则需要考虑使用session或者cookie。

      session:数据保存在服务器端,基于cookie实现,在浏览器生成一个sessionid。

      cookie:数据保存在用户内存或文件中,一般是不敏感信息,如:用户名。

    2、登录装饰器

      使用method_decorator模块,指定方法或者类进行装饰;

      使用dispatch方法,对所有请求进行装饰,也可以去掉某些方法的装饰;

  • 相关阅读:
    一个让echarts中国地图包含省市轮廓的技巧
    安装MySql for Visual Studio的坑
    EasyUI文档学习心得
    《Node.js+MongoDB+AngularJS Web开发》读书笔记及联想
    U3D自定义Inspector项未触发保存事件的解决方案
    ANT自动打包U3D安卓项目研究笔记
    HipChat上传文件报未知错误解决方案
    Unity3D读取模型文件自动生成AnimatorController简单实例
    较友好的Web文件下载用户体验实例
    Cocos2dx 3.x包含ext库报错解决
  • 原文地址:https://www.cnblogs.com/skyflask/p/9539198.html
Copyright © 2011-2022 走看看