这部分为Flask博客的登录页面加个验证码。使用了PIL模块生成验证码图片,并通过Flask的session机制,进行验证码验证。
1、生成验证码
使用string模块:string.ascii_letters+string.digits构造了验证码字符组合。使用的PIL模块,构建了图形对象,并进行划线和高斯模糊处理。字体文件可单独保存到工程里。绘制字符串时,draw.text的前两个参数为字符的位置,可以设置为随机数,使验证码各字符的位置不固定,并且相邻字符略有重合。get_verify_code返回了图形对象和字符串。
import random import string from PIL import Image, ImageFont, ImageDraw, ImageFilter def rndColor(): '''随机颜色''' return (random.randint(32, 127), random.randint(32, 127), random.randint(32, 127)) def gene_text(): '''生成4位验证码''' return ''.join(random.sample(string.ascii_letters+string.digits, 4)) def draw_lines(draw, num, width, height): '''划线''' for num in range(num): x1 = random.randint(0, width / 2) y1 = random.randint(0, height / 2) x2 = random.randint(0, width) y2 = random.randint(height / 2, height) draw.line(((x1, y1), (x2, y2)), fill='black', width=1) def get_verify_code(): '''生成验证码图形''' code = gene_text() # 图片大小120×50 width, height = 120, 50 # 新图片对象 im = Image.new('RGB',(width, height),'white') # 字体 font = ImageFont.truetype('app/static/arial.ttf', 40) # draw对象 draw = ImageDraw.Draw(im) # 绘制字符串 for item in range(4): draw.text((5+random.randint(-3,3)+23*item, 5+random.randint(-3,3)), text=code[item], fill=rndColor(),font=font ) # 划线 draw_lines(draw, 2, width, height) # 高斯模糊 im = im.filter(ImageFilter.GaussianBlur(radius=1.5)) return im, code
2、表单类
为LoginForm增加一个verify_code字段,用来输入验证码。
class LoginForm(FlaskForm): email = StringField('Email', validators=[DataRequired(), Length(1, 64), Email()],) password = PasswordField('Password', validators=[DataRequired()]) verify_code = StringField('VerifyCode', validators=[DataRequired()]) remember_me = BooleanField('Keep me logged in') submit = SubmitField('Log In')
3、视图函数
使用io.BytesIO对象将验证码图片转化为二进制形式,直接作为响应返回前端。设置首部字段的内容格式,这样二进制的内容就能以图形形式在页面中显示。验证码字符串保存在flask.session对象中,对session的操作就像处理字典一样。程序内部使用设置中的SECRET_KEY对session数据加密后,存储在cookie中。
from io import BytesIO @auth.route('/code') def get_code(): image, code = get_verify_code() # 图片以二进制形式写入 buf = BytesIO() image.save(buf, 'jpeg') buf_str = buf.getvalue() # 把buf_str作为response返回前端,并设置首部字段 response = make_response(buf_str) response.headers['Content-Type'] = 'image/gif' # 将验证码字符串储存在session中 session['image'] = code return response
在登录的视图函数中,添加验证码验证功能。注意一般验证码是不区分大小写的,这里将输入的验证码和session中保存的验证码字符串都转换成小写后再作判断。
@auth.route('/login', methods=['GET', 'POST']) def login(): form = LoginForm() if form.validate_on_submit(): user = User.query.filter_by(email=form.email.data).first() if session.get('image').lower() != form.verify_code.data.lower(): flash('Wrong verify code.') return render_template('auth/login.html', form=form) if user is not None and user.verify_password(form.password.data): login_user(user, form.remember_me.data) return redirect(request.args.get('next') or url_for('main.index')) flash('Invalid username or password.') return render_template('auth/login.html', form=form)
4、前端
在前端中加入了验证码图形的路径,该路径指定为生成图形响应的视图函数。当点击验证码图片时,验证码会予以更新。
{{ wtf.quick_form(form) }} <img class="verify_code" src="/auth/code " onclick="this.src='/auth/code?'+ Math.random()">
调整下布置,最终登录表单会显示成这个样子:
下面是不同方式生成的验证码:
a. 无特效 | b. 增加高斯模糊 | c. 增加划线 |