一、csrf简介
# 向之前我们所接触过的钓鱼网站来盗取别人的钱财的网站是我们所痛恨的 eg: 就比如现在有一个转账网站只需要输入转账用户和对方用户即可 然后现在不法分子做了一个与正规网页一模一样的网站 然后把对方用户改成了自己用户 那么用户的钱会扣但是转账用户却不是自己想要转的 这样当用户不小心进错了网站那么很有可能会造成钱财的流失 # 原理: 就是网站做成一模一样 但是用户的输入框的value改成自己的 然后隐藏即可 然后暴露给用户一个没有name属性的输入框 # 模拟 一台计算机上两个服务端不同端口启动 钓鱼网站提交地址改为正规网站的地址 # 预防 csrf策略:通过在返回的页面上添加独一无二的标识信息 这样钓鱼网站就会拿不到这个标识信息 从而区分正规网站和钓鱼网站的请求
二、csrf操作
1.form表单
因为我们HTML有post请求有两种 form表单和ajax # 针对form表单只有一种: # 在form表单中写上这个模板语法即可 模板语法: {% csrf_token %} '''我们在HTML上写上的时候django会帮我们在浏览器上创建一个input框 然后我们在提交post请求的时候 这个input框也会一起提交到后端 然后后端会校验短K:V键值对是否符合'''

