微信小程序-手机验证码短信登录接口(防薅方法)

前言

  • 开发的小程序手机号短信验证码登录这一个功能,入参只有手机号。

结局

  • 盗刷、 恶意刷。

解决方案

1. nginx

只允许referer是小程序来源的请求

# 你的接口
location ^~ /api/
{
    if ($http_referer !~* "^https://servicewechat.com/【你的小程序appid】/\d+/page-frame.html$") {
        return 444;
}

2. 利用小程序code来进行验证

  1. 利用小程序code,在后端静默获取openid,根据openid结合当前时间戳,生成加密串返回给后端。
  2. 前端请求短信接口时需要将openid和生成的加密串传给后端。
  3. 后端解析加密串后,判断解密后的openid是否一致、时间戳是否有效。

加密和解密过程都在后端进行,避免微信小程序被抓包反编译后能够找到加密方法。

伪代码实现逻辑

  • 前端
export default {
	mounted() {
		wx.login({
			success: function(res) {
				const {
					code
				} = res
				if (code) {
					// 后端code获取openid接口
					codeToOpenid({
						code
					}).then(data => {
						if (data) {
							const { openid, cryptoKey } = data
							that.openid = data.openid
							that.cryptoKey = data.cryptoKey
						}
					})
				} else {
					console.log('登录失败!' + res.errMsg)
				}
			}
		});
	},
	methods: {
		// 获取验证码
		getCode() {
				const checkPhone = /^1[3-9]\d{9}$/.test(this.phone)
				if (!checkPhone) {
					uni.showToast({
						icon: 'none',
						title: '请输入正确的手机号码'
					})
					return;
				}
				wx.showLoading({
					title: '正在获取验证码'
				})
				// 获取手机号验证码接口
				getPhoneCode({
					phone: this.phone,
					openid: this.openid,
					cryptoKey: this.cryptoKey,
				}).then((code) => {
						if (code) {
							uni.showToast({
							title: '验证码获取成功'
						})
					}
				})

			}
	}

}
  • 后端

const rp = require('request-promise');
const CryptoJS = require("crypto-js");

const key = 'xxxxxxxxx'
const iv = '1234567887654321'

// 加密
export function Encrypt(text) {
    return CryptoJS.AES.encrypt(text, CryptoJS.enc.Utf8.parse(key), {
        iv: CryptoJS.enc.Utf8.parse(iv),
        mode: CryptoJS.mode.CBC,
        padding: CryptoJS.pad.Pkcs7
    }).toString()
}

// 解密
export function Decrypt(text) {
    let decrypted = CryptoJS.AES.decrypt(text, CryptoJS.enc.Utf8.parse(key), {
        iv: CryptoJS.enc.Utf8.parse(iv),
        mode: CryptoJS.mode.CBC,
        padding: CryptoJS.pad.Pkcs7
    })
    return decrypted.toString(CryptoJS.enc.Utf8)
}

module.exports = {
	async getOpenIdByCode(code) {
        const secret = "xxxxxxx"
        const apppid = "xxxxxxx"
        const options = {
            method: 'GET',
            url: 'https://api.weixin.qq.com/sns/jscode2session',
            qs: {
                grant_type: 'authorization_code',
                js_code: code,
                secret,
                appid,
            }
        };
        return new Promise(async (resolve, reject) => {
            try {
                // 获取openid
                const res = await rp(options);
                const sessionData = JSON.parse(res);
                if (sessionData.openid) {
                    resolve(sessionData)
                } else {
                    throw sessionData;
                }
            } catch (e) {
                reject(e)
            }
        })

    }
	
	// 后端code获取openid接口逻辑
	async codeToOpenid() {
	        const code = this.post('code');
	        let openid;
	        try {
	        	// 通过微信接口使用code获取openid
	            const { openid } = await this.getOpenIdByCode(code);
	            // 返回成功结果
	            return this.success({
	                openid,
	                cryptoKey: Encrypt(`${Date.now()}:${openid}`) // 加密内容为  当前时间戳:openid
	            });
	
	        } catch (e) {
	        	// 返回失败结果
	            return this.fail(500, 'openid获取失败', e);
	        }
	    }
	
	// 获取手机号验证码
	async getPhoneCode() {
        const openid = this.post('openid')
        const cryptoKey = this.post('cryptoKey')
        const phone = this.post('phone')
        // 校验解密的cryptoKey是否和openid一致
        const decryptStr = Decrypt(cryptoKey)
        const [timestamp, decryptOpenid] = decryptStr.split(':')
        if(decryptOpenid !== openid) {
            return this.fail('无效凭证!')
        }
        const now = Date.now()
        // 5min有效期
        if( timestamp > now ||  (now - timestamp > 5 * 60 * 1000)) {
            return this.fail('凭证过期')
        }

        const oldCode = await this.cache(phone); // 判断是否redis中已存在该手机号的验证码
        if (oldCode) {
            return this.fail('60秒内不可重复获取')
        }
        
        let code = randomCode() // 随机生成验证码
        
         const smsSerivce = this.service('sms'); // 短信服务
         const msg = { code }
         try {
             const res = await smsSerivce.send(phone, msg) // 发送短信验证码
         } catch (e) {
             return this.fail('短信发送失败')
         }

        // 存储redis中1分钟,避免重复请求
        await this.cache(phone, code, { timeout: 60 * 1000 })
        return this.success(code) // 返回验证码给前端

    }

}

  • 7
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

温温温B

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值