day103:MoFang:用户登录部分:客户端提交登录信息&APICloud集成防水墙验证码&保存用户登录状态

目录

bug:修复jsonrpc修改源码以后celery无法运行的问题

1.客户端提交登录信息

2.在APICloud中集成防水墙验证码

3.保存用户登录状态

bug:修复jsonrpc修改源码以后celery无法运行的问题

引入message和status直接写全路径

# 源码文件中, /flask_jwt_extended/view_decorators.py 94行左右
from jwt.exceptions import ExpiredSignatureError,InvalidTokenError
from flask_jwt_extended.exceptions import InvalidHeaderError
# 引入message和status直接写全路径
from application.utils.language.message import ErrorMessage as message # ***
from application.utils.language.status import APIStatus as status # ***

def jwt_required(fn):
    @wraps(fn)
    def wrapper(*args, **kwargs):
        try:
            verify_jwt_in_request()
        except NoAuthorizationError:
            return {"errno":status.CODE_NO_AUTHORIZATION,"errmsg":message.no_authorization}
        except ExpiredSignatureError:
            return {"errno":status.CODE_SIGNATURE_EXPIRED,"errmsg":message.authorization_has_expired}
        except InvalidHeaderError:
            return {"errno":status.CODE_INVALID_AUTHORIZATION,"errmsg":message.authorization_is_invalid}
        except InvalidTokenError:
            # ***
            return {"errno": status.CODE_INVALID_AUTHORIZATION, "errmsg": message.authorization_is_invalid}
        return fn(*args, **kwargs)
    return wrapper


# 146行左右
def fresh_jwt_required(fn):
    @wraps(fn)
    def wrapper(*args, **kwargs):
        try:
            verify_fresh_jwt_in_request()
        except NoAuthorizationError:
            return {"errno":status.CODE_NO_AUTHORIZATION,"errmsg":message.no_authorization}
        except ExpiredSignatureError:
            return {"errno":status.CODE_SIGNATURE_EXPIRED,"errmsg":message.authorization_has_expired}
        except InvalidHeaderError:
            return {"errno": status.CODE_INVALID_AUTHORIZATION, "errmsg": message.authorization_is_invalid}
        except InvalidTokenError:
            return {"errno": status.CODE_INVALID_AUTHORIZATION, "errmsg": message.authorization_is_invalid}
        return fn(*args, **kwargs)
    return wrapper
修改 view_decorators.py 源码

1.客户端提交登录信息

用户在前端点击登录按钮:

前端执行LoginHandle方法:做了如下几件事:

  1.验证用户名和密码是否填写

  2.请求后端User.login接口,将用户名和密码发送给后端

html/login.html,代码:

<!-- 用户点击登录按钮 -->
<div class="form-item">
    <img class="commit" @click="loginHandle" src="../static/images/commit.png">
</div>

    <script>
    apiready = function(){
        init();
        new Vue({
            el:"#app",
            data(){
                return {
                    account: "",
                    password: "",
                    remember: false, // 是否记住登陆
                       music_play:true,
                    prev:{name:"",url:"",params:{}},
                    current:{name:"login",url:"login.html",params:{}},
                }
            },
            methods:{
                  loginHandle(){
                        // 登陆处理
                        this.game.play_music('../static/mp3/btn1.mp3');
                        // 验证密码和账户是否填写
                        if(this.account.length<1 || this.password.length < 1){
                            api.alert({
                                title: '警告',
                                msg: '账户或密码不能为空!',
                            });
                            return;
                        }
                        // 发送登陆信息
                        this.axios.post('',{
                            "jsonrpc": "2.0",
                            "id": this.uuid(),
                            "method": "User.login",
                            "params": {
                                "account": this.account,
                                "password": this.password
                            }
                        }).then(response=>{
                            // 获取服务端数据
                            this.game.print(response.data);
                        }).catch(error=>{
                            if(error.response){
                                // 服务端返回错误
                                this.game.print(error.response.data.errmsg);
                            }else{
                                // 本地代码出现错误
                                this.game.print(error);
                            }
                        })
                    },
                  goto_register(){
            this.game.goGroup("user",1);
          },
            }
        })
    }
    </script>
</body>
</html>

2.在APICloud中集成防水墙验证码

使用微信扫码登录腾讯云控制台,然后根据官方文档,把验证码集成到项目中

官网: https://007.qq.com

验证码控制台: https://console.cloud.tencent.com/captcha

快速接入:https://007.qq.com/python-access.html?ADTAG=acces.start

  1. 访问验证码控制台: https://console.cloud.tencent.com/captcha

  2. 新建验证应用,获取秘钥和应用ID[ 新用户可以领取一个免费的验证码套餐 ]

 

点击详情:

把验证码应用的ID和秘钥保存到application/settings/dev.py配置文件中.

# 防水墙验证码
CAPTCHA_GATEWAY="https://ssl.captcha.qq.com/ticket/verify"
CAPTCHA_APP_ID="2098385038"
CAPTCHA_APP_SECRET_KEY="05wGPzUzheZXvnuXPQfMaFg**"

