Flask-爱家租房项目ihome-02-注册
图片验证码功能
导入验证码生成逻辑
在utils
中导入验证码逻辑,fonts
文件夹和captcha.py
文件
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# refer to `https://bitbucket.org/akorn/wheezy.captcha`
import random
import string
import os.path
# from cStringIO import StringIO py2.x
# from io import StringIO # py3.x
from io import BytesIO # py3.x
from PIL import Image
from PIL import ImageFilter
from PIL.ImageDraw import Draw
from PIL.ImageFont import truetype
class Bezier:
def __init__(self):
self.tsequence = tuple([t / 20.0 for t in range(21)])
self.beziers = {}
def pascal_row(self, n):
""" Returns n-th row of Pascal's triangle
"""
result = [1]
x, numerator = 1, n
for denominator in range(1, n // 2 + 1):
x *= numerator
x /= denominator
result.append(x)
numerator -= 1
if n & 1 == 0:
result.extend(reversed(result[:-1]))
else:
result.extend(reversed(result))
return result
def make_bezier(self, n):
""" Bezier curves:
http://en.wikipedia.org/wiki/B%C3%A9zier_curve#Generalization
"""
try:
return self.beziers[n]
except KeyError:
combinations = self.pascal_row(n - 1)
result = []
for t in self.tsequence:
tpowers = (t ** i for i in range(n))
upowers = ((1 - t) ** i for i in range(n - 1, -1, -1))
coefs = [c * a * b for c, a, b in zip(combinations,
tpowers, upowers)]
result.append(coefs)
self.beziers[n] = result
return result
class Captcha(object):
def __init__(self):
self._bezier = Bezier()
self._dir = os.path.dirname(__file__)
# self._captcha_path = os.path.join(self._dir, '..', 'static', 'captcha')
@staticmethod
def instance():
if not hasattr(Captcha, "_instance"):
Captcha._instance = Captcha()
return Captcha._instance
def initialize(self, width=200, height=75, color=None, text=None, fonts=None):
# self.image = Image.new('RGB', (width, height), (255, 255, 255))
# self._text = text if text else random.sample(string.uppercase + string.uppercase + '3456789', 4)
self._text = text if text else random.sample(string.ascii_uppercase +
string.ascii_uppercase +
'3456789', 4) # py3.x
self.fonts = fonts if fonts else \
[os.path.join(self._dir, 'fonts', font) for font in ['Arial.ttf', 'Georgia.ttf', 'actionj.ttf']]
self.width = width
self.height = height
self._color = color if color else self.random_color(0, 200, random.randint(220, 255))
@staticmethod
def random_color(start, end, opacity=None):
red = random.randint(start, end)
green = random.randint(start, end)
blue = random.randint(start, end)
if opacity is None:
return red, green, blue
return red, green, blue, opacity
# draw image
def background(self, image):
Draw(image).rectangle([(0, 0), image.size], fill=self.random_color(238, 255))
return image
@staticmethod
def smooth(image):
return image.filter(ImageFilter.SMOOTH)
def curve(self, image, width=4, number=6, color=None):
dx, height = image.size
dx /= number
path = [(dx * i, random.randint(0, height))
# for i in xrange(1, number)]
for i in range(1, number)] # py3.x
bcoefs = self._bezier.make_bezier(number - 1)
points = []
for coefs in bcoefs:
points.append(tuple(sum([coef * p for coef, p in zip(coefs, ps)])
for ps in zip(*path)))
Draw(image).line(points, fill=color if color else self._color, width=width)
return image
def noise(self, image, number=50, level=2, color=None):
width, height = image.size
dx = width / 10
width -= dx
dy = height / 10
height -= dy
draw = Draw(image)
# for i in xrange(number):
for i in range(number): # py3.x
x = int(random.uniform(dx, width))
y = int(random.uniform(dy, height))
draw.line(((x, y), (x + level, y)), fill=color if color else self._color, width=level)
return image
def text(self, image, fonts, font_sizes=None, drawings=None, squeeze_factor=0.75, color=None):
color = color if color else self._color
fonts = tuple([truetype(name, size)
for name in fonts
for size in font_sizes or (65, 70, 75)])
draw = Draw(image)
char_images = []
for c in self._text:
font = random.choice(fonts)
c_width, c_height = draw.textsize(c, font=font)
char_image = Image.new('RGB', (c_width, c_height), (0, 0, 0))
char_draw = Draw(char_image)
char_draw.text((0, 0), c, font=font, fill=color)
char_image = char_image.crop(char_image.getbbox())
for drawing in drawings:
d = getattr(self, drawing)
char_image = d(char_image)
char_images.append(char_image)
width, height = image.size
offset = int((width - sum(int(i.size[0] * squeeze_factor)
for i in char_images[:-1]) -
char_images[-1].size[0]) / 2)
for char_image in char_images:
c_width, c_height = char_image.size
mask = char_image.convert('L').point(lambda i: i * 1.97)
image.paste(char_image,
(offset, int((height - c_height) / 2)),
mask)
offset += int(c_width * squeeze_factor)
return image
# draw text
@staticmethod
def warp(image, dx_factor=0.27, dy_factor=0.21):
width, height = image.size
dx = width * dx_factor
dy = height * dy_factor
x1 = int(random.uniform(-dx, dx))
y1 = int(random.uniform(-dy, dy))
x2 = int(random.uniform(-dx, dx))
y2 = int(random.uniform(-dy, dy))
image2 = Image.new('RGB',
(width + abs(x1) + abs(x2),
height + abs(y1) + abs(y2)))
image2.paste(image, (abs(x1), abs(y1)))
width2, height2 = image2.size
return image2.transform(
(width, height), Image.QUAD,
(x1, y1,
-x1, height2 - y2,
width2 + x2, height2 + y2,
width2 - x2, -y1))
@staticmethod
def offset(image, dx_factor=0.1, dy_factor=0.2):
width, height = image.size
dx = int(random.random() * width * dx_factor)
dy = int(random.random() * height * dy_factor)
image2 = Image.new('RGB', (width + dx, height + dy))
image2.paste(image, (dx, dy))
return image2
@staticmethod
def rotate(image, angle=25):
return image.rotate(
random.uniform(-angle, angle), Image.BILINEAR, expand=1)
def captcha(self, path=None, fmt='JPEG'):
"""Create a captcha.
Args:
path: save path, default None.
fmt: image format, PNG / JPEG.
Returns:
A tuple, (name, text, StringIO.value).
For example:
('fXZJN4AFxHGoU5mIlcsdOypa', 'JGW9', '\x89PNG\r\n\x1a\n\x00\x00\x00\r...')
"""
image = Image.new('RGB', (self.width, self.height), (255, 255, 255))
image = self.background(image)
image = self.text(image, self.fonts, drawings=['warp', 'rotate', 'offset'])
image = self.curve(image)
image = self.noise(image)
image = self.smooth(image)
# name = "".join(random.sample(string.lowercase + string.uppercase + '3456789', 24))
name = "".join(random.sample(string.ascii_lowercase + string.ascii_uppercase + '3456789', 24)) # py3.x
text = "".join(self._text)
# out = StringIO()# py2.x
out = BytesIO() # py3.x
image.save(out, format=fmt)
if path:
image.save(os.path.join(path, name), fmt)
return name, text, out.getvalue()
def generate_captcha(self):
self.initialize()
return self.captcha("")
captcha = Captcha.instance()
if __name__ == '__main__':
print(captcha.generate_captcha())
编写图片验证码接口
逻辑梳理:
- 将获取图片验证码的功能做成一个独立的接口,浏览器访问url:
http://127.0.0.1:5000/api/v1.0/image_codes/<image_code_key>
即可获得一张验证码图片,其中url需要携带一个image_code_key
参数,用来标记是谁获取的图片验证码 - 浏览器访问注册页面或者点击验证码图片时,就会调用这个验证码图片接口,传入一个全局唯一值
image_code_key
。 - 接口中拿到
image_code_key
后,调用上面的图片验证码生成逻辑,获取到验证码图片image_data
和真实的string验证码text
,将键值对{image_code_key:text}
存入redis数据库中,并设置过期时间,再将image_data
返回给前端. - 前端展示返回的验证码图片给用户识别并让用户填写验证码,点击
获取验证码
按钮时,调用验证验证码是否输入成功的逻辑.
在ihome/api_1_0
目录中添加验证码模块verify_code.py
# /ihome/api_1_0/verify_codes.py
from flask import make_response, jsonify, current_app
from ihome.utils.captcha import captcha
from ihome.utils.constants import IMAGE_CODE_REDIS_EXPIRES
from ihome.utils.response_codes import RET
from . import api
from ihome import redis_connect
@api.route('/image_codes/<image_code_key>')
def get_image_code(image_code_key):
# 获取验证码
name, text, image_data = captcha.generate_captcha()
print(f'真实验证码:{text}')
# 保存验证码
try:
redis_connect.setex(image_code_key, IMAGE_CODE_REDIS_EXPIRES, text) # setex(key, 过期时间, value)=set+expire
except Exception as e:
# 保存失败, 记录日志
current_app.logger.error(e)
return jsonify(error_code=RET.DBERR, error_msg='保存图片验证码失败')
# 保存成功
resp = make_response(image_data)
resp.headers["Content-Type"] = "image/jpg"
return resp
在上述代码中导入了自定义的常量模块ihome.utils.constants
和状态码模块ihome.utils.response_codes
,比较好集中管理常量和与前端约定的返回状态码
# ihome.utils.constants.py
# 图片验证码的redis有效期, 单位:秒
IMAGE_CODE_REDIS_EXPIRES = 180
# 短信验证码的redis有效期, 单位:秒
SMS_CODE_REDIS_EXPIRES = 300
# 发送短信验证码的间隔, 单位:秒
SEND_SMS_CODE_INTERVAL = 60
# ihome.utils.response_codes
class RET:
OK = "0" # "成功"
DBERR = "4001" # "数据库查询错误"
NODATA = "4002" # "无数据"
DATAEXIST = "4003" # "数据已存在"
DATAERR = "4004" # "数据错误"
SESSIONERR = "4101" # "用户未登录"
LOGINERR = "4102" # "用户登录失败"
PARAMERR = "4103" # "参数错误"
USERERR = "4104" # "用户不存在或未激活"
ROLEERR = "4105" # "用户身份错误"
PWDERR = "4106" # "密码错误"
REQERR = "4201" # "非法请求或请求次数受限"
IPERR = "4202" # "IP受限"
THIRDERR = "4301" # "第三方系统错误"
IOERR = "4302" # "文件读写错误"
SERVERERR = "4500" # "内部错误"
UNKOWNERR = "4501" # "未知错误"
浏览器访问接口http://127.0.0.1:5000/api/v1.0/image_codes/123
测试结果
编写前端获取图片验证码逻辑
// ihome/static/js/ihome/register.js
// 进入注册页面自动调用generateImageCode(),获取验证码图片
$(document).ready(function() {
generateImageCode();
......
// 形成图片验证码的后端地址, 设置到页面中,让浏览请求验证码图片
function generateImageCode() {
// 1. 生成图片验证码编号
imageCodeId = generateUUID();
// 2. 设置图片url
var url = "/api/v1.0/image_codes/" + imageCodeId;
$(".image-code img").attr("src", url);
}
// 保存图片验证码编号
var imageCodeId = "";
// 生成全局唯一值的ID
function generateUUID() {
var d = new Date().getTime();
if(window.performance && typeof window.performance.now === "function"){
d += performance.now(); //use high-precision timer if available
}
var uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
var r = (d + Math.random()*16)%16 | 0;
d = Math.floor(d/16);
return (c=='x' ? r : (r&0x3|0x8)).toString(16);
});
return uuid;
}
短信验证码功能
使用第三方平台容联云通讯提供的短信验证码功能,这里只测试使用短信验证码的功能, 因此只需完成注册登录(无需实名认证等)即可使用其短信验证码免费测试服务, 不过免费测试服务只能给控制台中指定的三个手机号发送短信, 且只能有一个短信模板可以使用.
安装容联云短信发送模块
pip install ronglian_sms_sdk
编辑短信发送接口
编辑verify_codes.py
,添加简单的短信发送逻辑,测试是否能成功发送短信
# /ihome/api_1_0/verify_codes.py
from ronglian_sms_sdk import SmsSDK
from . import api
import random
@api.route('/sms_codes/<phone>')
def send_sms_code(phone):
# 创建sdk对象,三个参数为容联云官网账号下的ID,这里设置在了constants.py中
sdk = SmsSDK(constants.ACCID, constants.ACCTOKEN, constants.APPID)
# 短信模板编号,该模板为:....您的验证码是{1},请于{2}分钟内正确输入
tid = '1'
# 接受短信的手机号,在测试环境中这个手机号需要在容联云的测试号码中维护
mobile = phone
# 获取随机6位验证码,容联云不会生成验证码,需要我们自己生成
sms_code = '%06d' % random.randint(0, 999999)
# data参数为二元元组,第一个值为模板中的{1},第二个值为模板中的{2}
data = (sms_code, constants.SMS_CODE_REDIS_EXPIRES//60)
# 调用发送短信的方法,返回的时json字符串
resp = sdk.sendMessage(tid, mobile, data)
print(f'resp: {resp}')
return 'ok'
浏览器访问http://127.0.0.1:5000/api/v1.0/sms_codes/17621081762
可以正常接收到短信并且打印出的日志为:
Sign plaintext: 8aaf07087249953401727fa88e1e1c9d653e8d1fe37d40b589ad85948fefc3ce20200815185159
Authorization plaintext: 8aaf07087249953401727fa88e1e1c9d:20200815185159
Request url: https://app.cloopen.com:8883/2013-12-26/Accounts/8aaf07087249953401727fa88e1e1c9d/SMS/TemplateSMS?sig=0F271A07ED79DF36628DB3DDA2D08F0A
Request headers: {'Content-Type': 'application/json;charset=utf-8', 'Accept': 'application/json', 'Accept-Charset': 'UTF-8', 'Authorization': b'OGFhZjA3MDg3MjQ5OTUzNDAxNzI3ZmE4OGUxZTFjOWQ6MjAyMDA4MTUxODUxNTk='}
Request body: {"to": "17621081762", "appId": "8aaf07087249953401727fa88f011ca4", "templateId": "1", "datas": ["936497", 5]}
Response body: {"statusCode":"000000","templateSMS":{"smsMessageSid":"2e41f82f7adc42e992431cd11637f94d","dateCreated":"20200815185200"}}
resp: {"statusCode":"000000","templateSMS":{"smsMessageSid":"2e41f82f7adc42e992431cd11637f94d","dateCreated":"20200815185200"}}
完善短信接口功能
-
给url中手机号参数phone添加正则转换器
-
添加验证图片验证码的逻辑
-
添加同一手机号一分钟内不能多次发送短信逻辑
-
redis中保存短信验证码,给注册接口使用
@api.route("/sms_codes/<re(r'1[^120]\d{9}'):phone>")
def send_sms_code(phone):
# 获取url中?参数
image_code = request.args.get('image_code')
image_code_key = request.args.get('image_code_key')
# 判断参数是否有值
if not all([image_code, image_code_key]):
return jsonify(errno=RET.PARAMERR, errmsg='图片验证码参数不能为空')
# 判断该手机号是否已经注册过
try:
user = Users.query.filter_by(phone=phone).first()
except Exception as e:
current_app.logger.error(e)
return jsonify(errno=RET.DBERR, errmsg='获取用户异常')
if user:
return jsonify(errno=RET.DATAEXIST, errmsg='该手机号已注册过用户')
# 判断该手机号一分钟内是否发送过短信
try:
his_sms_code_key = f'his_sms_code_{phone}'
sms_his = redis_connect.get(his_sms_code_key)
except Exception as e:
current_app.logger.error(e)
return jsonify(errno=RET.DBERR, errmsg='redis获取短信验证码异常')
# 存在短信记录
if sms_his:
return jsonify(errno=RET.DATAEXIST, errmsg='该手机号一分钟内已发送过短信,请稍候再试')
# 获取redis中的真实图片验证码
try:
real_image_code = redis_connect.get(image_code_key)
except Exception as e:
current_app.logger.error(e)
return jsonify(errno=RET.DBERR, errmsg='读取图片验证码异常')
# 判断验证码是否存在
if not real_image_code:
return jsonify(errno=RET.NODATA, errmsg='图片验证码已失效')
# 删除redis中的图片验证码,防止同一个验证码被使用多次
try:
redis_connect.delete(image_code_key)
except Exception as e:
current_app.logger.error(e)
# 比较用户输入的验证码与真实验证码
if image_code.lower() != real_image_code.decode().lower():
# 验证码错误
return jsonify(errno=RET.DATAERR, errmsg='图片验证码错误')
# 图片验证码正确,则发送短信验证码
# 获取随机6位验证码
# sms_code = '%06d' % random.randint(0, 999999)
sms_code = f'{random.randint(0,999999):06}'
try:
sdk = SmsSDK(constants.ACCID, constants.ACCTOKEN, constants.APPID)
tid = '1'
mobile = phone
# 短信模板为:....您的验证码是{1},请于{2}分钟内正确输入
# data参数为元组,第一个值为模板中的{1},第二个值为模板中的{2}
data = (sms_code, constants.SMS_CODE_REDIS_EXPIRES//60)
# 发送短信,接受返回值
sms_resp_json = sdk.sendMessage(tid, mobile, data)
except Exception as e:
current_app.logger.error(e)
return jsonify(errno=RET.THIRDERR, errmsg='发送短信异常')
# 处理返回值
sms_resp_dict = json.loads(sms_resp_json)
sms_status = sms_resp_dict.get('statusCode')
if sms_status != '000000':
# 发送失败
return jsonify(errno=RET.THIRDERR, errmsg=sms_resp_dict.get('statusMsg'))
# 发送成功
# redis缓存发送记录,一分钟内同一个手机号不能再发送短信了
try:
redis_connect.setex(his_sms_code_key, constants.SEND_SMS_CODE_INTERVAL, sms_code)
except Exception as e:
current_app.logger.error(e)
return jsonify(errno=RET.DBERR, errmsg='redis保存短信验证码记录异常')
# 保存短信验证码
try:
sms_code_key = f'sms_code_{phone}'
redis_connect.setex(sms_code_key, constants.SMS_CODE_REDIS_EXPIRES, sms_code)
except Exception as e:
current_app.logger.error(e)
return jsonify(errno=RET.DBERR, errmsg='redis保存短信验证码异常')
return jsonify(errno=RET.OK)
编写前端短信验证码逻辑
给注册页面获取验证码
按钮添加点击事件sendSMSCode
// register.py
function sendSMSCode() {
// 点击一次之后移除点击功能,防止重复点击
$(".phonecode-a").removeAttr("onclick");
// 校验手机号和图片验证码是否填写
var mobile = $("#mobile").val();
if (!mobile) {
$("#mobile-err span").html("请填写正确的手机号!");
$("#mobile-err").show();
$(".phonecode-a").attr("onclick", "sendSMSCode();");
return;
}
var imageCode = $("#imagecode").val();
if (!imageCode) {
$("#image-code-err span").html("请填写验证码!");
$("#image-code-err").show();
$(".phonecode-a").attr("onclick", "sendSMSCode();");
return;
}
// ajax调用后台接口
$.get("/api/v1.0/sms_codes/" + mobile, {image_code:imageCode, image_code_key:imageCodeId},
function(data){
if (0 != data.errno) {
// 后台返回错误,则展示错误
$("#image-code-err span").html(data.errmsg);
$("#image-code-err").show();
if (2 == data.errno || 3 == data.errno) {
generateImageCode();
}
// 重新设置点击功能
$(".phonecode-a").attr("onclick", "sendSMSCode();");
}
else {
// 短信发送成功
var $time = $(".phonecode-a");
var duration = 60;
// 设置计时器,60秒内不允许再次点击
var intervalid = setInterval(function(){
$time.html(duration + "秒");
if(duration === 1){
clearInterval(intervalid);
$time.html('获取验证码');
$(".phonecode-a").attr("onclick", "sendSMSCode();");
}
duration = duration - 1;
}, 1000, 60);
}
}, 'json');
}
注册接口逻辑
在ihome/api_1_0
下新增用户模块文件users.py
,增加用户注册逻辑,并在蓝图中导入该视图文件
from flask import request, jsonify, current_app, session
from sqlalchemy.exc import IntegrityError
from . import api
from ihome import models, csrf, redis_connect, db
from ihome.utils.response_codes import RET
from ihome.models import Users
import re
import json
# @csrf.exempt 该装饰器为取消csrf防护
@api.route('/users', methods=['POST'])
def register():
# 获取数据
dict_data = request.get_json()
phone = dict_data.get('mobile')
sms_code = dict_data.get('phoneCode')
password = dict_data.get('password')
password2 = dict_data.get('password2')
# 校验数据
if not all([phone, sms_code, password, password2]):
return jsonify(errno=RET.PARAMERR, errmsg='参数不完整')
# 两次密码是否一致
if password2 != password:
return jsonify(errno=RET.PARAMERR, errmsg='两次密码不一致')
# 校验手机号
if not re.match(r'1[^120]\d{9}', phone):
return jsonify(errno=RET.PARAMERR, errmsg='手机号格式不正确')
# 验证短信验证码
sms_code_key = f'sms_code_{phone}'
# 获取真实短信验证码
try:
real_sms_code = redis_connect.get(sms_code_key)
except Exception as e:
current_app.logger.error(e)
return jsonify(errno=RET.DBERR, errmsg='获取redis验证码异常')
# 校验验证码
if not real_sms_code:
return jsonify(errno=RET.NODATA, errmsg='验证码不存在或已过期')
if sms_code != real_sms_code.decode():
return jsonify(errno=RET.PARAMERR, errmsg='验证码不正确')
# 创建用户
user = Users(phone=phone, password=password, name=phone)
try:
db.session.add(user)
db.session.commit()
except IntegrityError as e:
current_app.logger.error(e)
return jsonify(errno=RET.DATAEXIST, errmsg='该手机号已注册过用户')
except Exception as e:
current_app.logger.error(e)
return jsonify(errno=RET.DBERR, errmsg='创建用户异常')
# 注册则认为已经登录, 设置session,记录登录状态
session['user_id'] = user.id
session['phone'] = phone
session['name'] = phone
return jsonify(errno=RET.OK)
注:
@csrf.exempt
该装饰器可以取消视图函数的csrf防护,导入的csrf为创建app时的csrf对象csrf = CSRFProtect(app)
,这里先不取消防护- flask的
request.get_json()
可以直接获取到前端传入的json数据,返回值类型为json字符串 - 校验该手机号是否已经创建过用户可以利用Users表中phone字段的唯一性约束特性,若已注册过用户,那么再次以该手机号创建用户时,会抛出
sqlalchemy.exc.IntegrityError
异常,处理该异常即可 - 注册成功则认为已经登录, 设置session
注册的前端逻辑
由于该项目前后端使用json传输,所以不能使用表单自带的提交功能,需要我们自定义表单的提交和后续的处理,在register.js
中添加该部分逻辑
$(document).ready(function() {
generateImageCode();
......
// 为表单的提交补充自定义的函数行为,参数e为提交事件
$(".form-register").submit(function(e){
//阻止默认的提交表单行为,走下面自定义的行为
e.preventDefault();
//判断字段是否填写
var mobile = $("#mobile").val();
var phoneCode = $("#phonecode").val();
var passwd = $("#password").val();
var passwd2 = $("#password2").val();
if (!mobile) {
$("#mobile-err span").html("请填写正确的手机号!");
$("#mobile-err").show();
return;
}
if (!phoneCode) {
$("#phone-code-err span").html("请填写短信验证码!");
$("#phone-code-err").show();
return;
}
if (!passwd) {
$("#password-err span").html("请填写密码!");
$("#password-err").show();
return;
}
if (passwd != passwd2) {
$("#password2-err span").html("两次密码不一致!");
$("#password2-err").show();
return;
}
//发送ajax请求
//js对象
var postData = {
mobile: mobile,
phoneCode: phoneCode,
password: passwd,
password2: passwd2
};
//转换为json格式
var postJson = JSON.stringify(postData);
$.ajax({
url: "api/v1.0/users",
type: "post",
contentType: "application/json",
data: postJson,
dataType: "json",
headers: {'X-CSRFToken': getCookie('csrf_token')},
success: function (resp) {
if(resp.errno != '0'){
alert(resp.errmsg);
}
else{
//注册成功,跳转到首页
location.href='/index.html';
}
}
})
});
})
注:
-
在注册页面一加载就阻止默认的提交表单行为
e.preventDefault();
-
使用
JSON.stringify()
方法将js对象格式转化为json格式 -
因为需要设置请求头等比较多的信息,所以使用ajax的完整写法,而不是用
$.post
简写 -
ajax设置发送和接受的数据都是json格式的
-
由于后端添加了csrf防护,所以前端需要发送csrf_token,又因为如果在请求体中发送csrf_token的话,必须要请求体的格式为form表单格式,而我们这里是使用的json格式,所以只能在请求头中发送csrf_token,因此在请求头中添加
X-CSRFToken
属性 -
X-CSRFToken
的值就是之前设置在浏览器cookie中的csrf_token
的值,因此需要获取到cookie中的值,这里的getCookie()
方法就是用来获取cookie的,原理就是document.cookie
获取所有的cookie,再通过正则表达式提取csrf_token
的值function getCookie(name) { var r = document.cookie.match("\\b" + name + "=([^;]*)\\b"); return r ? r[1] : undefined; }
-
document.cookie.match()
的输出为一个列表,第0个位置为完整的cookie信息,第一个位置为cookie值>document.cookie.match("\\b" + 'csrf_token' + "=([^;]*)\\b"); <(2) ["csrf_token=IjE4ZmU1ZGNkNzNkODU0NDA0YTMzMDQyYmYxNzI…A3NzI4ZTM2YzAi.XztX7g.E_9RCL50tFgogPM9AtAH54KXxZU", "IjE4ZmU1ZGNkNzNkODU0NDA0YTMzMDQyYmYxNzI2NzA3NzI4ZTM2YzAi.XztX7g.E_9RCL50tFgogPM9AtAH54KXxZU", index: 0, input: "csrf_token=IjE4ZmU1ZGNkNzNkODU0NDA0YTMzMDQyYmYxNzI…A3NzI4ZTM2YzAi.XztX7g.E_9RCL50tFgogPM9AtAH54KXxZU", groups: undefined]