2.ajax
# 针对ajax有三种csrf操作 # 方式1:先编写模板语法 然后在通过查找标签获取和值获取 手动添加 data:{'csrfmiddlewaretoken':$('[name="csrfmiddlewaretoken"]').val(),}, # 方式2:直接在data中添加上面这个K:V键值对 data:{ 'csrfmiddlewaretoken':'{{ csrf_token }}'}, # 方式3:通用法式(js脚本) 就是提前创建一个js文件 然后在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); } } }); # 然后页面上在导入即可 {% load static %} <script src="{% static 'mycsrf.js' %}"></script>
三、FBV相关装饰器
''' 当整个网站的都不需要校验csrf的时候 局部函数需要校验csrf任何处理 当整个网站的都需要校验csrf的时候 局部函数不需要校验csrf任何处理 ''' # 因为django的试图层可以有两种编写 一种是基于函数编写FBV和基于类编写 CBV这两种的处理方式不一样
1.FBV
# 只要局部函数需要校验或不校验csrf都需要导入模块 from django.views.decorators.csrf import csrf_protect,csrf_exempt ''' csrf_protect 校验csrf csrf_exempt 不校验csrf ''' # 1.当全局需要校验csrf 局部不需要校验csrf时 只需要在功能函数上面加上装饰器即可 @csrf_exempt def login(request): return render(request, 'login.html') # 2.当全局不需要校验csrf 局部需要校验csrf时 @csrf_protect def login(request): return render(request, 'login.html') # 也是一样的操作 这个时候朝这个函数提交post请求的时候就需要csrf校验了
2.CBV
# 针对CBV有三种加上装饰器 # CBV添加csrf装饰器也是需要导入模块 from django..views.decorators.csrf import csrf_protect,csrf_exempt # 针对CBV不能直接在方法上添加装饰器 需要借助于专门添加装饰器的方法 from django.utils.decorators import method_decorator '''全局需要不校验csrf 局部需要校验csrf''' # 然后通过另一个网址向该网站发送post请求 # 方式1:指名道姓的添加 class MyView(views.View): @method_decorator(csrf_exempt) def post(self, request): return HttpResponse('post 请求') # 方式2:指名道姓的加 @method_decorator(csrf_protect, name='post') class MyView(views.View): def post(self, request): return HttpResponse('post 请求') # 方式3:重写dispatch方法 影响类中所有的方法 class MyView(views.View): @method_decorator(csrf_protect) def dispatch(self, request, *args, **kwargs): # 根据属性查找顺序会先执行改dispatch方法 return super().dispatch(request, *args, **kwargs) # 但是当全局需要校验csrf 局部不需要校验csrf的时候 只有第三种方法会生效 前面两种方法不会生效 class MyView(views.View): @method_decorator(csrf_exempt) def dispatch(self, request, *args, **kwargs): return super().dispatch(request, *args, **kwargs)
四、auth认证模块
''' 当我们执行django迁移命令的时候 django会帮我们生成一张auth_user表 这张表中帮我们写了很多字段 然后该表可以配合auth模块做用户的相关功能:注册 登录 修改密码 注销 ... ''' # django自带的admin后台管理用户登入参考的就是auth_user表 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') is_obj = auth.authenticate(request, username=username, password=password) print(is_obj) return render(request, 'login.html') ''' 如果数据正确返回的是用户对象 用户对象就可以点出表中的字段 如果数据不正确返回的是None '''
2.用户登入
from django.contrib import auth def login(request): if request.method == 'POST': username = request.POST.get('username') password = request.POST.get('password') is_obj = auth.authenticate(request, username=username, password=password) if is_obj: # 因为如果用户正确那么返回的是用户对象 所以正确的时候是会走这条if分支的 auth.login(request, is_obj) # 会自动帮我们处理cookie和session return render(request, 'login.html') # 如果登入成功 那么auth.login会自动帮我们在页面上保存cookie和session
3.获取当前登入用户对象
from django.contrib import auth def login(request): print(request.user) if request.method == 'POST': username = request.POST.get('username') password = request.POST.get('password') is_obj = auth.authenticate(request, username=username, password=password) if is_obj: auth.login(request, is_obj) return render(request, 'login.html') ''' request.user 当用户登入成功之后并执行auth.login 返回的是当前登入用户对象 如果用户没有登入成功(就是没有执行auth.login) 返回的是一个匿名用户对象(AnonymousUser) '''
4.判断当前用户是否登入
from django.contrib import auth def login(request): print(request.user) print(request.user.is_authenticated) if request.method == 'POST': username = request.POST.get('username') password = request.POST.get('password') is_obj = auth.authenticate(request, username=username, password=password) if is_obj: auth.login(request, is_obj) return render(request, 'login.html') ''' request.user.is_authenticated 如果当前用户登入 返回True 如果当前用户没有登入 返回False '''
5.登入装饰器
# 就是现在用多个函数需要登入用户才能使用 那么这就需要登入装饰器即可 # auth装饰器需要导入 from django.contrib.auth.decorators import login_required @login_required(login_url='/login/') # 用户如果没有登入就会跳转到login页面 def func(request): return HttpResponse('登入用户查看 func') # 用户没有登入就会跳转到其他页面有两种设置 第一种就是上面方法 # 第二种可以到配置文件中修改 LOGIN_URL = '/login/' # 在配置文件中写上需要跳转的页面 然后在函数上面的装饰器就可以不用写括号内的参数
6.校验密码是否正确
# 就比如现在我需要修改密码 这种情况我还是记得住老密码的 但是修改密码是需要验证老密码是否正确的 auth会帮我们验证 @login_required def set_password(request): if request.method == 'POST': old_password = request.POST.get('old_password') is_true = request.user.check_password(old_password) print(is_true) return render(request, 'password.html') ''' request.user.check_password(old_password) 该方法如果验证正确 那么会返回True 如果错误 返回False '''
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') is_true = request.user.check_password(old_password) if is_true: request.user.set_password(new_password) request.user.save() # 修改之后一定要保存 return render(request, 'password.html') ''' request.user.set_password(new_password) 该方法会自动帮我们加密并保存 修改之后一定要保存 '''
8.注销用户
# 注销用户就是退出登入 浏览器上的cookie和session的值删除就是退出登入 @login_required def loginout(request): auth.logout(request) return HttpResponse('注销成功') auth.logot # 会自动帮我们操作session
9.创建用户
# 因为我们创建用户的时候需要保存用户信息的 但是现在我们点不出这张表 我们可以需要导入表 def register(request): User.objects.create(username='jason',password=123) # 不能使用auth模块(密码不加密) User.objects.create_user(username='jason',password=123) # 创建普通用户(密码会帮我们自动加密) User.objects.create_superuser(username='tony',password=321,email='666@qq.com') # 创建管理员的时候需要添加email return HttpResponse('注册功能')
六、auth_user切换
# 因为auth_user表字段django已经帮我们写好了 但是现在我们先要添加一些字段 并可以使用auth模块 # 因为创建表的时候是一个类所以我们可以继承这个类即可 # models.py from django.contrib.auth.models import AbstractUser class Userinfo(AbstractUser): '''扩展auth_user表中没有的字段 auth_user表中有的字段不能继续编写''' phone = models.BigIntegerField() desc = models.TextField() # settings AUTH_USER_MODEL = 'app01.Userinfo' # 让django创建Userinfo这张表表里面的字段原来的基础上再加上自己创建的字段 # 然后原本的auth_user表就会不见了 # 这个时候我们就可以添加这两个字段的数据了
七、importlib模块
'''之前我们导入模块的时候是只有两种方法: import 模块名 from ... import 模块名 ''' # 现在我们还可以使用字符串形式导入模块 # 使用该功能需要使用导入模块: import importlib
1.代码实现
# 现在我们可以编写两个py文件模拟导入模块 # bbb文件夹下的b.py中 name = 'from bbb.b' # 现在我想在a.py中使用b.py中的name # 方式1:常规导入 from bbb import b print(b.name) print(b) # <module 'bbb.b' from 'D:\\MYpycharm\\day41csrf与auth模块\\bbb\\b.py'> # 其实当我们打印b的时候其实本质就是一个路径 # 方式2:使用字符串导入 import importlib res = 'bbb.b' res1 = importlib.import_module(res) print(res1.name) # from bbb.b print(res1) # <module 'bbb.b' from 'D:\\MYpycharm\\day41csrf与auth模块\\bbb\\b.py'> # 也是可以导入使用 然后我们打印res1发现其实也就是一个路径 '''两种导入的区别''' 1.常规导入:最小单位可以是变量名 from bbb.b import name # 这个时候a.py就可以直接使用 2.字符串导入:最小单位只能是py文件 res = 'bbb.b.name' res1 = importlib.import_module(res) # 这样是会报错的
2.基于中间件思想编写项目
''' django中的中间件看起来的是一堆的字符串 但是其实这些都是模块 我们不用的时候可以把它注释掉即可 用户的时候打开即可 ''' # 我们可以模仿中间件写一个小项目 eg:已发送信息为例: 方式1:封装成函数 def qq(content): print(f'qq发送信息:{content}') def weixin(content): print(f'微信发送信息:{content}') def msg(content): print(f'短信发送信息:{content}') def all_send(content): qq(content) weixin(content) msg(content) if __name__ == '__main__': all_send('国庆放假7天')
3.插拔式设计(封装成配置)
# 需要一个启动文件 start.py import notify if __name__ == '__main__': notify.all_send('国庆放假') ''' 运行结果: 短信发送信息:国庆放假 请求发送信息:国庆放假 微信发送信息:国庆放假 ''' # 配置文件settings NOTIFY_STR_PATH = [ 'notify.msg.Msg', 'notify.qq.QQ', 'notify.weixin.WinXin', ] # 弄一个包notify 包中有各个功能的py文件和双下init文件 __init__.py: import settings import importlib def all_send(content): for res in settings.NOTIFY_STR_PATH: # notify.msg.Msg module_path, module_str_name = res.rsplit('.', maxsplit=1) # notify.msg Msg 按照.右分割 只分割一次 module_name = importlib.import_module(module_path) # 根据module_path导入模块 class_name = getattr(module_name, module_str_name) # 获取模块中的对象方法 module_obj = class_name() # 实例化对象 module_obj.send(content) # 发送信息 # msg.py class QQ(object): def __init__(self): pass # 在发送qq消息的时候肯定还需要导入一些模块的 def send(self, content): print(f'请求发送信息:{content}') # qq和微信也是一样的
