三种返回响应的方式:html
return HttpResponse() return render() return redirect()
HttpResponse:python
除了可以返回字符串,还能返回bytes。jquery
content = "Hello" return HttpResponse(content) return HttpResponse(bytes(content))
render:ajax
返回渲染后的模板。redis
return render(request, 'mlogin.html', {"pwd_error": pwd_error_msg})
redirect:数据库
跳转到url。django
return redirect('/mgmt/host')
三种方式均可以设置cookie:后端
response = redirect(...) # 或HttpResponse、render response.set_cookie('key', 'value')
set_cookie是将cookie放在响应头中的。浏览器
因为Cookie存放在客户端浏览器,因此Cookie是不安全的,能够被人查看和修改,甚至伪造。缓存
Cookie不适合存放敏感信息,例如密码等。
因此,咱们能够将本来存放在Cookie中的数据,放置到服务器端,例如:
{ 'username':'Alex', 'id':'12973', 'favor':'xxxxxx' }
而后,服务器在保存这些数据以前,生成一个随机字符串。并将这个字符串使用set_cookie放置在客户端。并在服务器也保存一份,做为数据的key,例如:
session = { # 某用户的session 'h9j2j3987d2bksjf98': { 'username': 'Alex', 'id': '12973', 'is_login':True, 'favor': 'xxxxxx' }, # 另外一个用户的session 'h978hnjdfi9100':{ 'username': 'Leo', 'id': '12989', 'is_login':True, 'favor': 'yyyyyy' } }
客户端浏览器保存的cookie为:
sessionid = 'h9j2j3987d2bksjf98'
当用户请求时,携带该sessionid。服务器拿到这个随机ID后,会到session中查到对应的数据(例如is_login),若是is_login为True,则说明用户登陆过。
优势:一样完成了用户验证,并且数据存放在服务端,安全性更高。
缺点:增长了服务器开销。
python manage.py makemigrations
python manage.py migrate
Django的Session默认是放在数据库的django_session表中的。
def login(request): # 若是是get请求,则检查session中is_login是否为True,若是有则免登陆,没有则跳转到登陆界面 if request.method == 'GET': if request.session.get('is_login'): return redirect('/index') else: return render(request, 'login.html') # 若是是post请求,则获取请求数据,并验证用户名密码。 if request.method == 'POST': user = request.POST.get('user') pwd = request.POST.get('pwd') # 若是验证经过,则写session,写入username和is_login,并跳转到业务页面index if user == USER_DICT.get('username') and pwd == USER_DICT.get('password'): request.session['username'] = user request.session['is_login'] = True return redirect('/index') else: # 没验证经过,则跳转回login页面 return redirect('/login')
1)当咱们使用request.session['is_login']=True设置session时,django会自动生成随机字符串(set_cookie以及本身保留一份存数据库)
2)存放session的键值对到数据库中(如今不少可能使用的是NOSQL数据库,例如redis之类,而且session要提供共享功能,方便网站分布式部署)
3)当浏览器带着sessionid请求时(以下图),视图函数中使用request.session['is_login']获取值时,django会使用sessionid,去数据库中查询对应条目的is_login(以下图)
不少网站不必定使用Django框架,因此在请求头中不必定能看到sessionid,但通常都有一个对应的ID来表示用户:
这是博客园存放的已登陆用户的随机字符串,相似于sessionid。后台拿到这个id后,能够从数据库中获取用户信息。
Django为session提供了不少操做。
# 相似字典操做session request.session['k1'] = 123 # 设置k1的值 k1 = request.session['k1'] # 取k1的值,若是k1不存在,则报错 k1 = request.session.get('k1', None) # 取k1的值,不存在则返回None del request.session['k1'] # 删除k1及对应的值
request.session.keys() # 获取全部key列表 request.session.values() # 获取全部value request.session.items() # 获取全部键值对 request.session.iterkeys() # 返回全部key迭代器 request.session.itervalues() # 返回全部value迭代器 request.session.iteritems() # 返回全部item迭代器
# 清空数据库中过时的session request.session.clear_expired() # 获取用户对应随机字符串(cookie中的sessionid) sk = request.session.session_key # 判断随机字符串在数据库是否存在(通常用不到,由于在获取session某个值得时候底层也会进行这个操做) request.session.exists(sk) # 删除当前用户的全部Session数据 request.session.delete(sk) # 或 request.session.clear() # 通常在用户注销时使用
为session设置过时时间:
request.session.set_expiry(10) #单位是秒
当这样设置之后,全部session的过时时间都是10秒。若是不设置的话,Session的过时时间默认是两周。
设置浏览器关闭时过时:
request.session.set_expiry(0) #设置为0时,浏览器关闭过时
在这种状况下,浏览器的sessionid cookie没有设置过时时间,因此关闭浏览器,Cookie就消失了, 天然没法再利用session来认证。可是在后台数据库中还存在sessionid的记录。
先查看一下Django对Session的默认配置:
from django.conf import settings print(settings.SESSION_COOKIE_NAME) # 'sessionid' print(settings.SESSION_COOKIE_PATH) #'/' print(settings.SESSION_COOKIE_DOMAIN) # None print(settings.SESSION_COOKIE_SECURE) # False print(settings.SESSION_COOKIE_HTTPONLY) # True print(settings.SESSION_COOKIE_AGE) #1209600 print(settings.SESSION_EXPIRE_AT_BROWSER_CLOSE) # False print(settings.SESSION_SAVE_EVERY_REQUEST) # False
以上打印的值即为默认值。
要修改session的默认配置,只需在settings.py配置文件中修改:
SESSION_COOKIE_NAME = "sessionid" # session产生的随机字符串保存到浏览器cookie时的名字,默认就是sessionid SESSION_COOKIE_PATH = "/" # Session的cookie生效路径 SESSION_COOKIE_DOMAIN = None # Session的cookie生效域名 SESSION_COOKIE_SECURE = False # 是否Https传输cookie SESSION_COOKIE_HTTPONLY = True # 是否只支持http传输 SESSION_COOKIE_AGE = 1209600 # 默认两周失效 SESSION_EXPIRE_AT_BROWSER_CLOSE = False # 是否关闭浏览器使得Session过时 SESSION_SAVE_EVERY_REQUEST = False # 是否每次请求都保存Session,即失效时间根据每次请求向后推
以上配置直接写在setting中便可。
数据库后端:(默认)
# settings.py
SESSION_ENGINE = 'django.contrib.sessions.backends.db' # 引擎(默认)
缓存后端:
SESSION_ENGINE = 'django.contrib.sessions.backends.cache' SESSION_CACHE_ALIAS = 'default' CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache', 'LOCATION': [ '192.168.1.3:11211', '192.168.1.4:11211', ] }, }
文件后端:
SESSION_ENGINE = 'django.contrib.sessions.backends.file' # 引擎 #SESSION_FILE_PATH = None # 缓存文件路径,若是为None,则使用tempfile模块获取一个临时地址tempfile.gettempdir()SESSION_FILE_PATH = os.path.join(BASE_DIR,'cache') # 在工程目录下建立一个cache目录来存放session
缓存+数据库做为后端:(推荐)
数据库作持久化,缓存提升效率。前提是缓存和数据库都要配置好,参照前面分别的配置。
SESSION_ENGINE = 'django.contrib.sessions.backends.cached_db'
加密cookie session:(不推荐)
将session的数据所有加密后放在cookie中,这种方式不能算一种后端存储技术,实际上都是cookie了。
SESSION_ENGINE = 'django.contrib.sessions.backends.signed_cookies'
总结:以上几种Session配置只是使用的引擎不一样,使用方法都是同样的。前面的默认配置就是通用配置。
Redis做为Session存储(推荐):
第一种配置方法:
首先安装django-redis-sessions:
pip install django-redis-sessions
# settings中配置 SESSION_ENGINE = 'redis_sessions.session' SESSION_REDIS = { 'host': '192.168.1.181', 'port': 6379, 'db': 2, # 哪一个数据库 'password': '', 'prefix': 'session', # key的前缀 'socket_timeout': 10 }
能够看到在redis中,session存储为如下形式:
127.0.0.1:6379[2]> keys * 1) "session:fnifus1tqkbilhr1k549mcn5q9k5utdv"
形式为,prefix:session_id。
第二种配置方法:
首先安装django_redis:
pip install django_redis
# 在settings中配置 CACHES = { "default": { "BACKEND": "django_redis.cache.RedisCache", # 把这里缓存你的redis服务器ip和port "LOCATION": "redis://192.168.1.181:6379/3", "OPTIONS": { "CLIENT_CLASS": "django_redis.client.DefaultClient", } } } # 咱们定义一个cache(本地缓存来存储信息,cahe指定的是redis SESSION_ENGINE = "django.contrib.sessions.backends.cache" # 指定本地的session使用的本地缓存名称是'default' SESSION_CACHE_ALIAS = "default"
这种配置方式下,session存储为如下形式:
127.0.0.1:6379[3]> keys * 1) ":1:django.contrib.sessions.cachefj33bv40gk7gf2srklfi2fminmohnskb"
在前面的章节,咱们注释掉了CSRF中间件,由于发送post请求的时候出现403错误。
CSRF是用来防止有人在其余非法站点向咱们的某个页面发送POST请求。
CSRF启用的时候,咱们以GET方式请求一个页面时,Django会生成随机字符串(分别放在render的参数中,以及cookie中,两个字符串是独立的),并传递到页面。
在咱们提交表单时,必须携带该随机字符串,才能正常提交,不然报403forbid错误。
因为咱们提交数据可使用form表单,也可使用Ajax,因此对应两个地方须要获取随机字符串。
在settings.py中启用CSRF中间件:
MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', ]
<body> <form action="/login/" method="post"> {% csrf_token %} <input type="text" name="user"/> <input type="password" name="pwd"/> <input type="submit" value="提交"/> </form> </body>
咱们使用了{%csrf_token%}后,在页面元素中能够看到:
Django为咱们自动添加了一个隐藏的<input>标签,value就是csrf随机字符串。后台能够自动获取这个值并验证。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Login</title> </head> <body> <form action="/login/" method="post"> {% csrf_token %} <input type="text" name="user"/> <input type="password" name="pwd"/> <input type="submit" value="提交"/> <input id='ajax_btn' type="button" value="Ajax提交"/> </form> <script src="/static/jquery-1.12.4.js"></script> <script src="/static/jquery.cookie.js"></script> <script> $(function () { $('#ajax_btn').click(function(){ $.ajax({ url:'/login', type:'POST', data:{'user':'leokale','pwd':123}, headers:{'X-CSRFtoken':$.cookie('csrftoken')}, success:function(arg){ location.reload() } }) }) }) </script> </body> </html>
以上这种使用方法在有不少Ajax提交的时候显得不是很方便,由于要为每个Ajax的请求添加 X-CSRFtoken字段,因此可使用如下方式:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Login</title> </head> <body> <form action="/login/" method="post"> {% csrf_token %} <input type="text" name="user"/> <input type="password" name="pwd"/> <input type="submit" value="提交"/> <input id='ajax_btn' type="button" value="Ajax提交"/> <input id='ajax_btn2' type="button" value="Ajax提交2"/> </form> <script src="/static/jquery-1.12.4.js"></script> <script src="/static/jquery.cookie.js"></script> <script> $(function () { //在这里的配置,对全部该页面的Ajax都生效 $.ajaxSetup({ beforeSend: function(xhr,settings){ xhr.setRequestHeader('X-CSRFtoken',$.cookie('csrftoken')); } }); //在ajaxSetup中配置后,ajax中就不用再设置X-CSRFtoken了 $('#ajax_btn').click(function(){ $.ajax({ url:'/login', type:'POST', data:{'user':'leokale','pwd':123}, success:function(arg){ location.reload() } }) }); $('#ajax_btn2').click(function(){ $.ajax({ url:'/login', type:'POST', data:{'user':'leokale','pwd':123}, success:function(arg){ location.reload() } }) }); }) </script> </body> </html>
在ajaxSetup中能够进行ajax的全局配置,后面的全部ajax操做都不用单独添加csrf值了。
在ajaxSetup中,参数xhr表示xmlHttpRequest,settings参数表示从后面$.ajax函数参数中得到的字典:
因此咱们应该在ajaxSetup中对请求方法进行过滤,GET|HEAD|OPTIONS|TRACE 请求不添加csrf字符串,其他请求类型才添加:
//使用正则判断是不是GET|HEAD|OPTIONS|TRACE function csrfSafeMethod(method){ return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method)); } //在这里的配置,对全部该页面的Ajax都生效 $.ajaxSetup({ beforeSend: function(xhr,settings){ // 若是不是GET|HEAD|OPTIONS|TRACE,就在请求头中添加x-csrftoken if(!csrfSafeMethod(settings.type)){ xhr.setRequestHeader('X-CSRFtoken',$.cookie('csrftoken')); } } });
特别注意,在ajax中为请求头添加xsrf随机字符串,这个字符串是从cookie中得到的。当咱们第一次以GET请求login页面时,Django除了使用render传递了一个csrftoken,还在cookie中放置了一个csrftoken:
对比经过{%csrf_token%}拿到的字符串:
能够发现这两个字符串是不同的,因此这两种方式的CSRF token是分别验证的。
在settings.py中启用CSRF中间件,至关于全部的views.py视图方法或类中都要验证CSRF随机字符串。可是有时候这样是不合适的。
经过装饰器,按照需求来为视图函数添加CSRF验证:
from django.views.decorators.csrf import csrf_exempt, csrf_protect @csrf_protect def login(request): return HttpResponse("login page") @csrf_protect def index(request): return HttpResponse("login page") def host(request): return HttpResponse("login page")
当settings.py中的csrf配置为禁用时(对所有视图函数禁用csrf验证),上述代码表示login()和index()启用csrf验证。
from django.views.decorators.csrf import csrf_exempt, csrf_protect def login(request): return HttpResponse("login page") def index(request): return HttpResponse("login page") @csrf_exempt def host(request): return HttpResponse("login page")
当settings.py中的csrf配置为启用时(对所有视图函数启用csrf验证),上述代码表示host()禁用csrf验证。
当用户发一个请求时,请求到达Django框架后,这个请求要通过一系列的中间件才能到达视图函数。
咱们能够在settings.py中查看通过了哪些中间件:
MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', ]
以上列表中,每一条表明一个类,每一个类都是一个中间件,咱们的请求会从上到下一个一个通过,才能到达视图函数进行处理。
视图函数处理完毕后,使用HttpResponse、render、redirect返回响应时,又要从下到上通过全部的中间件,最终才能返回给浏览器。
如图,每一个中间件都有两个方法,一个处理request,另外一个处理response:
咱们查看Django提供的中间件源码,能够看到如下代码:
class SessionMiddleware(MiddlewareMixin): def __init__(self, get_response=None): #...... def process_request(self, request): #...... def process_response(self, request, response): # ...... return response
中间件类继承于类MiddlewareMixin,一样咱们看如下它的源码:
class MiddlewareMixin: def __init__(self, get_response=None): self.get_response = get_response super().__init__() def __call__(self, request): response = None if hasattr(self, 'process_request'): response = self.process_request(request) response = response or self.get_response(request) if hasattr(self, 'process_response'): response = self.process_response(request, response) return response
咱们仿照SessionMiddleware,就能够实现本身的中间件。
建立mymiddles文件夹,在其中建立mm1.py:
from django.utils.deprecation import MiddlewareMixin # 定义第一个中间件 class MyMiddle1(MiddlewareMixin): def process_request(self, request): print('This is MyMiddle1, process request, Done') def process_response(self, request, response): print('This is MyMiddle1, process response, Done') return response # 定义第二个中间件 class MyMiddle2(MiddlewareMixin): def process_request(self, request): print('This is MyMiddle2, process request, Done') def process_response(self, request, response): print('This is MyMiddle2, process response, Done') return response # 定义第三个中间件 class MyMiddle3(MiddlewareMixin): def process_request(self, request): print('This is MyMiddle3, process request, Done') def process_response(self, request, response): print('This is MyMiddle3, process response, Done') return response
咱们定义了三个中间件。
修改settings.py配置文件,在中间件列表中加入咱们本身的中间件:
MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', 'mymiddles.mm1.MyMiddle1', 'mymiddles.mm1.MyMiddle2', 'mymiddles.mm1.MyMiddle3', ]
咱们任意发一个请求,在后台获得如下打印信息:
This is MyMiddle1, process request, Done This is MyMiddle2, process request, Done This is MyMiddle3, process request, Done This is MyMiddle3, process response, Done This is MyMiddle2, process response, Done This is MyMiddle1, process response, Done [23/Dec/2019 21:42:44] "GET /login HTTP/1.1" 200 2115
咱们能够看到,request按照流程图中所示,前后通过 MyMiddle1 ---> MyMiddle2 ---> MyMiddle3
response前后通过 MyMiddle3 ---> MyMiddle2 ---> MyMiddle1
既然request和response都要通过中间件(特别是request),咱们就能够在中间件中对request进行一些规则过滤、检查等。
当某个中间件验证不经过,则request不能到达下一层中间件,直接返回,例如:
# 定义第二个中间件 class MyMiddle2(MiddlewareMixin): def process_request(self, request): print('This is MyMiddle2, process request, Done') if request.headers.get('Host') == '127.0.0.1:8000': return HttpResponse("走开") def process_response(self, request, response): print('This is MyMiddle2, process response, Done') return response
咱们在第二个中间件中添加一个过滤条件,当请求恰好知足这条时,直接返回HttpResponse。后台能够看到如下打印信息:
[23/Dec/2019 21:51:00] "GET /login HTTP/1.1" 200 6 This is MyMiddle1, process request, Done This is MyMiddle2, process request, Done This is MyMiddle2, process response, Done This is MyMiddle1, process response, Done
咱们发现第二个中间件并无将request交给后面的中间件。
总结:经过中间件,咱们能够对请求和响应进行自定义的过滤。
除了前面所描述的process_request和process_response方法,中间件还有process_view方法。如图:
当request进过了每一层中间件的process_request方法后,会通过urls.py路由系统,找到对应的视图函数。
而后折返到第一层中间件执行process_view方法:
from django.utils.deprecation import MiddlewareMixin # 定义第一个中间件 class MyMiddle1(MiddlewareMixin): def process_request(self, request): print('This is MyMiddle1, process request, Done') def process_response(self, request, response): print('This is MyMiddle1, process response, Done') return response # view_func就是对应的视图函数,view_func_args对应视图函数的*args参数,view_func_kwargs对应**kwargs参数 def process_view(self, request, view_func, view_func_args, view_func_kwargs): print('This is MyMiddle1, process view, Done') # 定义第二个中间件 class MyMiddle2(MiddlewareMixin): def process_request(self, request): print('This is MyMiddle2, process request, Done') def process_response(self, request, response): print('This is MyMiddle2, process response, Done') return response # view_func就是对应的视图函数,view_func_args对应视图函数的*args参数,view_func_kwargs对应**kwargs参数 def process_view(self, request, view_func, view_func_args, view_func_kwargs): print('This is MyMiddle2, process view, Done') # 定义第三个中间件 class MyMiddle3(MiddlewareMixin): def process_request(self, request): print('This is MyMiddle3, process request, Done') def process_response(self, request, response): print('This is MyMiddle3, process response, Done') return response # view_func就是对应的视图函数,view_func_args对应视图函数的*args参数,view_func_kwargs对应**kwargs参数 def process_view(self, request, view_func, view_func_args, view_func_kwargs): print('This is MyMiddle3, process view, Done')
测试结果打印以下:
This is MyMiddle1, process request, Done This is MyMiddle2, process request, Done This is MyMiddle3, process request, Done This is MyMiddle1, process view, Done This is MyMiddle2, process view, Done This is MyMiddle3, process view, Done 执行了login() This is MyMiddle3, process response, Done This is MyMiddle2, process response, Done This is MyMiddle1, process response, Done
能够看到,request先通过process_request,而后折返通过process_view,而后执行视图函数,再经过process_response返回数据。
中间件中还有一个叫process_exception的方法,这个方法主要用来处理视图函数中的异常。处理流程图:
咱们在视图函数中添加一个错误:
def login(request): int('asdf') #......
而后在三个中间件中添加process_exception方法:
def process_exception(self,request,exception): print(exception) # 打印视图函数中出现的异常 return HttpResponse("出现异常")
当视图函数出现异常时,他会将异常交给离他最近的中间件(有process_exception的),例如MyMiddle3。
若是这个中间件对异常进行了处理(如上代码中return HttpResponse('出现异常')),则页面显示“出现异常”。
不然,会继续交给下一个中间件的process_exception方法(例如MyMiddle2),若是全部的中间件都没有处理这个异常,则页面报错。
这个方法主要用来让用户自定义render方法。例如咱们修改视图函数:
class Foo(object): def render(self): return HttpResponse('自定义render') # 视图函数login def login(request): return Foo()
咱们本身定义了一个类Foo,具备成员方法render(必须这个名字)。而后让视图函数返回Foo的一个对象。
而后在中间件中实现process_template_response方法:
# 定义第一个中间件 class MyMiddle1(MiddlewareMixin): def process_request(self, request): print('This is MyMiddle1, process request, Done') def process_response(self, request, response): print('This is MyMiddle1, process response, Done') return response # view_func就是对应的视图函数,view_func_args对应视图函数的*args参数,view_func_kwargs对应**kwargs参数 def process_view(self, request, view_func, view_func_args, view_func_kwargs): print('This is MyMiddle1, process view, Done') def process_template_response(self,request,response): print('This is MyMiddle1, process template response, Done') return response
这样,process_template_response方法就会执行,咱们将Foo对象直接返回,Django会将该对象里render函数的返回值做为数据返回给浏览器。咱们在页面上能够看到:
因此,process_template_response方法,主要就是让咱们自定义模板渲染方法。可是通常没怎么用。