【Django】第10回 csrf操作和auth认证模块
目录
1. csrf跨站请求伪造
1.1 什么是csrf
CSRF(Cross Site Request Forgery) 跨站请求伪造。也被称为One Click Attack和Session Riding,通常缩写为CSRF或XSRF。如果从名字你还不不知道它表示什么,你可以这样理解:攻击者(黑客,钓鱼网站)盗用了你的身份,
以你的名义发送恶意请求,这些请求包括发送邮件、发送消息、盗取账号、购买商品、银行转账,从而使你的个人隐私泄露和财产损失。
1.2 前戏
1. 简介
钓鱼网站:假设是一个跟银行一模一样的网址页面 用户在该页面上转账
账户的钱会减少 但是受益人却不是自己想要转账的那个人
2. 模拟
一台计算机上两个服务端不同端口启动 钓鱼网站提交地址改为正规网站的地址
3. 预防
csrf策略:通过在返回的页面上添加独一无二的标识信息从而区分正规网站和钓鱼网站的请求
1.3 案例操作
1.4 csrf操作
form表单
<form action="" method="post">
{% csrf_token %}
</form>
ajax
方式1:先编写csrf模板语法 然后利用标签查找和值获取 手动添加****
<h1>这是真正的网站</h1>
{% csrf_token %}
<button id="d1">点我发送ajax请求</button>
<script>
$('#d1').click(function (){
$.ajax({
url:'',
type:'post',
data:{'username':'jason','csrfmiddlewaretoken':$('[name="csrfmiddlewaretoken"]').val()},
success:function (args){
}
})
})
</script>
方式2:直接利用模板语法即可
<h1>这是真正的网站</h1>
<button id="d1">点我发送ajax请求</button>
<script>
$('#d1').click(function (){
$.ajax({
url:'',
type:'post',
data:{'username':'jason','csrfmiddlewaretoken':'{{ csrf_token }}'},
success:function (args){
}
})
})
</script>
方式3:通用方式(js脚本)
function getCookie(name) {
var cookieValue = null;
if (document.cookie && document.cookie !== '') {
var cookies = document.cookie.split(';');
for (var i = 0; i < cookies.length; i++) {
var cookie = jQuery.trim(cookies[i]);
// Does this cookie string begin with the name we want?
if (cookie.substring(0, name.length + 1) === (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
var csrftoken = getCookie('csrftoken');
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);
}
}
});
更多细节详见:Djagno官方文档中关于CSRF的内容
2. csrf相关装饰器
问题
1.当整个网站默认都不校验csrf 但是局部视图函数需要校验 如何处理
2.当整个网站默认都校验csrf 但是局部视图函数不需要校验 如何处理
2.1 FBA
点提交进入home页面
全局不校验,想让谁校验加@csrf_protect,点提交会403
全局都校验,某个不校验,用csrf_exempt
2.2 CBA
针对CBV不能直接在方法上添加装饰器 需要借助于专门添加装饰器的方法
针对csrf_exempt只有方式3有效 针对其他装饰器上述三种方式都有效
方式1 指名道姓的添加
from django import views
from django.utils.decorators import method_decorator
class MyHome(views.View):
def get(self,request):
return HttpResponse('Home Get view')
@method_decorator(csrf_protect)
def post(self,request):
return HttpResponse('Home Post view')
方式2 指名道姓的添加
@method_decorator(csrf_protect, name='post')
class MyHome(views.View):
def get(self,request):
return HttpResponse('Home Get view')
def post(self,request):
return HttpResponse('Home Post view')
方式3 影响类中所有的方法
from django import views
from django.utils.decorators import method_decorator
class MyHome(views.View):
@method_decorator(csrf_protect) # 方式3:影响类中所有的方法
def dispatch(self, request, *args, **kwargs):
super(MyHome, self).dispatch(request, *args, **kwargs)
def get(self,request):
return HttpResponse('Home Get view')
def post(self,request):
return HttpResponse('Home Post view')
3. auth认证模块
3.1 auth 简介
django执行数据库迁移命令之后会产生一个auth_user表
该表可以配合auth模块做用户相关的功能:注册 登录 修改密码 注销 ...
该表还是django admin后台管理默认的表
django admin后台管理员账号创建
python manage.py createsuperuser
3.2 auth模块常见功能
from django.contrib import auth
def login(request):
if request.method == 'POST':
username = request.POST.get('username')
password = request.POST.get('password')
# 查询数据库校验数据
res = auth.authenticate(request,username=username,password=password)
print(res)
'''
数据正确的情况下返回的是数据对象
数据错误的情况下返回的是None
'''
return render(request,'login.html')
print(res.username)
print(res.password)
print(res.pk)
def login(request):
if request.method == 'POST':
username = request.POST.get('username')
password = request.POST.get('password')
# 查询数据库校验数据(密码自动加密再比对)
is_user_obj = auth.authenticate(request, username=username, password=password)
if is_user_obj:
auth.login(request, is_user_obj) # 自动操作cookie和session
return render(request, 'login.html')
获取登录用户对象
print(request.user)
当用户登录成功之后(执行了auth.login)该方法返回当前登录用户对象
当用户没有登录成功(没有执行auth.login)该方法返回匿名用户对象
判断用户是否登录
print(request.user.is_authenticated)
判断当前用户密码是否登录
1.创建用户
from django.contrib.auth.models import User
User.object.create_user(username,password)
User.object.create_superuser(username,password,email)
2.校验用户名和密码是否正确
from django.contrib import auth
auth.authenticate(request,username,password)
3.用户登录
auth.login(request,user_obj)
4.判断用户是否登录
request.user.is_authecticated
5.获取登录用户对象
request.user
6.校验用户登录装饰器
from django.contrib.auth.decorators import login_required
跳转局部配置
login_required(login_url='/login/')
跳转全局配置
LOGIN_URL = '/login/'
7.校验密码是否正确
request.user.check_password(old_password)
8.修改密码
request.user.set_password(new_passowrd)
request.user.save()
9.注销登录
auth.logout(request)
4. auth_user表切换
1.models.py
from django.contrib.auth.models import AbstractUser # auth_user表序列化的一个类
class Userinfo(AbstractUser):
'''扩展auth_user表中没有的字段'''
phone = models.BigIntegerField()
desc = models.TextField()
"""
写一个类继承AbstractUser这个类,那么我写的这个类就拥有了AbstractUser这个类里面所有的字段
"""
2.settings.py
AUTH_USER_MODEL = 'app01.Userinfo'
5. 基于django中间件设计项目功能
"""
模块的导入
import 句式
from ... import ... 句式
"""
1.简单的函数式封装
2.配置文件插拔式设计
封装函数
def send_qq(content):
print('qq消息通知:', content)
def send_wechat(content):
print('微信消息通知:', content)
def send_msg(content):
print('短信消息通知:', content)
def send_all(content):
# send_qq(content) 不想发哪个消息就注释那个函数
send_wechat(content)
send_msg(content)
if __name__ == '__main__':
send_all('所有后来呢')
qq.py文件
class QQ(object): # 为了兼容括号内写object
def __init__(self):
pass # 模拟发送qq之前 应该准备的代码环境
def send(self, content):
print('qq消息通知:', content)
__init.py__文件
import importlib
import settings
def send_all(content):
for i in settings.NOTIFY_FUNC_LIST: # 'day64.notify.qq.QQ'
module_path, class_str_name = i.rsplit('.', maxsplit=1) # 'day64.notify.qq' 'QQ'
module = importlib.import_module(module_path) # from day64.notify import qq from day64.notify import weixin from day64.notify import msg
class_name = getattr(module, class_str_name) # 真正的类名
obj = class_name()
obj.send(content)
setting.py文件
NOTIFY_FUNC_LIST = [
'ffff.notify.qq.QQ',
'ffff.notify.weixin.WeiXin',
'ffff.notify.msg.Msg',
# 'ffff.notify.tt.Tt' # 如果那天不想用了,直接注释掉就可以
]
start.py文件
from ffff import notify
if __name__ == '__main__':
notify.send_all('XXX')