1205 CSRF跨站请求与django中的auth模块使用
今日内容
今日内容:
基于配置文件的编程思想
跨站请求伪造csrf
django auth模块
django settings源码剖析
基于django settings源码实现自己项目配置文件插拔式设计
昨日回顾
cookie与session
cookie与session发展史
由于http协议是无状态的 所以我们需要发明一些能够保存用户的技术
cookie
保存在客户端浏览器上面的键值对(可以有多个)
cookie是后端服务器控制与设值的 浏览器只是奉命行事
浏览器也有权利拒绝写入cookie(一切需要登录认证的网站全都无法正常登录)
如何查看浏览器上面的cookie
application/COOKIES/信息
session
保存在服务端上的键值对(可以有多个)
django如何操作cookie
前提是需要用到HttpResponse对象
obj = HttpResponse()
obj1 = render()
obj2 = redirect()
设值cookie
obj.set_cookie('k1','v1')
获取cookie
request.COOKIES.get('k1')
设值失效时间
obj.set_cookie('k1','v1',max_age=10)
obj.set_cookie('k1','v1',expires=5)
都以秒为单位expires针对IE浏览器
删除cookie
obj.delete_cookie('k1')
基于cookie实现用户登录校验
装饰器
用户在没有登录之前如果访问了一个需要登录之后才能访问的页面
那么会先跳转到登录页面 用户输入用户名和密码并正确的情况下
再自动跳转到之前用户想要访问的页面
1.request.path_info
2.利用get请求携带参数的方法
session操作
django默认的session失效时间是14天,你也可以设置
session是保存在服务端的 默认情况下 你的django需要先执行数据库迁移命令
因为保存sesson数据的表需要先提前创建出来,如果不创建会报错(no such table:django_session)
session不单单可以放在django_session表中 也可以放在任意能够作为数据库的地方
文件
内存
缓存
...
配置文件
django的session表中的数据是针对浏览器的 同一个浏览器只会存一行数据
设置session
request.session['k1'] = 'v1'
"""
1.django内部自动调用算法 生成一个随机字符串
2.将随机字符串与设置的值保存到django_session表中(真正写入数据的操作是在django中间件里面的session相关中间件发生的)
3.将随机字符串返回给浏览器保存到cookie
sessionid:随机字符串
"""
获取session
request.session.get('k1')
"""
1.django内部会自动获取浏览器发送过来的cookie中的获取sessionid所对应的随机字符串
2.拿着随机字符串去django_session表中比对
3.如果有拿出数据放到request.session中供用户调用
"""
删除session
request.session.delete()
request.session.flush()
失效时间
request.session.set_expiry()
1.数字(不能是0) 秒数
2.数字0 浏览器关闭自动失效
3.时间格式数据 datetime
4.不写 默认参考的是全局的失效时间
基于session实现用户登录校验
django中间件
django中间件就类似于django的门户或者保安
所有的请求和响应都必须经过中间件才能够正常通过
并且在中间件中可以对请求和响应的数据进行处理
django中间件在设计到一些全局方面的功能时 作用非常大
1.网站全局的用户登录校验
2.网站全局的访问频率校验
3.网站全局的用户权限校验
...
只要是全局的功能,你都应该第一个想到中间件
django默认有七个中间件 每个中间件都有自己独立的功能 如果你想用 你直接注释掉即可
django还支持用户自定义自己的中间件 并且暴露给用户五个可以自定义的方法
前提
1.只要是设计到处理前端业务逻辑的视图函数 都需要有形参request
2.如果你想自定义中间件 你必须写一个类并且继承MiddlewareMixin
3.一旦形参中reponse你就必须返回 因为response就是后端想要返回前端的数据
五个方法
需要掌握的
1.process_request(self,request) (******)
1.请求到来的时候 会按照配置文件注册的从上往下的顺序依次经过每一个中间件里面的该方法
如果中间件中没有定义该方法 直接跳过执行下一个
2.一旦该方法返回了一个HttpResponse对象 那么请求不再往后走 而是走到同级别的process_response依次返回
2.process_response(self,request,response)
1.响应走的时候 会按照配置文件注册的从下往上的顺序依次经过每一个中间件里面的该方法
该方法默认必须返回response 如果你返回了自己的HttpResponse对象 那么用户收到的就是你的
需要了解的
3.process_view(self,request,view_name,*args,**kwargs)
1.路由匹配成功之后执行视图函数之前
4.process_exception(self,request,response)
1.视图函数中出现错误的时候自动触发
5.process_template_response(self,request,response)
1.返回的对象中含有render方法
基于配置文件的编程思想
importlib模块
利用字符串的形式导入模块
import importlib
res = 'lib.bbb'
md = importlib.import_moudle(res)
print(md)
简单代码实现
首先定义一个配置路径列表
NOTIFY_LIST = [
'notify.email.Email',
'notify.msg.Msg',
'notify.wechat.WeChat'
]
-------------------------------------------------------
在包的__init__文件中设置send_all函数
# import settings
import importlib
def send_all(countent):
# 将settings文件中的存储函数路径迭代循环
for path in settings.NOTIFY_LIST:
# 通过.切割,获取每一个文件的的路径,以及cls类名
module_path,cls_name = path.rsplit('.',maxsplit=1)
# 通过importlib方法传入路径,获取导入文件对象
md = importlib.import_module(module_path)
# getattr反射获取md文件中的相对应cls类
cls = getattr(md,cls_name)
# 实例化类,获取一个个类的对象
obj = cls()
# 调用类中的发送方法
obj.send(countent)
跨站请求伪造csrf
1. 钓鱼网站
你自己写一个跟中国银行正规网站一模一样的页面
用户输入用户名 密码 对方账户 转账金额提交
请求确实是朝中国银行的接口发送的 钱也扣了
但是对方账户变了 变成了钓鱼网站自己提前设置好的账户
如何实现
你在写form表单的时候 让用户填写的对方账户input并没有name属性
而是你自己在内部偷偷隐藏了一个具有name属性的input框
并且value值是你自己的账户 然后将该标签隐藏了
模拟该现象的产生
创建两个django项目
------------------------------------------------------
html
<p>假的</p>
// 提交的地址是真正的网站地址
<form action="http://127.0.0.1:8000/transfer/" method="post">
<p>username<input type="text" name="username"></p>
<p>
target_account:
<input type="text">
<input type="text" name="target_account" value="jason" style="display: none;">
// 通过设置两个input框,并隐藏自定义value的框,获得该白转账人的目的
</p>
<p>money:<input type="text" name="money"></p>
<input type="submit">
------------------------------------------------------
<p>这是正经的网站</p>
<form action="" method="post">
<p>username<input type="text" name="username"></p>
<p>target_account<input type="text" name="target_account"></p>
<p>money:<input type="text" name="money"></p>
<input type="submit">
</form>
2. 解决问题
django中的中间件`'django.middleware.csrf.CsrfViewMiddleware',`就是负责校验csrf的
只处理本网站发送的post请求
如何识别如何判断当前请求是否是本网站发出的
**防御CSRF攻击:**
目前防御 CSRF 攻击主要有三种策略:验证 HTTP Referer 字段;在请求地址中添加 token 并验证;在 HTTP 头中自定义属性并验证。
解决 {% csrf_token %}
在from表单中添加 {% 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>
3. ajax如何解决
方式1
- 先在页面任意的位置上书写
{% csrf_token %}
- 然后在发送ajax请求的时候 通过标签查找获取随机字符串添加到data自定义对象即可
data:{'username':'jason','csrfmiddlewaretoken':$('input[name="csrfmiddlewaretoken"]').val()},
方式2
-
先在页面任意的位置上书写
{% csrf_token %}
-
直接在data中书写
'csrfmiddlewaretoken':'{{ csrf_token }}
键值对 -
data:{'username':'jason','csrfmiddlewaretoken':'{{ csrf_token }}'},
方式3
官网提供的文件 最通用的一种方式
- 直接新建js文件拷贝代码,进行导入
script
- 不需要做任何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);
}
}
});
将下面的文件配置到你的Django项目的静态文件中,在html页面上通过导入该文件即可自动帮我们解决ajax提交post数据时校验csrf_token的问题,(导入该配置文件之前,需要先导入jQuery,因为这个配置文件内的内容是基于jQuery来实现的)
更多细节详见:Djagno官方文档中关于CSRF的内容
4. csrf相关的两个装饰器
两个装饰器,分别制定视图函数方法哪个需要校验csrf,或不需要校验的
1. 使用
导入
from django.views.decorators.csrf import csrf_exempt,csrf_protect
不校验 @csrf_exempt
@csrf_exempt # 不校验 csrf
def index(request):
return HttpResponse('index')
校验 @csrf_protect
@csrf_protect # 校验
def login(request):
return HttpResponse('login')
2. 两个装饰器在CBV上的异同
cbv
CBV就是在url中一个路径对应一个类
基于FBV的模式就是在Django的路由映射表里进行url和视图函数的关联,而基于CBV的模式则是在views.py文件中定义视图类,在视图类中视图函数,如get,post,put,delete等
在写代码中的几点注意事项:
cbv定义类的时候必须要继承view
在写url的时候必须要加as_view
类里面使用form表单提交的话只有get和post方法
类里面使用ajax发送数据的话支持定义以下很多方法
restful规范:
'get'获取数据, 'post'创建新数据, 'put'更新, 'patch'局部更新, 'delete'删除, 'head', 'options', 'trace'
----------------------------------------------------
视图函数中
from django.views import View
class IndexView(View):
# 以get形式访问会执行get函数,一般情况下获取数据
def get(self, *args, **kwargs):
return HttpResponse('666')
# 以post形式访问的话会执行post函数,一般情况下发送数据
def post(self, *args, **kwargs):
return HttpResponse('999')
@ csrf_exempt
csrf_exempt这个装饰器只能给dispatch装才能生效
# @method_decorator(csrf_exempt,name='post') # csrf_exempt不支持该方法
@method_decorator(csrf_exempt,name='dispatch') # csrf_exempt
class MyIndex(views.View):
# @method_decorator(csrf_exempt) # 可以
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') # csrf_exempt不支持该方法
def post(self,request):
return HttpResponse('OK')
@ csrf_protect
csrf_protect方式全都可以 跟你普通的装饰器装饰CBV一致
# @method_decorator(csrf_protect,name='post') # 可以
class MyIndex(views.View):
@method_decorator(csrf_protect)
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) # 可以
def post(self,request):
return HttpResponse('OK')
django里settings源码剖析
另外的配置文件settings在
from django.conf import settings 中
django有两个配置文件
一个是暴露给用户可以配置的
一个是内部全局的(用户配置了就用用户的 用户没有配就用自己的)
obj
obj.name = 'egon' # 全局
obj.name = 'jason' # 局部
- 先加载全局配置 给对象设置
- 然后在加载局部配置 再给对象设置
- 一旦有重复的项 后者覆盖前者
思考题
参看django settings源码 应用到自己的项目中
在你的项目中 实现配置文件的插拔式设计
用户配置了就用用户的 用户没有配就用自己的
django auth模块
1. 是什么
Auth模块是Django自带的用户认证模块:
我们在开发一个网站的时候,无可避免的需要设计实现网站的用户系统。此时我们需要实现包括用户注册、用户登录、用户认证、注销、修改密码等功能,这还真是个麻烦的事情呢。
Django作为一个完美主义者的终极框架,当然也会想到用户的这些痛点。它内置了强大的用户认证系统--auth,它默认使用 auth_user 表来存储用户数据。
创建超级用户(root)
python3 manage.py createsuperuser
tool中直接createsuperuser
2. 常用方法
2.1 创建用户 create_user()
导入表
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=username,password=password,email='123@qq.com') # 创建超级用户 需要邮箱数据
2.2 校验 auth.authenticate
导入auth模块
from django.contrib import auth
校验用户名和密码是否正确
user_obj = auth.authenticate(request,username=username,password=password)
# 必须传用户名和密码两个参数缺一不能
2.3 保存登录 login(request,对象)
auth.login(request,user_obj)
# 只要这句话执行了 后面在任意位置 只要你能拿到request你就可以通过request.user获取到当前登录的用户对象
2.4 判断是否登录 is_authenticated()
request.user.is_authenticated()
2.5 校验原密码是否正确 check_password(旧密码)
request.user.check_password(old_password)
2.6 修改密码 set_password(新密码)
获取
request.user.set_password(new_password)
request.user.save() # 千万不要忘了
2.7 注销 logout(request)
auth.logout(request)
# request.session.flush()
2.8 登录装饰器 @login_required
导入
from django.contrib.auth.decorators import login_required
局部配置
可以自定义未登录的报错跳转界面login_url = '/跳转连接/'
@login_required(login_url='/login/')
def index(request):
pass
全局配置
settings配置文件中 直接配置
LOGIN_URL = '/login/'
然后在视图函数添加
@login_required
def index(request):
pass
如果全局配置了 局部也配置 以局部的为准
3. 扩展auth_user表字段
方式1
利用外键一对一进行扩展
class UserDetail(models.Model):
phone = models.BigIntegerField()
user = models.OneToOneField(to='User')
方式2
利用面向对象的继承
导入AbstractUser类
from django.contrib.auth.models import AbstractUser
定义模型表类
class Userinfo(AbstractUser):
phone = models.BigIntegerField()
register_time = models.DateField(auto_now_add=True)
settings配置文件中设置
AUTH_USER_MODEL = 'app01.Userinfo' # 应用名.表名
执行数据库迁移命令
# 这么写完之后 之前所有的auth模块功能全都以你写的表为准
扩展
基于django settings配置文件是实现插拔式设计
# 执行start文件中
if __name__ == '__main__':
设置全局大字典的键值 键(随便) 值(文件的路径)
os.environ['xxx'] = 'conf.settings'
form lib.conf import settings
print(settings.NAME)
有两个settings配置文件,分别对应暴露给用户,以及隐藏的全局配置
各自有NAME属性
------------ conf.settings.__init__配置-------------------
class Settings(object):
def __init__(self):
# 获取全局配置中的所有变量名
for name in dir(global_settings):
# 筛选出需要的大写变量名
if name.isupper():
# 给self对象设置全局配置中的大写变量名 属性值
setattr(self,name,getattr(globals_settings,name))
# 获取暴露给用户的配置文件字符串路径
module_path = os.environ.get('xxx')
md = importlib.import_module(module_path)
for name in dir(md):
if name.isupper():
k = name
v = getattr(md,name)
setattr(self,k,v)
# 实例化类获得对象
settings = Settings()