csrf简单用法
什么是CSRF ?
跨站请求伪造,
问题:
-
钓鱼网站的页面和正经网站的页面对浏览器来说有什么区别? (页面是怎么来的?)
钓鱼网站的页面是由 钓鱼网站的服务端给你返回的
正经网站的网页是由 正经网站的服务端给你返回的 -
Django中内置了一个专门处理csrf问题的中间件
django.middleware.csrf.CsrfViewMiddleware
当你提交POST数据的时候,它帮你做校验,如果校验不通过就拒绝这次请求
功能:在render返回页面的时候,在页面中塞了一个隐藏的input标签<input type="hidden" name="csrfmiddlewaretoken" value="8gthvLKulM7pqulNl2q3u46v1oEbKG7BSwg6qsHBv4zf0zj0UcbQmpbAdijqyhfE"> 用法:我们在页面上 form表单 里面写上 {% csrf_token %}
COOKIE
Cookie具体指的是一段小信息,它是服务器发送出来存储在浏览器上的一组组键值对,下次访问服务器时浏览器会自动携带这些键值对,以便服务器提取有用信息。
cookie的工作原理是:由服务器产生内容,浏览器收到请求后保存在本地;当浏览器再次访问时,浏览器会自动带上Cookie,这样服务器就能通过Cookie的内容来判断这个是“谁”了。
获取Cookie
request.COOKIES['key']
request.get_signed_cookie(key, default=RAISE_ERROR, salt='', max_age=None)
参数:
default: 默认值
salt: 加密盐
max_age: 后台控制过期时间
设置Cookie
rep = HttpResponse(...)
rep = render(request, ...)
rep.set_cookie(key,value,...)
rep.set_signed_cookie(key,value,salt='加密盐', max_age=None, ...)
参数:
key, 键
value='', 值
max_age=None, 超时时间
expires=None, 超时时间
path='/', Cookie生效的路径,/ 表示根路径,特殊的:根路径的cookie可以被任何url的页面访问
domain=None, Cookie生效的域名
secure=False, https传输
httponly=False 只能http协议传输,无法被JavaScript获取(不是绝对,底层抓包可以获取到也可以被覆盖)
删除Cookie
def logout(request):
rep = redirect("/login/")
rep.delete_cookie("user") # 删除用户浏览器上之前设置的usercookie值
return rep
登录校验案例
def check_login(func):
@wraps(func)
def inner(request, *args, **kwargs):
next_url = request.get_full_path()
if request.get_signed_cookie("login", salt="SSS", default=None) == "yes":
# 已经登录的用户...
return func(request, *args, **kwargs)
else:
# 没有登录的用户,跳转刚到登录页面
return redirect("/login/?next={}".format(next_url))
return inner
def login(request):
if request.method == "POST":
username = request.POST.get("username")
passwd = request.POST.get("password")
if username == "xxx" and passwd == "dashabi":
next_url = request.GET.get("next")
if next_url and next_url != "/logout/":
response = redirect(next_url)
else:
response = redirect("/class_list/")
response.set_signed_cookie("login", "yes", salt="SSS")
return response
return render(request, "login.html")
@check_login
def home(request):
return render(request, "home.html")
session
我们可以给每个客户端的Cookie分配一个唯一的id,这样用户在访问时,通过Cookie,服务器就知道来的人是“谁”。然后我们再根据不同的Cookie的id,在服务器上保存一段时间的私密资料,如“账号密码”等等。
# 获取、设置、删除Session中数据
request.session['k1']
request.session.get('k1',None)
request.session['k1'] = 123
request.session.setdefault('k1',123) # 存在则不设置
del request.session['k1']
# 所有 键、值、键值对
request.session.keys()
request.session.values()
request.session.items()
request.session.iterkeys()
request.session.itervalues()
request.session.iteritems()
# 会话session的key
request.session.session_key
# 将所有Session失效日期小于当前日期的数据删除
request.session.clear_expired()
# 检查会话session的key在数据库中是否存在
request.session.exists("session_key")
# 删除当前会话的所有Session数据
request.session.delete()
# 删除当前的会话数据并删除会话的Cookie。
request.session.flush()
这用于确保前面的会话数据不可以再次被用户的浏览器访问
例如,django.contrib.auth.logout() 函数中就会调用它。
# 设置会话Session和Cookie的超时时间
request.session.set_expiry(value)
* 如果value是个整数,session会在些秒数后失效。
* 如果value是个datatime或timedelta,session就会在这个时间后失效。
* 如果value是0,用户关闭浏览器session就会失效。
* 如果value是None,session会依赖全局session失效策略。
# session校验案例
def check_login(func):
@wraps(func) # 装饰器修复技术
def inner(request, *args, **kwargs):
ret = request.session.get("is_login")
# 1. 获取cookie中的随机字符串
# 2. 根据随机字符串去数据库取 session_data --> 解密 --> 反序列化成字典
# 3. 在字典里面 根据 is_login 取具体的数据
if ret == "1":
# 已经登陆过的 继续执行
return func(request, *args, **kwargs)
# 没有登录过的 跳转到登录页面
else:
# 获取当前访问的URL
next_url = request.path_info
print(next_url)
return redirect("/app02/login/?next={}".format(next_url))
return inner
def login(request):
if request.method == "POST":
user = request.POST.get("user")
pwd = request.POST.get("pwd")
# 从URL里面取到 next 参数
next_url = request.GET.get("next")
if user == "alex" and pwd == "dsb":
# 登陆成功
# 告诉浏览器保存一个键值对
if next_url:
rep = redirect(next_url) # 得到一个响应对象
else:
rep = redirect("/app02/home/") # 得到一个响应对象
# 设置session
request.session["is_login"] = "1"
request.session["name"] = user
request.session.set_expiry(7) # 7秒钟之后失效
return rep
return render(request, "app02/login.html")
@check_login
def home(request):
user = request.session.get("name")
return render(request, "app02/home.html", {"user": user})
# 注销函数
def logout(request):
# 只删除session数据
# request.session.delete()
# 如何删除session数据和cookie
request.session.flush()
return redirect("/app02/login/")
class怎样使用装饰器
1.加在CBV视图的get或post方法上
from django.utils.decorators import method_decorator
@method_decorator(check_login)
def post(self, request):
print("Home View POST method...")
return redirect("/index/")
2. 加在dispatch方法上,相当于给get和post方法都加上了登录校验。
from django.utils.decorators import method_decorator
@method_decorator(check_login)
def dispatch(self, request, *args, **kwargs):
return super(HomeView, self).dispatch(request, *args, **kwargs)
3. 直接加在视图类上,但method_decorator必须传 name 关键字参数
from django.utils.decorators import method_decorator
@method_decorator(check_login, name="get")
@method_decorator(check_login, name="post")
class HomeView(View):
def dispatch(self, request, *args, **kwargs):
return super(HomeView, self).dispatch(request, *args, **kwargs)
def get(self, request):
return render(request, "home.html")
def post(self, request):
print("Home View POST method...")
return redirect("/index/")
补充
CSRF Token相关装饰器在CBV只能加到dispatch方法上
或加在视图类上然后name参数指定为dispatch方法。
备注:
csrf_protect,为当前函数强制设置防跨站请求伪造功能,即便settings中没有设置全局中间件。
csrf_exempt,取消当前函数防跨站请求伪造功能,即便settings中设置了全局中间件。
方法1
from django.views.decorators.csrf import csrf_exempt, csrf_protect
from django.utils.decorators import method_decorator
class HomeView(View):
@method_decorator(csrf_exempt)
def dispatch(self, request, *args, **kwargs):
...
方法2
from django.views.decorators.csrf import csrf_exempt, csrf_protect
from django.utils.decorators import method_decorator
@method_decorator(csrf_exempt, name='dispatch')
class HomeView(View):
...
AJAX
搜索引擎根据用户输入的关键字,自动提示检索关键字。
还有一个很重要的应用场景就是注册时候的用户名的查重。
优点:
AJAX使用JavaScript技术向服务器发送异步请求;
AJAX请求无须刷新整个页面;
因为服务器响应内容是是页面中的部分内容,所以AJAX性能高;
#案例
<input type="text" id="i1">+
<input type="text" id="i2">=
<input type="text" id="i3">
<input type="button" value="AJAX提交" id="b1">
<script>
//定位b1的点击操作,执行function
$("#b1").on("click", function () {
var i1 = $("#i1").val();
//通过val()方法取值
var i2 = $("#i2").val();
// 往后端发数据
$.ajax({
url: "/ajax_add/",
type: "get",
data: {"i1": i1, "i2": i2},
success: function (arg) {
// 把返回的结果填充到 id是i3的input框中
$("#i3").val(arg);
}
})
});
</script>
AJAX请求如何设置csrf_token
$.ajax({
url: "/cookie_ajax/",
type: "POST",
data: {
"username": "Q1mi",
"password": 123456,
"csrfmiddlewaretoken": $("[name = 'csrfmiddlewaretoken']").val() // 使用jQuery取出csrfmiddlewaretoken的值,拼接到data中
},
success: function (data) {
console.log(data);
}
})
$.ajax({
url: "/cookie_ajax/",
type: "POST",
headers: {"X-CSRFToken": $.cookie('csrftoken')}, // 从Cookie取csrftoken,并设置到请求头中
data: {"username": "Q1mi", "password": 123456},
success: function (data) {
console.log(data);
}
})
#编写setupajax.js文件,使用Ajax前导入介绍文件就行
<script src="/static/setupajax.js"></script>
文件代码如下
function csrfSafeMethod(method) {
// these HTTP methods do not require CSRF protection
return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
}
$.ajaxSetup({
beforeSend: function (xhr, settings) {
if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
xhr.setRequestHeader("X-CSRFToken", csrftoken);
}
}
});
Django内置的serializers序列化方法
def books_json(request):
book_list = models.Book.objects.all()[0:10]
from django.core import serializers
ret = serializers.serialize("json", book_list)
return HttpResponse(ret)
Ajax文件上传
// 上传文件示例
$("#b3").click(function () {
var formData = new FormData();
formData.append("csrfmiddlewaretoken", $("[name='csrfmiddlewaretoken']").val());
formData.append("f1", $("#f1")[0].files[0]);
$.ajax({
url: "/upload/",
type: "POST",
processData: false, // 告诉jQuery不要去处理发送的数据
contentType: false, // 告诉jQuery不要去设置Content-Type请求头
data: formData,
success:function (data) {
console.log(data)
}
})
})
JS 操作替代删除提示框
地址https://github.com/lipis/bootstrap-sweetalert
HTML文件
删除
<script src="/static/sweetalert/sweetalert.min.js"></script>
<script>
// 找到删除按钮绑定事件,button的class属性del
$(".del").on("click", function () {
var $trEle = $(this).parent().parent();
var delId = $trEle.children().eq(1).text();
swal({
title: "你确定要删除吗?",
text: "一旦删除就找不回来了",
type: "warning",
showCancelButton: true,
confirmButtonClass: "btn-warning",
confirmButtonText: "确认",
cancelButtonText: "取消",
closeOnConfirm: false,
},
function(){
// 向后端发送删除的请求
$.ajax({
url: "/delete/",
type: "post",
data: {"id":delId},
success:function (arg) {
swal(arg, "你可以跑路了!", "success");
$trEle.remove();
}
});
});
})
</script>
路由
url(r'^delete/$', views.delete),
视图
def delete(request):
del_id = request.POST.get("id")
print(del_id)
models.Person.objects.filter(id=del_id).delete()
return HttpResponse("删除成功!")
Form组件
views.py先定义好一个RegForm类:
Field
required=True, 是否允许为空
widget=None, HTML插件
label=None, 用于生成Label标签或显示内容
initial=None, 初始值
help_text='', 帮助信息(在标签旁边显示)
error_messages=None, 错误信息 {'required': '不能为空', 'invalid': '格式错误'}
show_hidden_initial=False, 是否在当前插件后面再加一个隐藏的且具有默认值的插件(可用于检验两次输入是否一直)
validators=[], 自定义验证规则
localize=False, 是否支持本地化
disabled=False, 是否可以编辑
label_suffix=None Label内容后缀
from django import forms
username = forms.CharField(min_length=8,label="用户名",initial="张三" #设置默认值)
error_messages 重写错误信息
password 密码隐藏
from django.forms import widgets
pwd = forms.CharField(
min_length=6,
label="密码",
widget=forms.widgets.PasswordInput(attrs={'class': 'c1'}, render_value=True)
radioSelect 单radio值为字符串
gender = forms.fields.ChoiceField(
choices=((1, "男"), (2, "女"), (3, "保密")),
label="性别",
initial=3,
widget=forms.widgets.RadioSelect()
)
单选Select
hobby = forms.fields.ChoiceField(
choices=((1, "篮球"), (2, "足球"), (3, "双色球"), ),
label="爱好",
initial=3,
widget=forms.widgets.Select()
)
多选Select
hobby = forms.fields.MultipleChoiceField(
choices=((1, "篮球"), (2, "足球"), (3, "双色球"), ),
label="爱好",
initial=[1, 3],
widget=forms.widgets.SelectMultiple()
)
单选checkbox
keep = forms.fields.ChoiceField(
label="是否记住密码",
initial="checked",
widget=forms.widgets.CheckboxInput()
)
多选checkbox
hobby = forms.fields.MultipleChoiceField(
choices=((1, "篮球"), (2, "足球"), (3, "双色球"),),
label="爱好",
initial=[1, 3],
widget=forms.widgets.CheckboxSelectMultiple()
)
choice字段注意事项 在使用选择标签时,需要注意choices的选项可以配置从数据库中获取,但是由于是静态字段 获取的值无法实时更新,需要重写构造方法从而实现choice实时更新。
from django.forms import Form
from django.forms import widgets
from django.forms import fields
class MyForm(Form):
user = fields.ChoiceField(
# choices=((1, '上海'), (2, '北京'),),
initial=2,
widget=widgets.Select
)
def __init__(self, *args, **kwargs):
super(MyForm,self).__init__(*args, **kwargs)
self.fields['user'].choices = models.Classes.objects.all().values_list('id','caption')
完整案例
from django import forms
# 按照Django form组件的要求自己写一个类
from django import forms
class RegForm(forms.Form):
name = forms.CharField(max_length=16,label="用户名",error_messages={"required""该字段不能为空",},)
pwd = forms.CharField(min_length=6,label="密码",error_messages={
"min_length": "密码不能少于6位!","required": "该字段不能为空",})
视图函数
# 使用form组件实现注册方式
def reg(request):
form_obj = RegForm()
if request.method == "POST":
# 实例化form对象的时候,把post提交过来的数据直接传进去
form_obj = RegForm(request.POST)
# 调用form_obj校验数据的方法
if form_obj.is_valid():
return HttpResponse("注册成功")
return render(request, "register.html", {"form_obj": form_obj})
HTML文件
#第一种
<form action="/reg/" method="post" novalidate>
{% csrf_token %}
//显示表单
{{ form_obj.as_p }}
//显示错误信息
{{ form_obj.errors }}
<p><input type="submit"></p>
</form>
#第二种
<form action="" method="post" novalidate>
{% csrf_token %}
{% for field in form %}
<div>
{{ field.label }}
{{ field }}
{{ field.errors }}
</div>
{% endfor %}
<input type="submit">
# 第三种,自己写HTML
<form action="/reg2/" method="post" novalidate>
{% csrf_token %}
<div class="form-group {% if form_obj.name.errors.0 %}has-error{% endif %}">
{{ form_obj.name.label }}
{{ form_obj.name }}
<span class="help-block">{{ form_obj.name.errors.0 }}</span>
</div>
<div class="form-group {% if form_obj.pwd.errors.0 %}has-error{% endif %}">
{{ form_obj.pwd.label }}
{{ form_obj.pwd }}
<span class="help-block">{{ form_obj.pwd.errors.0 }}</span>
</div>
</form>
form校验RegexValidator验证器,通过form_obj.is_valid()校验
from django.core.validators import RegexValidator
mobile = forms.CharField(
label="手机",
# 自己定制校验规则
validators=[
RegexValidator(r'^[0-9]+$', '手机号必须是数字'),
RegexValidator(r'^1[3-9][0-9]{9}$', '手机格式有误')
],
error_messages={
"required": "该字段不能为空",
}
)
form校验自定义方法
1局部钩子 在Fom类中定义 clean_字段名() 方法,就能够实现对特定字段进行校验。
# 定义局部钩子,用来校验username字段
def clean_username(self):
value = self.cleaned_data.get("username")
if "666" in value:
raise ValidationError("光喊666是不行的")
else:
return value
2全局钩子 在Fom类中定义 clean() 方法,就能够实现对字段进行全局校验。
def clean(self):
pwd = self.cleaned_data.get("pwd")
re_pwd = self.cleaned_data.get("re_pwd")
if pwd != re_pwd:
self.add_error("re_pwd", ValidationError("两次密码不一致"))
raise ValidationError("两次密码不一致")
return self.cleaned_data
ModelForm
你也许会有个Book模型,并且你还想创建一个form表单用来添加和编辑书籍信息到这个模型中
class BookForm(forms.ModelForm):
class Meta:
model = models.Book
fields = "__all__"
labels = {
"title": "书名",
"price": "价格"
}
widgets = {
"password": forms.widgets.PasswordInput(attrs={"class": "c1"}),
}
常用参数
model = models.Book # 对应的Model中的类
fields = "__all__" # 字段,如果是__all__,就是表示列出所有的字段
exclude = None # 排除的字段
labels = None # 提示信息
help_texts = None # 帮助提示信息
widgets = None # 自定义插件
error_messages = None # 自定义错误信息
每个ModelForm还具有一个save()方法。 这个方法根据表单绑定的数据创建并保存数据库对象。 ModelForm的子类可以接受现有的模型实例作为关键字参数instance;如果提供此功能,则save()将更新该实例。 如果没有提供,save() 将创建模型的一个新实例:
from myapp.models import Book
from myapp.forms import BookForm
# 根据POST数据创建一个新的form对象
form_obj = BookForm(request.POST)
# 新建书籍对象,并存储
new_ book = form_obj.save()
#基于一个书籍对象创建form对象
edit_obj = Book.objects.get(id=1)
# 使用POST提交的数据更新书籍对象的数据
form_obj = BookForm(request.POST, instance=edit_obj)
form_obj.save()
Django中间件
官方的说法:中间件是一个用来处理Django的请求和响应的框架级别的钩子。它是一个轻量、低级别的插件系统,用于在全局范围内改变Django的输入和输出每个中间件组件都负责做一些特定的功能。
自定义中间件
中间件可以定义五个方法,分别是:(主要的是process_request和process_response)
process_request(self,request)
它的返回值可以是None也可以是HttpResponse对象。返回值是None的话,按正常流程继续走,交给下一个中间件处理,如果是HttpResponse对象,Django将不执行视图函数,而将相应对象返回给浏览器。
总结
1.中间件的process_request方法是在执行视图函数之前执行的。
2.当配置多个中间件时,会按照MIDDLEWARE中的注册顺序,也就是列表的索引值,从前到后依次执行的。
3.不同中间件之间传递的request都是同一个对象
process_view(self, request, view_func, view_args, view_kwargs)
它应该返回None或一个HttpResponse对象。 如果返回None,Django将继续处理这个请求,执行任何其他中间件的process_view方法,然后在执行相应的视图。 如果它返回一个HttpResponse对象,Django不会调用适当的视图函数。 它将执行中间件的process_response方法并将应用到该HttpResponse并返回结果。
process_template_response(self,request,response)
process_template_response是在视图函数执行完成后立即执行,但是它有一个前提条件,那就是视图函数返回的对象有一个render()方法(或者表明该对象是一个TemplateResponse对象或等价方法)。顺序是倒序。
process_exception(self, request, exception)
这个方法只有在视图函数中出现异常了才执行,它返回的值可以是一个None也可以是一个HttpResponse对象。如果是HttpResponse对象,Django将调用模板和中间件中的process_response方法,并返回给浏览器,否则将默认处理异常。如果返回一个None,则交给下一个中间件的process_exception方法来处理异常。它的执行顺序也是按照中间件注册顺序的倒序执行。
process_response(self, request, response)
1.该方法的返回值也必须是HttpResponse对象。
2.多个中间件中的process_response方法是按照MIDDLEWARE中的注册顺序倒序执行的,
以上方法的返回值可以是None或一个HttpResponse对象,如果是None,则继续按照django定义的规则向后继续执行,如果是HttpResponse对象,则直接将该对象返回给用户。
中间件版登陆验证
urls.py
from django.conf.urls import url
from app01 import views
urlpatterns = [
url(r'^index/$', views.index),
url(r'^login/$', views.login, name='login'),
]
views.py
from django.shortcuts import render, HttpResponse, redirect
def index(request):
return HttpResponse('this is index')
def home(request):
return HttpResponse('this is home')
def login(request):
if request.method == "POST":
user = request.POST.get("user")
pwd = request.POST.get("pwd")
if user == "Q1mi" and pwd == "123456":
# 设置session
request.session["user"] = user
# 获取跳到登陆页面之前的URL
next_url = request.GET.get("next")
# 如果有,就跳转回登陆之前的URL
if next_url:
return redirect(next_url)
# 否则默认跳转到index页面
else:
return redirect("/index/")
#get请求就访问登录页面
return render(request, "login.html")
login.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="x-ua-compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>登录页面</title>
</head>
<body>
<form action="{% url 'login' %}">
<p>
<label for="user">用户名:</label>
<input type="text" name="user" id="user">
</p>
<p>
<label for="pwd">密 码:</label>
<input type="text" name="pwd" id="pwd">
</p>
<input type="submit" value="登录">
</form>
</body>
</html>
middlewares.py
class AuthMD(MiddlewareMixin):
white_list = ['/login/', ] # 白名单
balck_list = ['/black/', ] # 黑名单
def process_request(self, request):
from django.shortcuts import redirect, HttpResponse
#获取登陆页面的路径
next_url = request.path_info
print(request.path_info, request.get_full_path())
if next_url in self.white_list or request.session.get("user"):
return
elif next_url in self.balck_list:
return HttpResponse('This is an illegal URL')
else:
return redirect("/login/?next={}".format(next_url))
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',
'middlewares.AuthMD',
]