返回顶部

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);
                }
            }
        })
View Code

 

 

 用户密码加密处理

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)
View Code

 

获取用户的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)
View Code

 

 前端注册代码如下:

$(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);
                }
            }
        })
    });
})
View Code

 

注册成功后会返回如下的页面

 

用户登陆接口

 接口文档

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='用户登录成功')
View Code

 

前端登陆代码如下:

$(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;
                }
            }
        });



    });
})
View Code

 

登陆成功页面如下所示:

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

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

posted @ 2017-12-05 00:55  Crazymagic  阅读(421)  评论(0)    收藏  举报