4- 用户注册和登陆
---恢复内容开始---
需要明白的几个知识点
前端获取csrf_token的函数
function getCookie(name) { var r = document.cookie.match("\\b" + name + "=([^;]*)\\b"); return r ? r[1] : undefined; }
前端需要向后端传送json数据,不是通过表单的方式传送,修改表单的默认行为,
$(".form-register").submit(function(e){ e.preventDefault();
原始的ajax传送json的数据:
var req_data = { mobile : mobile, sms_code : phoneCode, password : passwd } //这里需要将js对象, 转换成JSON字符串 req_json = JSON.stringify(req_data) //$.get和$.post都是ajax的简写, 没法设置json. // CSRF_token的设置, 除了body里, 还还可以通过请求头设置 // 我们可以再设置的时候, 从cookie中去读csrf_token设置到请求头中 // 因为有同源策略, 别的是访问不到我们的cookie. 我们可以取 $.ajax({ url: "/api/v1_0/users", //请求路径URL type: "post", //请求方式 data: req_json, //发送的请求数据 contentType: "application/json", //指明给后端发送的是JSON格式的数据 dataType: 'json', //指明从后端收到的数据是JSON格式的数据 headers:{ "X-CSRFToken": getCookie("csrf_token")//自定义请求头, 调用一个方法从cookie获取 }, success: function (resp) { if (resp.errno == 0) { //注册成功, 引导到首页 location.href = "/"; } else { alert(resp.errmsg); } } })
用户密码加密处理
Flask密码生成和密码验证的一种通用方法文档 https://segmentfault.com/a/1190000002393218
from werkzeug.security import generate_password_hash, check_password_hash
通过 generate_password_hash(密码)可以把密码进行加密处理
通过方法 check_password_hash(数据库中的密码,用户输入的密码)可以把用户输入的密码和数据库中加密的密码进行比较,为一致则返回真,反之,则为假。
需要在用户的模型类中添加如下的方法:
from werkzeug.security import generate_password_hash, check_password_hash @property def password(self): # 在属性的getter方法中, 禁止访问密码数据 raise AttributeError('禁止访问用户密码') @password.setter def password(self, value): # 在属性的setter方法中执行加密处理 # 直接传入value即可, 默认sha256, 并会生成随机的8位盐值 self.password_hash = generate_password_hash(value) def check_password(self, value): """检查用户密码, value 是用户填写密码""" return check_password_hash(self.password_hash, value)
获取用户的ip地址
从request中可以获取用户的ip地址
通过调用
request.remote_addr
数据库回滚
from flask import session db.session.rollback()
用户注册接口
接口文档
url : /avi/v1_0/users/
请求的方式:POST
发送数据类型:json
必传的参数:mobile,sms_code,password
返回结果
类型:json
参数:errno,errmsg
后端业务逻辑分析:
一 大步奏
接受参数 手机号,验证码,和密码
二 参数校验
总体的参数校验
校验手机号的完整性
从redis中获取短信验证码,判断有没有过期失效,和用户输入的是否一致
三 以上通过,删除验证码
四 创建用户保存注册信息
五 设置session 信息
完整代码如下:
# coding:utf8 from . import api from flask import request, jsonify, session from ihome.response_code import RET import re import logging from ihome import redis_store, db from ihome.models import User # POST /avi/v1_0/users/ # 手机号 mobile # 短信验证码 sms_code # 密码 password @api.route('/users', methods=['POST']) def register(): # 后端向前端传送的是json的数据,要求前端向后端传送的也是json数据 # 一大步奏 # 接受参数 手机号,验证码,和密码 # 总体的参数校验 # 校验手机号的完整性 # 获取前端传送来的json数据 resp_dict = request.get_json() mobile = resp_dict.get('mobile') sms_code = resp_dict.get('sms_code') password = resp_dict.get('password') # 对接受的所有数据进行判断 if not all([mobile, sms_code, password]): resp = { 'errno': RET.PARAMERR, 'errmsg': '参数不完整' } return jsonify(resp) # 对单个参数进行判断 if not re.match(r'1[34578]\d{9}', mobile): resp = { 'errno': RET.DATAERR, 'errmsg': '手机号格式错误' } return jsonify(resp) # 业务逻辑 # 从redis中获取短信验证码,判断有没有过期失效,和传过来的是否一直等 try: real_sms_code = redis_store.get('sms_code_'+mobile) except Exception as e: logging.error(e) resp = { 'errno': RET.DBERR, 'errmsg': '获取短信验证码失败' } return jsonify(resp) # 判断短信验证码是否失效 if real_sms_code is None: resp = { 'errno': RET.NODATA, 'errmsg': '短信验证码已过期' } return jsonify(resp) # 判断用户输入的短信验证码和真实的验证码是否一致 if sms_code != real_sms_code: resp = { 'errno': RET.DATAERR, 'errmsg': '短信验证码填写错误' } return jsonify(resp) # 把数据库中的验证码删除 try: redis_store.delete('sms_code_'+mobile) except Exception as e: logging.error(e) # 创建用户,保存信息 user = User(name=mobile,mobile=mobile) user.password = password try: db.session.add(user) db.session.commit() except Exception as e: # a. 记录日志 logging.error(e) # b. 回滚操作 db.session.rollback() # c. 返回错误数据 resp = { 'errno': RET.NODATA, 'errmsg': '短信验证码已过期' } return jsonify(resp) session['user_id'] = user.id session['user_name'] = mobile session['mobile'] = mobile resp = { 'errno': RET.OK, 'errmsg': '注册成功' } return jsonify(resp)
前端注册代码如下:
$(document).ready(function() { // 一进入就调用此方法, 调用生成验证码的接口 generateImageCode(); $("#mobile").focus(function(){ $("#mobile-err").hide(); }); $("#imagecode").focus(function(){ $("#image-code-err").hide(); }); $("#phonecode").focus(function(){ $("#phone-code-err").hide(); }); $("#password").focus(function(){ $("#password-err").hide(); $("#password2-err").hide(); }); $("#password2").focus(function(){ $("#password2-err").hide(); }); $(".form-register").submit(function(e){ e.preventDefault(); mobile = $("#mobile").val(); phoneCode = $("#phonecode").val(); passwd = $("#password").val(); 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; } var req_data = { mobile : mobile, sms_code : phoneCode, password : passwd } //这里需要将js对象, 转换成JSON字符串 req_json = JSON.stringify(req_data) //$.get和$.post都是ajax的简写, 没法设置json. // CSRF_token的设置, 除了body里, 还还可以通过请求头设置 // 我们可以再设置的时候, 从cookie中去读csrf_token设置到请求头中 // 因为有同源策略, 别的是访问不到我们的cookie. 我们可以取 $.ajax({ url: "/api/v1_0/users", //请求路径URL type: "post", //请求方式 data: req_json, //发送的请求数据 contentType: "application/json", //指明给后端发送的是JSON格式的数据 dataType: 'json', //指明从后端收到的数据是JSON格式的数据 headers:{ "X-CSRFToken": getCookie("csrf_token")//自定义请求头, 调用一个方法从cookie获取 }, success: function (resp) { if (resp.errno == 0) { //注册成功, 引导到首页 location.href = "/"; } else { alert(resp.errmsg); } } }) }); })
注册成功后会返回如下的页面

用户登陆接口
接口文档
url : /avi/v1_0/session/
请求的方式:POST
发送数据类型:json
必传的参数:mobile,password
返回结果
类型:json
参数:errno,errmsg
后端业务逻辑分析:
一 大步奏
接受参数 手机号,密码
二 参数校验
总体的参数校验
校验手机号的完整性
三 获取用户的ip地址
获取用户输入的错误次数
判断用户输入的次数是否超过5次
检验用户和密码是否匹配
四 以上都正确删除,redis中用户输入错误的次数
五 设置session 信息
六 返回给前端结果
完整代码如下:
@api.route('/sessions', methods=['POST']) def login(): # 一. 获取参数 resp_json = request.get_json() mobile = resp_json.get('mobile') password = resp_json.get('password') # 二. 检查完整性及有效性 if not all([mobile, password]): return jsonify(errno = RET.PARAMERR, errmsg = '参数不完整') if not re.match(r'1[34578]\d{9}', mobile): return jsonify(errno=RET.PARAMERR, errmsg='手机格式错误') # 三. 业务逻辑处理 # 1. try:判断用户的登录错误次数 # 如果用户在redis中存储的错误次数过多, 不需要在判断了, 直接返回即可 user_ip = request.remote_addr try: # 保存错误次数的key, 为access+userIP access_counts = redis_store.get('access_' + user_ip) except Exception as e: logging.error(e) return jsonify(errno=RET.DBERR, errmsg='查询数据库失败') # 如果有错误记录, 加入已经是6次登录, 而我们设置的最大次数是5. 此时直接返回(登录太频繁, 请稍后再试)即可 # 判断是否超过了最大的限制次数 # 错误次数不为空 and 错误次数超过了最大值 --> 直接返回 if access_counts is not None and int(access_counts) >= 5: return jsonify(errno=RET.REQERR, errmsg='请求已超过最大次数') # 2. try:查询数据库, 判断用户信息与密码 try: user = User.query.filter_by(mobile=mobile).first() except Exception as e: logging.error(e) return jsonify(errno=RET.DBERR, errmsg='查询用户数据失败') # 同时对用户名和密码做判断, 只要有一个错误的, 就告诉用户: 用户名或密码输入错误 # TODO(huizhubo) 没写密码判断 # pbkdf2:sha256:50000$ym86IiQY$aba9ba50157890e30a5e8d948b6a8e4c62a1ab23ae2cd540ff20992ff864ccb5 # 加密后, 两个$$之间的就是盐值, 每个记录都不一样 if user is None or not user.check_password(password): # 累加错误次数, 并设置时间 try: # incr:累加错误次数 redis_store.incr('access_' + user_ip) # expire: 第一个参数 key, 第二个参数 过期时间 redis_store.expire('access_' + user_ip, 600) except Exception as e: logging.error(e) return jsonify(errno=RET.LOGINERR, errmsg='用户名或密码输入错误') # 3. try:如果手机和密码都正确, 说明登录成功, 清除之前保存的错误次数 try: redis_store.delete('access_' + user_ip) except Exception as e: logging.error(e) # 4. 设置session session['user_id'] = user.id session['user_name'] = mobile session['mobile'] = mobile # 四. 返回值 return jsonify(errno=RET.OK, errmsg='用户登录成功')
前端登陆代码如下:
$(document).ready(function() { $("#mobile").focus(function(){ $("#mobile-err").hide(); }); $("#password").focus(function(){ $("#password-err").hide(); }); $(".form-login").submit(function(e){ e.preventDefault(); mobile = $("#mobile").val(); passwd = $("#password").val(); if (!mobile) { $("#mobile-err span").html("请填写正确的手机号!"); $("#mobile-err").show(); return; } if (!passwd) { $("#password-err span").html("请填写密码!"); $("#password-err").show(); return; } var data = { mobile: mobile, password: passwd }; // 将data转为json字符串 var jsonData = JSON.stringify(data); $.ajax({ url:"/api/v1_0/sessions", type:"post", data: jsonData, contentType: "application/json", dataType: "json", headers:{ "X-CSRFToken":getCookie("csrf_token"), }, success: function (data) { if (data.errno == 0) { // 登录成功,跳转到主页 location.href = "/"; return; } else { // 其他错误信息,在页面中展示 $("#password-err span").html(data.errmsg); $("#password-err").show(); return; } } }); }); })
登陆成功页面如下所示:

---恢复内容结束---

浙公网安备 33010602011771号