1.前端配置防水墙相关信息

把防水墙的前端核心js文件在客户端根目录下index.html中使用script引入或者在src/main.js中通过import引入。

下载地址:https://ssl.captcha.qq.com/TCaptcha.js

在客户端项目的settings.js中添加配置app_id:

function init(){
  var game = new Game("../mp3/bg1.mp3");
  Vue.prototype.game = game;
  axios.defaults.baseURL = "http://192.168.3.229:5000/api" 
  axios.defaults.timeout = 5000; 
  axios.defaults.withCredentials = false; 
  Vue.prototype.axios = axios;
  Vue.prototype.uuid  = UUID.generate;
  Vue.prototype.settings = {
    app_id: "2044977606" // 前端配置防水墙appid
  }
}

2.客户端展示验证码

html/login.html,代码:

用户在前端点击登录按钮:

前端执行LoginHandle方法:做了如下几件事:

  1.验证用户名和密码是否填写

  2.前端向防水墙发送请求,防水墙会给前端返回一个结果res

  3..当用户操作验证通过(res.ret=0)以后,请求后端User.login接口,将用户名和密码以及ticket(res.ticket)和randstr(res.randstr)发送给后端

<div class="form-item">
    <img class="commit" @click="loginHandle" src="../static/images/commit.png">
</div>
                
    <script>
    apiready = function(){
        init();
        new Vue({
            el:"#app",
            data(){
                return {
                    account: "",
                    password: "",
                    remember: false, // 是否记住登陆
                       music_play:true,
                    prev:{name:"",url:"",params:{}},
                    current:{name:"login",url:"login.html",params:{}},
                }
            },
            methods:{
                  loginHandle(){
                        this.game.play_music('../static/mp3/btn1.mp3');
                        if(this.account.length<1 || this.password.length < 1){
                            api.alert({
                                title: '警告',
                                msg: '账户或密码不能为空!',
                            });
                            return;
                        }

                        // ***图形验证码***
                            // 1.前端向防水墙发送请求,防水墙会给前端返回一个结果res
                        var captcha1 = new TencentCaptcha(this.settings.captcha_app_id,res=>{
                               // 2.当用户操作验证通过以后, 发送登陆信息和验证校验信息
                            if(res.ret == 0){
                                this.axios.post('',{
                                        "jsonrpc": "2.0",
                                        "id": this.uuid(),
                                        "method": "User.login",
                                        "params": {
                                            "ticket": res.ticket,   // 验证通过以后的防水墙验证码返回的临时凭证,需要发送给服务端,和腾讯服务器进行校验
                                            "randstr": res.randstr, // 随机数, 为了让ticket更加随机和安全
                                            "account": this.account,
                                            "password": this.password
                                        }
                                }).then(response=>{
                                    // 获取服务端数据
                                    this.game.print(response.data);
                                }).catch(error=>{
                                    if(error.response){
                                        // 服务端返回错误
                                        this.game.print(error.response.data.errmsg);
                                    }else{
                                        // 本地代码出现错误
                                        this.game.print(error);
                                    }
                                });
                            }
                        });
                        captcha1.show(); // 显示验证码

                    },
                  goto_register(){
            this.game.goGroup("user",1);
          },
            }
        })
    }
    </script>
</body>
</html>

3.服务端校验验证码是否正确

User.login接口做了如下几件事:

  1.后端校验防水墙验证码

  2.根据前端发送的用户名和密码判断用户是否存在,若不存在返回错误信息

  3.后端验证用户密码是否正确

  4.如果1-2-3都通过,给用户生成access_token和refresh_token

  5.将id,nickname,token返回给前端

from flask_jwt_extended import create_access_token,create_refresh_token,jwt_required,get_jwt_identity,jwt_refresh_token_required
from flask import jsonify,json
from sqlalchemy import or_
from .models import User
from message import ErrorMessage as message
from status import APIStatus as status
from flask import current_app,request
from urllib.parse import urlencode
from urllib.request import urlopen
@jsonrpc.method("User.login")
def login(ticket,randstr,account,password):
    """根据用户登录信息生成token"""
    # 校验防水墙验证码
    params = {
        "aid": current_app.config.get("CAPTCHA_APP_ID"),
        "AppSecretKey": current_app.config.get("CAPTCHA_APP_SECRET_KEY"),
        "Ticket": ticket,
        "Randstr": randstr,
        "UserIP": request.remote_addr
    }
    
    # 把字典数据转换成地址栏的查询字符串格式
    # aid=xxx&AppSecretKey=xxx&xxxxx
    params = urlencode(params)
    url = current_app.config.get("CAPTCHA_GATEWAY")
    
    # 发送http的get请求
    f = urlopen("%s?%s" % (url, params))
    # https://ssl.captcha.qq.com/ticket/verify?aid=xxx&AppSecretKey=xxx&xxxxx

    content = f.read()
    res = json.loads(content)
    print(res)

    if int(res.get("response")) != 1:
        # 验证失败
        return {"errno": status.CODE_CAPTCHA_ERROR, "errmsg": message.captcaht_no_match}

    
    
    # 1. 根据账户信息和密码获取用户
    if len(account) < 1:
        return {"errno":status.CODE_NO_ACCOUNT,"errmsg":message.account_no_data}
    user = User.query.filter(or_(
        User.mobile==account,
        User.email==account,
        User.name==account
    )).first()

    if user is None:
        return {"errno": status.CODE_NO_USER,"errmsg":message.user_not_exists}

    # 验证密码
    if not user.check_password(password):
        return {"errno": status.CODE_PASSWORD_ERROR, "errmsg":message.password_error}

    # 2. 生成jwt token
    access_token = create_access_token(identity=user.id)
    refresh_token = create_refresh_token(identity=user.id)

    return {
        "errno": status.CODE_OK,
        "errmsg": message.ok,
        "id": user.id,
        "nickname": user.nickname if user.nickname else account,
        "access_token": access_token,
        "refresh_token":refresh_token
    }

