学生管理之登录实现
作者:@skyflask
转载本文请注明出处:https://www.cnblogs.com/skyflask/p/9539198.html
目录
一、普通登录验证
二、带会话的登录验证
三、FBV和CBV
四、总结
一、普通登录验证
众所周知,一个网站涉及到数据的增删改查,所以对于访问网站的用户必须是经过合法验证的,这样才能对其授予权限进行操作。同时,也可以方便对登录的用户行为进行审计。
一般的登录验证过程:输入用户名和密码,判断用户输入信息是否正确,如果正确,则登录成功,进入主页;如果错误,则提示用户重新输入。
实现过程如下:
urls.py文件:
1 2 3 4 | urlpatterns = [ url(r'^login.html$', views.login), url(r'^index.html$', views.index), ] |
views.py文件,其中index.html为登录成功后的页面,login.html为登录页面。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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视图函数如下:
1 2 | def index(request): return render(request,'index.html') |
如果验证成功,则跳转到欢迎页,index.html。
index.html网页内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | {% extends "layout.html" %} {% block css %} {% endblock %} {% block content %} < h1 >欢迎学生管理中心</ h1 > {% endblock %} {% block js %} {% endblock %} |
login.html文件:提交url为login.html,提交方法为POST。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | <! DOCTYPE html> < html lang="en"> < head > < meta charset="UTF-8"> < title >Title</ title > < style > label{ width: 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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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,但是这里会有一个问题,我们每个页面都需要用户是经过验证的,不然,没经过验证的用户是可以随意跳转页面的,这肯定是不安全的。因为一个网站里面肯定有很多页面,如果不设定每个页面都需要登录,那么用户可能会跳转到某些管理页面对数据进行随意串改,所以这里必须在每个页面都设定用户必须的登录的状态。
用户认证成功后,跳转到欢迎页。初步实现如下:
1 2 3 4 5 6 7 8 | 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' ) |
如前面所说,每个页面都需要经过验证才能登陆,那么我们每个页面都需要上面这一段代码。而且,更重要的是,我们登陆成功后可能还需要添加其他功能,不止是验证登陆,所以每个页面都需要增加相同的代码。这样,是不是非常麻烦且繁琐?
其实,我们可以有更优雅的方法解决这个问题,答案就是:装饰器!我们把需要的功能封装成一个装饰器,然后对需要验证的函数进行装饰,同时,你又可以新增任何功能,需要修改的地方非常少,只是修改装饰器!
实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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清除:
1 2 3 | 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设置如下:
1 2 3 4 5 6 7 8 9 10 | 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设置如下:
1 2 3 4 5 6 7 8 9 10 11 | 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设置如下:
1 2 3 4 5 6 7 8 9 10 11 12 | 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设置如下:
1 | SESSION_ENGINE = 'django.contrib.sessions.backends.cached_db' # 引擎 |
5、加密cookie Session
setttings.py设置如下:
1 | 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文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | 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方法会对类里面的每个方法都应用,如果类里面的每个方法都需要不同的装饰,则单独装饰类里面的每个方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | 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在类上面装饰,或者针对类里面的某个函数装饰。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | 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文件:
1 2 3 4 | urlpatterns = [ url(r '^login.html$' , views.Login.as_view()), ] |
查看views源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 | 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函数:
1 2 3 4 5 6 7 8 9 | 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) |
1 | 对于urls处理,重点查看as_view函数: |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | @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方法,对所有请求进行装饰,也可以去掉某些方法的装饰;
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 上周热点回顾(3.3-3.9)
· AI 智能体引爆开源社区「GitHub 热点速览」