day103:MoFang:用户登录部分:客户端提交登录信息&APICloud集成防水墙验证码&保存用户登录状态
bug:修复jsonrpc修改源码以后celery无法运行的问题
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
# 源码文件中, /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
1.客户端提交登录信息
用户在前端点击登录按钮:
前端执行LoginHandle方法:做了如下几件事:
1.验证用户名和密码是否填写
2.请求后端User.login接口,将用户名和密码发送给后端
<!-- 用户点击登录按钮 --> <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>
验证码控制台: https://console.cloud.tencent.com/captcha
快速接入:https://007.qq.com/python-access.html?ADTAG=acces.start
-
新建验证应用,获取秘钥和应用ID[ 新用户可以领取一个免费的验证码套餐 ]
点击详情:
# 防水墙验证码 CAPTCHA_GATEWAY="https://ssl.captcha.qq.com/ticket/verify" CAPTCHA_APP_ID="2098385038" CAPTCHA_APP_SECRET_KEY="05wGPzUzheZXvnuXPQfMaFg**"
1.
下载地址: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.客户端展示验证码
用户在前端点击登录按钮:
前端执行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>
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.保存用户登录状态
// 保存数据到内存中 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' });
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; }
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); }, } }) }