csrf跨站请求伪造

简介
  • 钓鱼网站:假设是一个跟银行一模一样的网址网页 用户在该页面转账
  • 账户的钱会减少 但是受益人不是自己想要转账的那个人
模拟

一台计算机上两个服务端不同端口启动 钓鱼网站提交地址改为正规网站的地址

真正的网址

后端代码
def transfer(request):
    if request.method == 'POST':
        username = request.POST.get('username')
        target_user = request.POST.get('target_user')
        money = request.POST.get('money')
        print(f'{username}给{target_user}转账{money}')
    return render(request, 'transfer.html')

前端代码
<h1>真正的网站</h1>
<form action="" method="post">
    <p>username: 
        <input type="text" name="username">
    </p>
    <p>target_user: 
        <input type="text" name="target_user">
    </p>
    <p>money: 
        <input type="text" name="money">
    </p>
    <input type="submit" value="转账">
</form>

image

钓鱼网站

后端代码
def transfer(request):
    return render(request, 'transfer.html')

前端代码
<h1>钓鱼网站</h1>
<form action="http://127.0.0.1:8000/transfer/" method="post">  # 朝8000端口发送请求
    <p>username: 
        <input type="text" name="username">
    </p>
    <p>target_user: 
        <input type="text">
        <input type="text" name="target_user" value="世纪大盗" style="display: none">
    </p>
    <p>money: 
        <input type="text" name="money">
    </p>
    <input type="submit" value="转账">
</form>
"""
预防
	csrf策略:通过在返回的页面上添加独一无二的标识信息从而区分正规网站和钓鱼网站的请求
"""

image

csrf操作

  • form表单
  • Ajax

form表单

<form action="" method="post">
    {% csrf_token %} #  在form加上这个
    <p>username:
        <input type="text" name="username">
    </p>
    <p>target_user:
        <input type="text" name="target_user">
    </p>
    <p>money:
        <input type="text" name="money">
    </p>
    <input type="submit" value="转账">
</form>

ajax

方式一、先编写csrf模板语法 然后利用标签查找和值获取 手动添加
<h1>真正的网站</h1>
{% csrf_token %}  # 引入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>
方式二、直接利用模板语法即可
<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>
方式三

通用方式直接拷贝js代码并应用到自己的html页面上即可(js脚本) 扩展性最高

<h1>真正的网站</h1>
<button id="d1">点我发送ajax请求</button>
<script src="/static/csrf.js"></script>  # 引入js文件
<script>
    $('#d1').click(function () {
        $.ajax({
            url: '',
            type: 'post',
            data:{'username':'jason'},  # 直接使用
            success: function (args) {

            }
        })
    })
</script>
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);
    }
  }
});

csrf相关装饰器

  • FBV
  • CBV

针对FBV

from django.views.decorators.csrf import csrf_exempt, csrf_protect
"""
csrf_exempt 校验csrf
csrf_protect 不校验csrf
"""
@csrf_protect
@csrf_exempt
def home(request):
    return HttpResponse('哈哈哈')

针对CBV

针对CBV不能直接在方法上添加装饰器 需要借助于专门添加装饰器的方法

from django.views.decorators.csrf import csrf_exempt, csrf_protect
from django import views
from django.utils.decorators import method_decorator

@method_decorator(csrf_protect,name='post')# 方式二:指名道姓的添加
@method_decorator(csrf_exempt,name='post')  # 针对csrf_exempt 第二种方式不可以
class MyHome(views.View):
    @method_decorator(csrf_protect)  # 方式三:影响类中所有的方法
    @method_decorator(csrf_exempt,name='post')  # 针对csrf_exempt 第一种方式可以
    def dispatch(self, request, *args, **kwargs):
        return super(MyHome, self).dispatch(request, *args, **kwargs)

    def get(self, request):
        return HttpResponse('Home Get view')

    @method_decorator(csrf_protect)  # 方式一:指名道姓的添加
    @method_decorator(csrf_exempt,name='post')  # 针对csrf_exempt 第一种方式不可以
    def post(self, request):
        return HttpResponse('Home Post view')
