Flask--CSRFToken保护(前后端分离和不分离的操作)、CORS跨域请求

Flask--CSRFToken保护(前后端分离和不分离的操作)、CORS跨域请求

  • 什么是csrf
  • csrf具体过程
  • csrf保护实现
    • 后端写保护
    • 如果是前后端不分离项目
      • 表单提交添加保护
    • 自定义错误响应和关闭保护
    • ajax提交
    • 如果是前后端分离项目
  • 同源策略
  • 使用flask-cors设置白名单

前文:
查看官方文档: http://www.pythondoc.com/flask-wtf/csrf.html#id4

什么是csrf

为什么需要 CSRF?
Flask-WTF 表单保护你免受 CSRF 威胁,你不需要有任何担心。尽管如此,如果你有不包含表单的视图,那么它们仍需要保护。

例如,由 AJAX 发送的 POST 请求,然而它背后并没有表单。在 Flask-WTF 0.9.0 以前的版本你无法获得 CSRF 令牌。这是为什么我们要实现 CSRF。

csrf具体过程

根据 csrf_token 校验原理,具体操作步骤有以下几步:

  • 1.后端生成 csrf_token 的值,在前端请求登录或者注册界面的时候将值传给前端,传给前端的方式可能有以下两种:
  • 在模板中的 From 表单中添加隐藏字段
  • 将 csrf_token 使用 cookie 的方式传给前端
  • 2.在前端发起请求时,在表单或者在请求头中带上指定的 csrf_token
  • 3.后端在接受到请求之后,取到前端发送过来的 csrf_token,与第1步生成的 csrf_token 的值进行校验
  • 4.如果校验对 csrf_token 一致,则代表是正常的请求,否则可能是伪造请求,不予通过

csrf保护实现

后端写保护

为了能够让所有的视图函数受到 CSRF 保护,你需要开启 CsrfProtect 模块:

from flask_wtf.csrf import CsrfProtect
CsrfProtect(app)

像任何其它的 Flask 扩展一样,你可以惰性加载它:

from flask_wtf.csrf import CsrfProtect
csrf = CsrfProtect()

def create_app():
    app = Flask(__name__)
    csrf.init_app(app)

Note
你需要为 CSRF 保护设置一个秘钥。通常下,同 Flask 应用的 SECRET_KEY 是一样的。

如果是前后端不分离项目

表单提交添加保护

如果模板中存在表单,你不需要做任何事情。与之前一样:

<form method="post" action="/">
    {{ form.csrf_token }}
form>

但是如果模板中没有表单,你仍然需要一个 CSRF 令牌:

<form method="post" action="/">
    <input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
form>

自定义错误响应和关闭保护

无论何时未通过 CSRF 验证,都会返回 400 响应。你可以自定义这个错误响应:

@csrf.error_handler
def csrf_error(reason):
    return render_template('csrf_error.html', reason=reason), 400

我们强烈建议你对所有视图启用 CSRF 保护。但也提供了某些视图函数不需要保护的装饰器:

@csrf.exempt
@app.route('/foo', methods=('GET', 'POST'))
def my_handler():
    # ...
    return 'ok'

默认情况下你也可以在所有的视图中禁用 CSRF 保护,通过设置 WTF_CSRF_CHECK_DEFAULT 为 False,仅仅当你需要的时候选择调用 csrf.protect()。这也能够让你在检查 CSRF 令牌前做一些预先处理:

@app.before_request
def check_csrf():
    if not is_oauth(request):
        csrf.protect()

ajax提交

不需要表单,通过 AJAX 发送 POST 请求成为可能。Flask0.9.0 版本后这个功能变成可用的。

  • 假设你已经使用了 CsrfProtect(app),你可以通过 {{ csrf_token() }} 获取 CSRF
    令牌。这个方法在每个模板中都可以使用,你并不需要担心在没有表单时如何渲染 CSRF 令牌字段。

我们推荐的方式是在 meta 标签中渲染 CSRF 令牌:

<meta name="csrf-token" content="{{ csrf_token() }}">

在 script 标签中渲染同样可行:

<script type="text/javascript">
    var csrftoken = "{{ csrf_token() }}"
script>

下面的例子采用了在 meta标签渲染的方式, 在 script 中渲染会更简单,你无须担心没有相应的例子。

无论何时你发送 AJAX POST 请求,为其添加 X-CSRFToken 头:

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)
        }
    }
})

故障排除
当你定义你的表单的时候,如果犯了这个错误 : 从 wtforms 中导入 Form 而不是从 flask.ext.wtf 中导入,CSRF 保护的大部分功能都能工作(除了 form.validate_on_submit()),但是 CSRF 保护将会发生异常。在提交表单的时候,你将会得到 Bad Request/CSRF token missing or incorrect 错误。这个错误的出现就是因为你的导入错误,而不是你的配置问题。

如果是前后端分离项目

很明显不能通过jinja2模板语言获取csrf_token的值,而且我们需要生成token值并利用请求钩子设置cookie,然后前端就能获取到cookie值:

from flask_wtf.csrf import generate_csrf

@app.after_request
def after_request(response):
    # 调用函数生成csrf token
    csrf_token = generate_csrf()
    # 设置cookie传给前端
    response.set_cookie('csrf_token', csrf_token)
    return response
  • 在前端请求时带上 csrf_token 值
  • 根据登录和注册的业务逻辑,当前采用的是 ajax 请求
  • 所以在提交登录或者注册请求时,需要在请求头中添加 X-CSRFToken 的键值对
$.ajax({
	...
	headers: {"X-CSRFToken": getCookie("csrf_token")},
	...
})

function getCookie(name) {
    var r = document.cookie.match("\\b" + name + "=([^;]*)\\b");
    return r ? r[1] : undefined;
}

同源策略

另外不要担心黑客能拿到csrf_token值,由于同源策略, 黑客是没办法获取用户浏览器中的csrf_token隐藏域,由于浏览器安全机制,黑客也没办法获取cookie中的csrf_token。
Flask--CSRFToken保护(前后端分离和不分离的操作)、CORS跨域请求_第1张图片

使用flask-cors设置白名单

跨域文件上传的时候,浏览器会自动发起一个 OPTIONS 方法到服务器,现在后台解决前端跨域解决前端跨域请求的问题

客户端发起的这个 OPTIONS 可以说是一个“预请求”,用于探测后续真正需要发起的跨域 POST 请求对于服务器来说是否是安全可接受的,因为跨域提交数据对于服务器来说可能存在很大的安全问题

请求头 Access-Control-Request-Method 用于提醒服务器在接下来的请求中将会使用什么样的方法来发起请求

Access-Control-Allow-Method 和 Access-Control-Allow-Origin 分别告知客户端,服务器允许客户端用于跨域的方法和域名
示例

下载flask_cors 包

  • pip install flask-cors

以下是Flask-CORS 的官方文档链接:http://flask-cors.readthedocs.io/en/latest/
实现

现在我们看看解决问题的实际案例

from flask_cors import CORS
app = Flask(__name__,)
# r'/*' 是通配符,让本服务器所有的URL 都允许跨域请求
CORS(app, supports_credentials=True, resources=r'/*')

你可能感兴趣的:(Flask)