小程序登录是每个项目的必经之路,也是安全事故的高发区。本文按"登录流程 → 会话设计 → 接口防护 → 敏感数据"四层拆解,代码均来自我们上线运行的小程序项目。
一、code2Session:官方流程的每一步都在防什么
标准时序:
- 小程序端
wx.login()拿到临时凭证code(5 分钟有效、一次性) - code 发给你的服务端
- 服务端拿
appid + secret + code调微信code2Session接口,换取openid、session_key - 服务端生成自己的会话凭证(自定义登录态)下发给小程序
// 服务端:Node.js 示例
const { data } = await axios.get('https://api.weixin.qq.com/sns/jscode2session', {
params: {
appid: process.env.WX_APPID,
secret: process.env.WX_SECRET,
js_code: code,
grant_type: 'authorization_code',
},
});
if (data.errcode) throw new AuthError(data.errmsg);
const { openid, session_key } = data;
const user = await upsertUserByOpenid(openid); // openid 唯一定位用户
const token = issueToken(user.id, '7d'); // 下发自研 token,绝不下发 session_key
红线:session_key和secret绝不能下发到小程序端或写进前端代码。secret泄露等于任何人都能模拟你的登录。
二、会话设计:JWT 还是服务端 Session?
| 方案 | 优点 | 缺点 | 适用 |
|---|---|---|---|
| JWT(无状态) | 服务端零存储、水平扩展友好 | 无法主动吊销、续期复杂 | 纯数据展示类小程序 |
| Redis Session(有状态) | 可随时踢人、可控续期 | 多一次 Redis 查询 | 有支付、订单的业务系统(推荐) |
涉及交易的小程序一律推荐 Redis Session:token → userId 映射,7 天滑动过期,改密码/风控封号时 DEL 即可即时踢下线。
三、接口防护:四道闸门
1. 登录态校验中间件
async function auth(ctx, next) {
const token = ctx.get('X-Token');
const userId = await redis.get(`session:${token}`);
if (!userId) ctx.throw(401, '登录已过期');
ctx.state.userId = userId;
await redis.expire(`session:${token}`, 7 * 86400); // 滑动续期
await next();
}
2. 敏感操作二次确认
支付、改绑手机号等操作要求重新调 wx.login 或短信验证码,防止 token 被盗后资金操作。
3. 频控
按 userId + 接口 维度限流,例如验证码接口 60 秒 1 次、下单接口 10 秒 5 次,Redis 计数器实现:
const key = `rl:${userId}:createOrder`;
const n = await redis.incr(key);
if (n === 1) await redis.expire(key, 10);
if (n > 5) ctx.throw(429, '操作过于频繁');
4. 越权校验(水平权限)
查订单永远带 WHERE user_id = ctx.state.userId。只校验"登录了"而不校验"资源属主",是后台管理系统 API 暴露后最常见的越权漏洞。
四、手机号与敏感数据
2023 年后微信已下线 getPhoneNumber 的 session_key 解密方案,统一改为服务端 getAccessToken + phonenumber.getPhoneNumber 接口换取:
// 小程序端 button open-type="getPhoneNumber" 拿到 code
// 服务端:
const { phone_info } = await wxapi.post(
`wxa/business/getuserphonenumber?access_token=${await getAccessToken()}`,
{ code }
);
// phone_info.purePhoneNumber 即脱敏前的真实手机号
access_token 有每日获取次数上限且全局唯一,必须集中存储(Redis,2 小时有效,提前 5 分钟刷新),各业务共用,严禁每次请求都去取。
五、上线前安全自查清单
secret只存在于服务端环境变量- token 传输走 Header 而非 URL 参数(避免日志泄露)
- 所有写接口校验资源属主
- 验证码/支付回调有幂等与频控
- 生产环境关闭 debug 日志中的手机号、openid 明文输出
- 小程序后台配置 request 合法域名,杜绝 IP 直连
小结
登录安全的核心是信任边界:小程序端的一切数据都不可信,code 只是敲门砖,真正的身份判断必须在服务端完成;而会话的价值在于"可控制、可吊销、可审计"。