"""
针对csrf_exempt只有方式三有效 针对其他装饰器上述三种方式都有效
"""

auth认证模块

django执行数据库迁移命令之后会产生一个auth_user表
	该表可以配合auth模块做用户相关的功能:注册 登录 修改密码 注销 ...
 	该表还是django admin后台管理默认的表
django admin后台管理员账号创建
	python manage.py createsuperuser

auth模块常见功能

1.创建用户
from django.contrib import auth
def login(request):
    if request.method == 'POST':
        username = request.POST.get('username')
        password = request.POST.get('password')
        user_obj = auth.authenticate(request,username=username,password=password)
        print(user_obj)  # 数据正确的情况下返回的是数据对象,错误的情况下返回的None
        print(user_obj.username)  # joker
        print(user_obj.password)  # 密文密码
        print(user_obj.pk)  # 1
    return render(request,'login.html')
2.校验用户名和密码是否正确
from django.contrib import auth
def login(request):
    if request.method == 'POST':
        username = request.POST.get('username')
        password = request.POST.get('password')
        auth.authenticate(request,username,password)
    return render(request,'login.html')
"""
auth.authenticate()
	会自动查找有没有该用户
	密码会自动加密进行比对
"""
3.用户登录
def login(request):
    if request.method == 'POST':
        username = request.POST.get('username')
        password = request.POST.get('password')
        user_obj = auth.authenticate(request,username=username,password=password)
        if user_obj:
            auth_login(request,user_obj)  # 自动操作cookie和cession
    return render(request,'login.html')
4.判断用户是否登录
def login(request):
    print(request.user.is_authecticated)
    if request.method == 'POST':
        username = request.POST.get('username')
        password = request.POST.get('password')
        user_obj = auth.authenticate(request,username=username,password=password)
        if user_obj:
            auth_login(request,user_obj)  # 自动操作cookie和cession
    return render(request,'login.html')
"""
request.user.is_authecticated
	当前用户登录返回True
	当前用户没有登录返回False
"""
5.获取登录用户对象
def login(request):
    print(request.user)
    if request.method == 'POST':
        username = request.POST.get('username')
        password = request.POST.get('password')
        user_obj = auth.authenticate(request,username=username,password=password)
        if user_obj:
            auth_login(request,user_obj)  # 自动操作cookie和cession
    return render(request,'login.html')
"""
request.user
	当用户登录成功之后(执行了auth.login) 该方法返回当前登录用户对象
	当用户没有登录成功(没有执行了auth.login) 该方法返回匿名用户对象
6.校验用户登录装饰器
跳转局部配置
	from django.contrib.auth.decorators import login_required  # 装饰需要导入改模块
	login_required(login_url='/login/')  # 用户没有登录跳转到登录页面
	def func(request):
		return HttpResponse('登录用户才能查看 from func')
跳转全局配置
需要在配置文件中设置
	LOGIN_URL = '/login/'
"""
1.如果局部和全局都有 该听谁的
	局部 > 全局
2.局部和全局哪个好呢
	全局的好处在于无需重复写代码 但是跳转的页面却很单一
	局部的好处在于不同的视图函数在用户没有登陆的情况下可以跳转到不同的页面
"""
7.校验密码是否正确
@login_required
def set_password(request):
    if request.method == 'POST':
    old_password = request.POST.get('old_password')
    new_password = request.POST.get('new_password')
    confirm_password = request.POST.get('confirm_password')
    is_right = request.user.check_password(old_password)  # 自动加密并校验
    print(id_right)
    return render(requuest,'password.html')
"""
request.user.check_password(old_password)
	原密码对的就返回True
	原密码错误就返回False
