Django 配置CSRF(跨站点请求伪造)保护
配置
在项目的setings.py文件中 添加如下。
MIDDLEWARE = [
'django.middleware.csrf.CsrfViewMiddleware',
]
跨域请求伪造保护实现主要分为两步
第一步:
Django 的 CSRF 保护要求请求的Referer
标头与标头中存在的来源相匹配Host
。例如,这可以防止针对 的POST
请求subdomain.example.com
成功api.example.com
。如果您需要通过 HTTPS 的跨源不安全请求,继续示例,添加"subdomain.example.com"
到此列表。该设置还支持子域,因此您可以添加".example.com"
CSRF_TRUSTED_ORIGINS = [
'change.allowed.com' ,
]
Referer
即是打开当下链接的页面的域名
跨域攻击只能直接跳转或者 < form>提交,正常情况是不能伪造Referer的 服务端只要获取到Referer 校验是否合法链接即可,当然这是不能百分百保证的,Referer 的值是由浏览器提供的,虽然 HTTP 协议上有明确的要求,但是每个浏览器对于 Referer 的具体实现可能有差别,并不能保证浏览器自身没有安全漏洞。所以需要第二步来同时保证。
第二步:
通过csrf_token的方式解决,Django会产生在前端HTML页面产生一段随机的字符串即csrf_token,然后通过from表单的形式提交发送到后台,csrf中间件会验证随机字符串是否存放在了form表单当中,根据接收到的随机字符串验证这是否为一个安全的提交。
<form action="/login/" method="post">
{% csrf_token %}
<input type="text" name="user" />
<input type="text" name="pwd" />
<input type="submit" value="提交" />
</form>
如图所示 提交时将会发送csrfmiddlewaretoken 给后台
如果是异步请求,我们访问后台接口的时候应在头部信息中传入 csrf_token ,如下所示,先从cookie中获取csrftoken,然后设置请求头,调用接口
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="/login/" method="post">
<input type="text" name="user" />
<input type="text" name="pwd" />
<input type="submit" value="提交" />
<input id="btn" type="button" value="按钮">
</form>
<script src="http://libs.baidu.com/jquery/2.0.0/jquery.js"></script>
<script src="https://lf3-cdn-tos.bytecdntp.com/cdn/expire-1-M/jquery-cookie/1.4.1/jquery.cookie.js"></script>
<script>
var csrftoken = $.cookie('csrftoken');
console.log(csrftoken)
$(function () {
$('#btn').click(function () {
console.log(csrftoken)
$.ajax({
url:'/login/',
type:"POST",
data:{'username':'root','pwd':'123123'},
headers:{'X-CSRFToken':csrftoken},
success:function (arg) {
}
})
})
})
</script>
</body>
</html>
Django 只要开启CSRF(跨站点请求伪造)保护,每次请求都会在返回的cookie中带有csrftoken
如图所示
前后端分离项目时
例如一个登录页面,前端技术栈vue+elementUi,后端采用Django
要实现CSRF(跨站点请求伪造)保护 大致分为3步
第一步 获取页面的cookies中csrf的值
这里写了一个util.js 文件,他的作用是根据传入的key 获取cookies中的value,代码如下
export const getCookie = name => {
var strcookie = document.cookie// 获取cookie字符串
var arrcookie = strcookie.split('; ')// 分割
// 遍历匹配
for (var i = 0; i < arrcookie.length; i++) {
var arr = arrcookie[i].split('=')
if (arr[0] === name) {
return arr[1]
}
}
return ''
}
第二步 编写一个api.js负责发送异步请求 并将csrftoken 加入post请求的请求头
import axios from 'axios'
import Qs from 'qs'
import {getCookie} from '../util/util'
axios.defaults.baseURL = 'http://127.0.0.1:8000'
export const login = params => {
// 这里获取了Django通过cookies返回给前端的csrftoken的值
let csrftoken = getCookie('csrftoken')
let data = Qs.stringify(params)
return axios.post('/testPlatform/signin/', data, {
headers: {'Content-Type': 'application/x-www-form-urlencoded', 'X-CSRFToken': csrftoken}
})
}
如图所示 一进入登录页面 就获取到了cookies
第三步 实现登录页面点击提交发送登录请求
<template>
<div class="loginIndex" style="overflow-y:hidden">
<el-form
class="login-container"
ref="AccountForm"
:model="account"
:rules="loginRules"
label-position="left">
<h3>Login</h3>
<el-form-item prop="username">
<el-input
v-model="account.username"
type="text"
placeholder="账号">
</el-input>
</el-form-item>
<el-form-item prop="password">
<el-input v-model="account.password" type="password" placeholder="密码"></el-input>
</el-form-item>
<el-form-item>
<el-button @click.native.prevent="handleLogin" :loading="logining" type="primary">login</el-button>
<el-button
type="primary"
class="resetBtn"
@click.native.prevent="reset">
reset
</el-button>
</el-form-item>
</el-form>
</div>
</template>
<script>
import {login} from '../axios/api'
export default {
name: 'login',
data () {
return {
account: {
username: '',
password: ''
},
loginRules: {
username: [{required: true, message: '请输入账号', trigger: 'blur'}],
password: [{required: true, message: '请输入密码', trigger: 'blur'}]
},
logining: false
}
},
methods: {
handleLogin () {
this.$refs.AccountForm.validate((valid) => {
if (valid) {
this.logining = true
let loginParams = {
username: this.account.username,
password: this.account.password
}
// 调用axios登录接口
login(loginParams).then(res => {
// debugger;
this.logining = false
// 根据返回的ret判断是否成功
let { ret, user, msg, expiry } = res.data
if (ret === 0) {
// elementui中提示组件
this.$message({
type: 'success',
message: '登陆成功'
})
// 登陆成功,用户信息就保存在localStorage中
localStorage.setItem('user', user)
localStorage.setItem('expiry', expiry)
// 跳转到后台主页面
this.$router.push({ path: '/home' })
} else {
this.$message({
type: 'error',
message: msg
})
}
}).catch(err => {
console.log(err)
})
} else {
console.log('error submit!')
return false
}
})
},
reset () {
this.$refs.AccountForm.resetFields()
}
}
}
</script>
<style scoped>
body{
background: #DFE9FB;
}
.login-container {
width:350px;
margin-left:40%;
border: 1px solid #d3d3d3;
box-sizing: border-box;
padding: 10px 30px;
border-radius: 5px;
margin-top: 100px;
}
.el-button {
width:100%;
box-sizing: border-box;
margin: 10px 0;
}
</style>
此时,点击登录就会携带csrf登录成功了