众所周知,一个网站涉及到数据的增删改查,因此对于访问网站的用户必须是通过合法验证的,这样才能对其授予权限进行操做。同时,也能够方便对登陆的用户行为进行审计。css
通常的登陆验证过程:输入用户名和密码,判断用户输入信息是否正确,若是正确,则登陆成功,进入主页;若是错误,则提示用户从新输入。html
实现过程以下:数据库
urls.py文件:django
urlpatterns = [
url(r'^login.html$', views.login),
url(r'^index.html$', views.index),
]
views.py文件,其中index.html为登陆成功后的页面,login.html为登陆页面。flask
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{
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>
体验过程以下:cookie
虽然登陆达到了目的,也就是成功到达了欢迎页。可是,这里存在一个问题,不知道你是否注意到:欢迎页的右边用户名为空,也就是从login到index进行跳转的过程当中,咱们怎么将用户登陆信息进行携带过来呢?
你可能会想到,在login的时候,把登陆的user变量设置成global,这样不就在任何地方可使用了么?这样确实能够生效,可是对于客户端,也就是浏览器端无法区分当前登陆的是哪一个用户。好比是购物网站,当一个用户选了一些商品进入购物车,而后可能由于不当心把网址给关了,当他再次进入网站的时候,发现以前选择的商品全没了,此时他可能就没心思再去选商品,由于还须要花费时间,这样客户就流失了。
那是否还有更好的方法解决这个问题呢?有,确定有,并且很早之前人们就想到了这样的情景,且已经很好的解决了。答案就是session。固然cookie也同样,由于cookie属于session的一个变种,不一样的是,session的内容是存在服务器端,而cookie的内容存在客户端,用户的浏览器或本地文件内。
具体cookie和session的介绍能够看以前的文件:12、Django用户认证之cookie和session
在实现会话的登陆验证以前,咱们再次来回顾一下session和cookie。
一、cookie不属于http协议范围,因为http协议没法保持状态,但实际状况,咱们却又须要“保持状态”,所以cookie就是在这样一个场景下诞生。
cookie的工做原理是:由服务器产生内容,浏览器收到请求后保存在本地;当浏览器再次访问时,浏览器会自动带上cookie,这样服务器就能经过cookie的内容来判断这个是“谁”了。
二、cookie虽然在必定程度上解决了“保持状态”的需求,可是因为cookie自己最大支持4096字节,以及cookie自己保存在客户端,可能被拦截或窃取,所以就须要有一种新的东西,它能支持更多的字节,而且他保存在服务器,有较高的安全性。这就是session。
问题来了,基于http协议的无状态特征,服务器根本就不知道访问者是“谁”。那么上述的cookie就起到桥接的做用。
咱们能够给每一个客户端的cookie分配一个惟一的id,这样用户在访问时,经过cookie,服务器就知道来的人是“谁”。而后咱们再根据不一样的cookie的id,在服务器上保存一段时间的私密资料,如“帐号密码”等等。
三、总结而言:cookie弥补了http无状态的不足,让服务器知道来的人是“谁”;可是cookie以文本的形式保存在本地,自身安全性较差;因此咱们就经过cookie识别不一样的用户,对应的在session里保存私密的信息以及超过4096字节的文本。
四、另外,上述所说的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存储方式:
一、数据库(database-backed sessions)
二、缓存(cached sessions)
三、文件系统(file-based sessions)
四、缓存+数据库Session
五、cookie(cookie-based sessions)
一、数据库为缓存
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实现过程以下:
二、缓存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,默认修改以后才保存
三、文件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,默认修改以后才保存
四、缓存+数据库Session
setttings.py设置以下:
SESSION_ENGINE = 'django.contrib.sessions.backends.cached_db' # 引擎
五、加密cookie Session
setttings.py设置以下:
SESSION_ENGINE = 'django.contrib.sessions.backends.signed_cookies' # 引擎
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
一、若是咱们须要追踪用户信息或者保持用户状态,则须要考虑使用session或者cookie。
session:数据保存在服务器端,基于cookie实现,在浏览器生成一个sessionid。
cookie:数据保存在用户内存或文件中,通常是不敏感信息,如:用户名。
二、登陆装饰器
使用method_decorator模块,指定方法或者类进行装饰;
使用dispatch方法,对全部请求进行装饰,也能够去掉某些方法的装饰;