一、跨站请求伪造(csrf)
1. 什么是csrf
- csrf是一种挟制用户在当前已登录的Web应用程序上执行非本意的操作的攻击方法。跟跨网站脚本(XSS)相比,XSS 利用的是用户对指定网站的信任,CSRF 利用的是网站对用户网页浏览器的信任
- 跨站请求攻击,简单地说,是攻击者通过一些技术手段欺骗用户的浏览器去访问一个自己曾经认证过的网站并运行一些操作(如发邮件,发消息,甚至财产操作如转账和购买商品)。由于浏览器曾经认证过,所以被访问的网站会认为是真正的用户操作而去运行。这利用了web中用户身份验证的一个漏洞:简单的身份验证只能保证请求发自某个用户的浏览器,却不能保证请求本身是用户自愿发出的。
2. 钓鱼网站原理
-
钓鱼网站利用的就是csrf
-
通过开发一个和正规网站一模一样的页面,当用户不经意点击进去后,用户以为这就是正规的网站(因为长得都一样)。此时用户可能会在这个虚假的网页上做一些操作,最后往后端提交数据时,虚假网站其实对用户提交的数据做一定的篡改,再往正规网站后端提交。
-
如何实现(以银行转账为例)
- 钓鱼网站的页面写form表单的时候,让用户填写的对方账户input并没有name属性
- 而是在内部偷偷隐藏了一个具有name属性的input,并且value值是钓鱼网站的账户
3. 如何解决csrf
(1)思路:
- 让正规网站只处理由本正规网站发送来的post请求,不处理其他网站发送来的post请求。
- 但是此思路有个问题:如何识别、判断当前请求是由本网站发出的
(2)实现方法
- 当用户发送get请求想要获取到正规网站的页面时,正规网站返回给用户html页面时,会自动在该页面的源码中隐藏一个input框
- 这个框的value是一个随机字符串,并且网站自己也保留同样的一个这个字符串
- 当用户发送post请求时,后端会自动校验
request.POST
中的随机字符串是否和网站之前保存的随机字符串相同 - 相同则处理该post请求,不同则返回403错误页面。
(3)实现的具体代码
-
基于该项目是django项目
-
分为2种情况的post请求
- form表单发送post请求
- ajax发送post请求
1. 针对form表单发送的post请求
- 在写form表单的时候,你只需要在表单中写一个
{% csrf_token %}
即可
<form action="" method="post">
{% csrf_token %}
<p>username:<input type="text" name="username"></p>
<p>target_account:<input type="text" name="target_user"></p>
<p>money:<input type="text" name="money"></p>
<input type="submit">
</form>
2. 针对ajax发送post请求
- 有3中书写方式。推荐使用第三种,最为通用
- 第一种(较繁琐)
先在页面任意的位置上书写{% csrf_token %}
然后在发送ajax请求的时候 通过标签查找获取随机字符串添加到data自定义对象即可
data:{'username':'jason','csrfmiddlewaretoken':$('input[name="csrfmiddlewaretoken"]').val()},
- 第二种(较简单)
先在页面任意的位置上书写{% csrf_token %}
然后在发送ajax请求的时候 通过标签查找获取随机字符串添加到data自定义对象即可
data:{'username':'jason','csrfmiddlewaretoken':'{{ csrf_token }}'},
- 第三种(最通用)
直接新建js文件拷贝官方代码,再在html文件中导入即可
你不需要做任何的csrf相关的代码书写
<!--这里直接提供了官方的代码,直接拷贝即可-->
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);
}
}
});
3. csrf相关的装饰器
- 在django的项目中,django有一个中间件,专门用来处理csrf。
- 这里django中提供的装饰器分为两种:
- 对post请求进行csrf处理:
csrf_protect
- 忽略对post请求的csrf处理:
csrf_exempt
- 对post请求进行csrf处理:
(1)csrf_protect
装饰器
- 分为对FBV和CBV的装饰
1. 对FBV的装饰
- 直接装饰在视图函数上即可
# 应用文件夹的views文件中
from django.views.decorators.csrf import csrf_protect
@csrf_protect # 直接装饰在视图函数上
def index(request):
return HttpResponse('index')
2. 对CBV的装饰
- 给CBV加装饰器的三种方式都可以
# 应用文件夹的views文件中
from django.views.decorators.csrf import csrf_protect
from django.views import View
from django.utils.decorators import method_decorator
@method_decorator(csrf_protect,name='post') # 1. 可以
class MyIndex(views.View):
# @method_decorator(csrf_protect) # 2. 可以
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request,*args,**kwargs)
def get(self,request):
return render(request,'transfer.html')
# @method_decorator(csrf_protect) # 3. 可以
def post(self,request):
return HttpResponse('OK')
(2)csrf_exempt
装饰器
- 分为对FBV和CBV的装饰
1. 对FBV的装饰
- 直接装饰在视图函数上即可
# 应用文件夹的views文件中
from django.views.decorators.csrf import csrf_protect
@csrf_exempt # 直接装饰在视图函数上
def index(request):
return HttpResponse('index')
2. 对CBV的装饰
- 注意:给CBV加装饰器的三种方式中,只有给
dispatch
方法装才有效
from django.views.decorators.csrf import csrf_protect
from django.views import View
from django.utils.decorators import method_decorator
# @method_decorator(csrf_exempt,name='post') # 1. csrf_exempt不支持该方法
@method_decorator(csrf_exempt,name='dispatch') # 2. 可以
class MyIndex(views.View):
# @method_decorator(csrf_exempt) # 2. 可以
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request,*args,**kwargs)
def get(self,request):
return render(request,'transfer.html')
# @method_decorator(csrf_exempt,name='post') # 3. csrf_exempt不支持该方法
def post(self,request):
return HttpResponse('OK')
二、django的settings源码剖析
- django有两个配置文件
- 一个是直接暴露给用户,支持用户修改配置
- 一个是内部全局的(隐藏的)
- 当django项目启动时,先加载全局的配置,再加载局部的配置(即暴露给用户的配置)。如果两者有重复的,则局部覆盖全局的配置。
1. 模仿django的settings源码设计自定义settings
# 新建两个conf文件夹,conf1中新建一个settings.py文件,conf2中新建一个global_settings.py文件,且conf2文件夹中含有__init__.py文件。settings.py当做暴露给用户的 ,global_settings.py当做全局的
# 与conf1,conf2同一级中,新建一个启动文件start.py
# dir(obj) 作用是列出obj中所有的名字,保存成列表
# settings.py文件中
NAME = '我是暴露给用户的自定义配置'
# global_settings.py文件中
NAME = '我是项目默认的配置文件'
# conf2文件夹中的__init__.py文件中
import importlib
from conf2 import global_settings
import os
class Settings(object):
def __init__(self):
# 先将全局中的global_settings.py文件中的配置内容加载进来
for name in dir(global_settings):
if name.isupper():
setattr(self,name,getattr(global_settings,name))
# 获取暴露给用户的配置文件字符串路径
module_path = os.environ.get('xxx')
md = importlib.import_module(module_path) # md = settings
# 再将局部中的settings文件中的配置内容加载进来,有和全局配置相同的就覆盖掉全局的
for name in dir(md):
if name.isupper():
k = name
v = getattr(md,name)
setattr(self,k,v)
settings = Settings()
# start.py文件中
import os
import sys
BASE_DIR = os.path.dirname(__file__)
sys.path.append(BASE_DIR) # pycharm会自动将根目录添加到环境变量,但是其他开发环境可能没有这个功能,所以为了更加安全完善。这里要写这样一句话,把根目录添加到环境变量
if __name__ == '__main__':
# os.environ.setdefault('xxx','conf1.settings') # 等价于下一行
os.environ['xxx'] = 'conf1.settings' # environ 就是一个大字典
from conf2 import settings
print(settings.NAME)
三、django的auth
模块
- 在使用
auth
模块的时候,要用就用全套(即不要一会用普通的方法来操作表,一会又用auth模块的方法来操作。类似面向对象中,子类重用父类的方法时,super().__init__(要重用的属性)
和父类.__init__(self,要重用的属性)
这两个也不能混用)
1. 使用终端如何创建超级用户:
python manage.py createsuperuser
2. auth
模块常用方法
(1)创建用户
from django.contrib.auth.models import User
User.objects.create(username=username,password=password) # 不可用 保存的密码不是加密的
User.objects.create_user(username=username,password=password) # 创建普通用户 密码自动加密
User.objects.create_superuser(username='nick',password='123',email='123@qq.com') # 创建超级用户 必须要有邮箱数据
(2)校验用户名和密码是否正确
from django.contrib import auth
user_obj = auth.authenticate(request,username=username,password=password) # 必须传用户名和密码两个参数,缺一不可,返回布尔值
(3)保存用户登录状态
- 执行下面的代码后,会自动设置session并保存在django_session表中。还会把当前的用户对象保存在
request.user
中。
from django.contrib import auth
auth.login(request,user_obj) # 只要这句话执行了,后面在任意位置,只要你能拿到request你就可以通过request.user获取到当前登录的用户对象。且该方法有一个返回值,会返回用户对象
(4)判断当前用户是否登录
request.user.is_authenticated() # 返回布尔值
(5)校验密码
request.user.check_password(old_password)
(6)修改密码
request.user.set_password(new_password)
request.user.save() # 千万不要忘了
(7)注销
from django.contrib import auth
auth.logout(request)
(8)登录装饰器
- 装饰器的配置有两种:局部配置和全局配置。(配置的作用是如果没有登录的用户访问需要登录的功能时,会将用户的浏览器页面跳转到指定的页面。配置里面书写的就是要跳转的url)
- 局部配置:只对装饰器装饰的函数有效
- 全局配置:对所有被装饰函数都有效
- 注意:如果全局配置了,局部也配置了 ,则以局部的为准
# 1. 局部配置
# views文件中:
from django.contrib.auth.decorators import login_required
@login_required(login_url='/login/')
def index(request):
pass
# 2. 全局配置:需要在settings文件中配置一些数据
# settings配置文件中 直接配置
LOGIN_URL = '/login/'
# views文件中:
from django.contrib.auth.decorators import login_required
@login_required
def index(request):
pass
# 如果全局配置了 局部也配置 以局部的为准
3. 扩展auth_user
表字段
-
有两种方式
-
利用一对一外键字段,再创建一张表保存新的字段
-
利用继承关系,重写
auth_user
表
-
(1)方式一
class UserDetail(models.Model):
phone = models.BigIntegerField()
user = models.OneToOneField(to='User')
(2)方式二
-
注意:如果要扩展auth_user表,必须在项目第一次执行数据库迁移命令时,将下面的模型类写好。(即如果已经执行完数据库迁移命令生成auth_user表后,再想扩展表字段,方式二是无法实现的)
-
注意:下面创建模型类后,还需要去settings文件中配置(
AUTH_USER_MODEL = 'app01.Userinfo' # 应用名.表名
,这么写完之后,你新建的Userinfo
表会代替原来的auth_user
表,之前所有的auth
模块功能全都以你写的表为准)
from django.contrib.auth.models import AbstractUser
class Userinfo(AbstractUser):
phone = models.BigIntegerField() # 新增字段phone
register_time = models.DateField(auto_now_add=True) # 新增字段register_time