CSRF(Cross Site Request Forgery)跨站域请求伪造是一种网络攻击方式。
网站是通过cookie
来实现登录功能的,而cookie只存在于浏览器中,那么浏览器访问这个cookie的服务器的时候,就会自动携带cookie上去,这时候存在漏洞:如果你访问了一个病毒网站,这个网站可以在源代码中插入js代码,使用js代码给其它服务器发送请求(如银行的转账请求),那么在发送请求的时候,浏览器会自动的携带cookie发送给对应的服务器,这时服务器就不知道这个请求是伪造的,就被欺骗了,从而达到在用户不知情的情况下,给服务器发送了一个请求:比如转账。
CSRF攻击的要点就是在向服务器发送请求的时候,相应的cookie会自动发送给对应的服务器,造成服务器不知道这个请求用户发送的还是伪造的,这时候,每当用户访问表单页面的时候,我们可以在网页源代码中添加一个随机字符串叫csrf_token
,在cookie中加入一个相同值的csrf_token字符串,以后给服务器发送请求的时候,必须在body中以及cookie中携带csrf_token,服务器只有检测到cookie中的csrf_token和body中的csrf_token相同时,才认为这个请求是正常的。
from flask_wtf import CSRFProtect
CSRFProtect(app)
表单页面添加:
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" >
在AJAX中要使用csrf保护,则必须手动添加x-CSRFToken到Header中,但是csrf还是需要在模板中渲染,Flask推荐使用meta标签来渲染csrf
<meta name="csrf_token" content="{{ csrf_token() }}">
如果要使用AJAX请求,则在发送之前添加CSRF,代码如下:
var csrftoken = $('meta[name=csrf_token]').attr('content')
$.ajaxSetup({
beforeSend: function(xhr, settings) {
if (!/^(GET|HEAD|OPTIONS|TRACE)$/i.test(settings.type) && !this.crossDomain) {
xhr.setRequestHeader("X-CSRFToken", csrftoken)
}
}
})
把csrf_token放在meta标签中也更容易继承。
先在页面中添加meta标签
<meta name="csrf_token" content="{{ csrf_token() }}">
文件: yajax.js
// 对jquery的ajax的封装
'use strict';
var yajax = {
'get':function(args) {
args['method'] = 'get';
this.ajax(args);
},
'post':function(args) {
args['method'] = 'post';
this.ajax(args);
},
'ajax':function(args) {
// 设置csrftoken
this._ajaxSetup();
$.ajax(args);
},
'_ajaxSetup': function() {
$.ajaxSetup({
'beforeSend':function(xhr,settings) {
if (!/^(GET|HEAD|OPTIONS|TRACE)$/i.test(settings.type) && !this.crossDomain) {
var csrftoken = $('meta[name=csrf-token]').attr('content');
xhr.setRequestHeader("X-CSRFToken", csrftoken)
}
}
});
}
};
// 整个文档都加载完毕后才会执行这个函数
$(function () {
$('#submit').click(function (event) {
// 阻止默认的提交表单的行为
event.preventDefault();
var email = $('input[name=email]').val();
var password = $('input[name=password]').val();
yajax.post({
'url': '/login/',
'data': {
'email': email,
'password': password
},
'success': function (data) {
console.log(data);
},
'fail': function (error) {
console.log(error);
}
});
});
});
from werkzeug.local import Local
local = Local()
local.session / local.g / local.request # 这三个都是线程隔离的
在Flask中。类似request
的对象,其实是绑定到了werkzeug.local.Local
对象上。这样,在多线程中,每个对象都是隔离的。说白了就是,不同用户访问时,数据是独立的。
只要满足绑定到这个对象上的属性,在每个线程中都是隔离的,那么它就叫Thread Local
对象。
##60. app上下文和request上下文
应用上下文和请求上下文都是存放到一个LocalStack
的栈中。和应用app相关的操作就必须要用到应用上下文,比如通过current_app
获取当前的这个app
。和请求相关的操作就必须用到请求上下文,比如使用url_for
反转视图函数。
1. 在视图函数中,不用担心上下文的问题。因为视图函数要执行,那么肯定是通过访问url的方式执行的,那么这种情况下,Flask底层就已经自动的帮我们把请求上下文和应用上下文都推入到了相应的栈中。
2. 如果想要在视图函数外面执行相关的操作,比如获取当前的app(current_app),或者是反转url,那么就必须要手动推入相关的上下文:
* 手动推入app上下文:
python
# 第一种方式:
app_context = app.app_context()
app_context.push()
# 第二种方式:
with app.app_context():
print(current_app)
* 手动推入请求上下文:推入请求上下文到栈中,会首先判断有没有应用上下文,如果没有那么就会先推入应用上下文到栈中,然后再推入请求上下文到栈中:
python
with app.test_request_context():
print(url_for('my_list'))
g对象是在整个Flask应用运行期间都是可以使用的。并且他也是跟request一样,是线程隔离的。这个对象是专门用来存储开发者自己定义的一些数据,方便在整个Flask程序中都可以使用。一般使用就是,将一些经常会用到的数据绑定到上面,以后就直接从g上面取就可以了,而不需要通过传参的形式,这样更加方便。
g.username = username
在Flask中钩子函数是使用特定的装饰器装饰的函数。为什么叫做钩子函数呢,是因为钩子函数可以在正常执行的代码中,插入一段自己想要执行的代码。那么这种函数就叫做钩子函数。(hook)
1. before_first_request
:Flask项目第一次部署后会执行的钩子函数。
before_request
:请求已经到达了Flask,但是还没有进入到具体的视图函数之前调用。一般这个就是在视图函数之前,我们可以把一些后面需要用到的数据先处理好,方便视图函数使用。
context_processor
:使用这个钩子函数,必须返回一个字典。这个字典中的值在所有模版中都可以使用。这个钩子函数的函数是,如果一些在很多模版中都要用到的变量,那么就可以使用这个钩子函数来返回,而不用在每个视图函数中的render_template
中去写,这样可以让代码更加简洁和好维护。
@app.context_processor def context_processor(): return {"username":"ying"}
errorhandler
:在发生一些异常的时候,比如404错误,比如500错误。那么如果想要优雅的处理这些错误,就可以使用errorhandler
来出来。需要注意几点: flask.abort
可以手动的抛出相应的错误,比如开发者在发现参数不正确的时候可以自己手动的抛出一个400错误。
示例代码如下:@app.errorhandler(404)
def page_not_found(error):
return render_template('404.html'),404
Flask信号,安装blinker,blinker是一个第三方插件。一般用于记录日志,如用户一登陆就把相关信息写入文件。
pip install blinker
使用信号分为3步,第一是定义一个信号,第二是监听一个信号,第三是发送一个信号。以下将对这三步进行讲解:
python
def visit_func(sender,username):
print(sender)
print(username)
mysignal.connect(visit_func)
python
mysignal.send(username='zhiliao')
flashed
方法的信号。
Restful API 是用于前后端通信的一套规范,这个规范可以使得前后端开发更加轻松。
采用http或https协议
数据之间传输的格式应该都是用json格式,而不使用xml
url链接中,不能有动词,只能由名词,并且名词如果位复数,就要在后面加s
GET
:从服务器上获取资源POST
:在服务器上新创建一个资源PUT
:在服务器上更新资源(客户端提供所有改变后的数据)PATCH
在服务器上更新资源(客户端只提供需要改变的属性)DELETE
:从服务器上删除资源状态码 | 原生描述 | 描述 |
---|---|---|
200 | ok | 服务器成功响应客户端请求 |
400 | invalid request | 用户发出的请求有误,服务器没有进行新建或修改数据的操作 |
401 | unauthorized | 用户没有权限访问这个请求 |
403 | forbidden | 因为某些原因禁止访问这个请求 |
404 | not found | 用户发送请求的url不存在 |
406 | not acceptable | 用户请求不被服务器接收(比如服务器期望客户端发送某个字段,但是客户端没有发送) |
500 | internal server error | 内部服务器错误,代码错误 |
Flask-Restful需要在Flask 0.8以上的版本,在Python2.6或者Python3.3上运行。通过pip install flask-restful即可安装。
flask_restful
中导入Api
,来创建一个api
对象。Resource
,然后在这个里面,使用你想要的请求方式来定义相应的方法,比如你想要将这个视图只能采用post
请求,那么就定义一个post
方法。api.add_resource
来添加视图与url
。
示例代码如下:class LoginView(Resource):
def post(self,username=None):
return {"username":"ying"}
api.add_resource(LoginView,'/login/<username>/','/regist/')
注意事项:
* 如果你想返回json数据,那么就使用flask_restful,如果你是想渲染模版,那么还是采用之前的方式,就是app.route
的方式。
* url还是跟之前的一样,可以传递参数。也跟之前的不一样,可以指定多个url。
* endpoint是用来给url_for反转url的时候指定的。如果不写endpoint,那么将会使用视图的名字的小写来作为endpoint。
Flask-Restful插件提供了类似WTForms来验证提交的数据是否合法的包,叫做reqparse。以下是基本用法:
python
parser = reqparse.RequestParser()
parser.add_argument('username',type=str,help='请输入用户名')
args = parser.parse_args()
add_argument可以指定这个字段的名字,这个字段的数据类型等。以下将对这个方法的一些参数做详细讲解:
1. default:默认值,如果这个参数没有值,那么将使用这个参数指定的值。
2. required:是否必须。默认为False,如果设置为True,那么这个参数就必须提交上来。 3. type:这个参数的数据类型,如果指定,那么将使用指定的数据类型来强制转换提交上来的值。
3. choices:选项。提交上来的值只有满足这个选项中的值才符合验证通过,否则验证不通过。
4. help:错误信息。如果验证失败后,将会使用这个参数指定的值作为错误信息。
5. trim:是否要去掉前后的空格。
其中的type,可以使用python自带的一些数据类型,也可以使用flask_restful.inputs下的一些特定的数据类型来强制转换。比如一些常用的: 1. url:会判断这个参数的值是否是一个url,如果不是,那么就会抛出异常。 2. regex:正则表达式。 3. date:将这个字符串转换为datetime.date数据类型。如果转换不成功,则会抛出一个异常。