3.保存用户登录状态

1.APICloud提供的数据存储

基于APICloud提供的本地存储可以有效保存数据

// 保存数据到内存中
api.setGlobalData({
    key: 'userName',
    value: 'api'
});
// 从内存中获取数据
var userName = api.getGlobalData({
    key: 'userName'
});

// 保存数据到文件中
api.setPrefs({//储存
    key: 'userName',
    value: 'api'
});
// 从文件中获取数据
api.getPrefs({//获取
    key: 'userName'
}, function(ret, err) {
    ...
});
// 注意:基于api.getPrefs获取数组时,会出现转义格式的字符

// 从文件中删除数据
api.removePrefs({//删除
    key: 'userName'
});

2.客户端保存用户登陆数据

static/js/main.js,代码:

class Game{
    save(data){
        // 保存数据到内存中
        for(var key in data){
            api.setGlobalData({
                    key: key,
                value: data[key]
                })
        }
    }
    get(data){
        // 从内存中获取数据
        if(!Array.isArray(data)){
            data = [data];
        }
        var result = {};
        for(var key of data){
            result[key] = api.getGlobalData({
                    "key": key
                });
        }
        return result;
    }
    fsave(data){
        // 保存数据到文件中
        for(var key in data){
            api.setPrefs({
                    "key": key,
                value: data[key]
                });
        }
    }
    fremove(data){
        // 从文件中删除数据
        if(!Array.isArray(data)){
            data = [data];
        }
        for(var key of data){
            api.removePrefs({
                    "key": key,
                });
        }
    }
    fget(data){
        // 从文件中获取数据
        if(!Array.isArray(data)){
            data = [data];
        }
        var value;
        var result = {}
        for(var key of data){
            result[key] = api.getPrefs({
                sync: true,
                key: key
            });
        }
        return result;
    }

html/login.html,代码:

当后端用户登录校验通过后,要将id,nickname,token等数据保存在前端

  1.如果用户登录的时候点击了记住密码,那就调用fsave永久保存

  2.如果用户登录的时候没有点击记住密码,那就调用save暂时保存

methods:{
    loginHandle(){
        this.game.play_music('../static/mp3/btn1.mp3');
        if(this.account.length<1 || this.password.length < 1){
            api.alert({
                title: '警告',
                msg: '账户或密码不能为空!',
            });
            return;
        }

        var captcha1 = new TencentCaptcha(this.settings.captcha_app_id,res=>{
            if(res.ret == 0){
                this.axios.post('',{
                    "jsonrpc": "2.0",
                    "id": this.uuid(),
                    "method": "User.login",
                    "params": {
                        "ticket": res.ticket,   
                        "randstr": res.randstr, 
                        "account": this.account,
                        "password": this.password
                    }
                }).then(response=>{
                    if(response.data.result.errno == 1000){
                        if( this.remember ){
                            // ***记住登陆***
                            this.game.fsave({
                                "id": response.data.result.id,
                                "nickname": response.data.result.nickname,
                                "access_token":response.data.result.access_token,
                                "refresh_token":response.data.result.refresh_token,
                            });
                        }else{
                            // ***不记住登陆***
                            this.game.save({
                                "id": response.data.result.id,
                                "nickname": response.data.result.nickname,
                                "access_token":response.data.result.access_token,
                                "refresh_token":response.data.result.refresh_token,
                            });
                        }
                    }
                }).catch(error=>{
                    if(error.response){
                        this.game.print(error.response.data);
                    }else{
                        this.game.print(error);
                    }
                });
            }
        });
        captcha1.show(); 

    },
        goto_register(){
            this.game.goGroup("user",1);
        },
}
})
}

 

posted @ 2020-12-08 20:45  iR-Poke  阅读(349)  评论(0编辑  收藏  举报