"""
8.修改密码
@login_required
def set_password(request):
    if request.method == 'POST':
    old_password = request.POST.get('old_password')
    new_password = request.POST.get('new_password')
    confirm_password = request.POST.get('confirm_password')
    is_right = request.user.check_password(old_password)  # 自动加密并校验
    if is_right:
        request.user.set_password(new_passowrd)
        request.user.save()  # 必须要保存
    return render(requuest,'set_password.html')
"""
request.user.set_password(new_passowrd)  # 会自动把密码加密
request.user.save()  # 必须要保存
"""
9.注销登录
@login_required
def logout(request):
    auth.logout(request)  # 自动清除cookie和cession
    return HttpResponse('注销')
"""
auth.logout(request)
	自动清除cookie和cession
"""
10.注册用户
from django.contrib.auth.models import User
def register(request):
    User.objects.create(username=username,password=password)
    User.objects.create_user(username=username,password=password)
    User.objects.create_superuser(username=username,email='123@qq.com',password=password)
    return render(requuest,'register.html')
"""
1.操作auth_user表写入数据
	User.objects.create(username=username,password=password)  # 写入数据  不能用create 密码没有加密处理
2.创建普通用户
	User.objects.create_user(username=username,password=password)
3.创建超级用户:使用代码创建超级用户 邮箱是必填的 而用命令创建则可以不填
	User.objects.create_superuser(username=username,email='123@qq.com',password=password)
"""

auth_user表切换

我们还想用auth模块的方法并且扩展auth_user表字段

"""
如果继承了AbstractUser
    那么在执行数据库迁移命令的时候auth_user表就不会再创建出来了
    而UserInfo表中会出现auth_user所有的字段外加自己扩展的字段
    这么做的好处在于你能够直接点击你自己的表更加快速的完成操作及扩展
    
前提:
	1.在继承之前没有执行过数据库迁移命令
		auth_user没有被创建,如果当前库已经创建了那么你就重新换一个库
	2.继承的类里面不要覆盖AbstractUser里面的字段名
		表里面有的字段都不要动,只扩展额外字段即可
	3.需要在配置文件中告诉django你要用UserInfo替代auth_user
		AUTH_USER_MODEL = 'app01.UserInfo'
						'应用名.表名'
"""
from django.db import models
from django.contrib.auth.models import User,AbstractUser

class UserInfo(AbstractUser):
    phone = models.BigIntegerField()
    desc = models.TextField()
"""
如果自己写表替代了auth_user那么
auth模块的所有功能还是照常使用 参考的表页由原来的auth_user变成了UserInfo
"""

基于django中间件设计项

一、简单的函数式封装
def send_qq(content):
    print('qq消息通知:',content)
def send_weixin(content):
    print('微信消息通知:',content)
def send_msg(content):
    print('短信消息通知:'content) 

def send_add(content):
    send_qq(conent)
    send_weixin(content)
    send_msg(content)

if __name__ == '__main__':
    send_all('快国庆喽')
二、配置文件插拔式设计

需要有一个启动文件start 和一个配置文件settings 和一个notify文件夹

notify文件夹
qq.py
class QQ(object):
    def __init__(self):
        pass  # 模拟发送qq之前 应该准备的代码环境
    def send_qq(content):
    print('qq消息通知:',content)

weixin.py
class WeiXin(object):
    def __init__(self):
        pass  # 模拟发送微信之前 应该准备的代码环境
    def send_qq(content):
    print('微信消息通知:',content)

msg.py
class Msg(object):
    def __init__(self):
        pass  # 模拟发送短信之前 应该准备的代码环境
    def send_qq(content):
    print('短信消息通知:',content)


配置文件settings
NOTIFY_FUNC_LIST = [
    'notify.qq.QQ',
    'notify.weixin.WeiXin',
    'notify.msg.Msg',
]


notify文件夹的__init__
import importlib
import settings

def send_all(content):
    for i in settings.NOTIFY_FUNC_LIST:
        module_path, class_str_name = i.rsplit('.',maxsplit=1)
        module = importlib.import_module(module_path)
        class_name = getattr(module, class_str_name)
        obj = class_name()
        obj.send(content)

启动文件start
import notify

if __name__ == '__main__':
    notify.send_all('国庆七天假')
 posted on 2022-09-13 22:35  Joker_Ly  阅读(59)  评论(0)    收藏  举报