小程序登录到底是怎么工作的?一次请求背后的三方信任链

"用户点一下,系统跑千里。"

------小程序登录看似简单,背后却是一套精密的身份信任链。

在微信生态中,小程序登录不是"输账号密码",而是一场三方协作的信任建立仪式:用户、你的服务器、微信平台------三者缺一不可。详解 token 过期处理、静默刷新、退出登录等关键场景,并明确每个参数的作用与安全边界。帮助 Java 开发者构建清晰、可靠、符合微信规范的登录体系。


一、为什么不能像 Web 那样直接登录?

传统 Web 登录依赖账号密码体系,但小程序运行在微信封闭环境中,不允许开发者直接获取用户密码或手机号等敏感信息。微信出于安全和隐私考虑,提供了一套标准的身份识别机制:

  • 微信负责验证"这个用户是不是真实的微信用户";
  • 你(开发者)负责决定"这个用户能做什么"。

因此,登录过程被拆解为两个阶段:

  1. 身份识别 :通过微信获取用户唯一标识(openid);
  2. 会话管理:你自己生成并维护登录状态(如 Token)。

核心原则:前端只传递临时凭证,后端才是身份仲裁者。


二、登录全流程详解

第一步:前端获取临时登录凭证 code

当用户点击"登录"按钮时,小程序调用:

javascript 复制代码
wx.login({
  success(res) {
    if (res.code) {
      // 将 code 发送给你的后端
      wx.request({
        url: 'https://api.yourdomain.com/auth/login',
        method: 'POST',
        data: { code: res.code }
      });
    }
  }
});

code 是什么?有什么用?

  • 它是微信生成的一次性、短期有效的临时票据
  • 有效期约 5 分钟 ,且只能使用一次
  • 它本身不包含任何用户信息,仅作为"请求微信帮忙查身份"的凭证。
  • 类比:就像你去银行办事,先拿一个叫号单(code),柜台(微信)看到号单才知道要为你服务。

第二步:后端用 code 向微信换取用户身份

你的 Java 后端收到 code 后,必须立即向微信接口发起请求:

java 复制代码
// 构造请求 URL
String url = "https://api.weixin.qq.com/sns/jscode2session" +
             "?appid=YOUR_APPID" +
             "&secret=YOUR_APPSECRET" +
             "&js_code=" + code +
             "&grant_type=authorization_code";

// 发起 HTTP GET 请求(使用 RestTemplate、OkHttp 等)
Map<String, Object> wxResponse = httpClient.get(url);

微信返回的 JSON 示例:

json 复制代码
{
  "openid": "oAbc123xyz...",
  "session_key": "KLMnop456qrs..."
}

appid 和 appsecret是什么?

  • appid:你的小程序唯一标识,告诉微信"你是谁"。
  • appsecret:只有你和微信知道的密钥,用于证明"你确实是这个小程序的合法后端"。

openid 是什么?

  • 用户在当前小程序下的唯一 ID。
  • 同一个用户在不同小程序中 openid 不同。
  • 可用于数据库关联用户数据(如 user.openid = openid)。
  • 但它不能直接当作登录凭证,因为它是永久不变的,一旦泄露无法撤销。

session_key 是什么?

  • 用于解密用户敏感数据 的密钥,例如:
    • 用户点击"获取手机号"后返回的加密数据;
    • 用户头像、昵称等通过 <button open-type="getUserInfo"> 获取的信息。
  • 必须安全存储在后端绝不能返回给前端
  • 有效期由微信控制,通常在用户长时间无操作后失效。

第三步:后端生成自定义登录凭证(Token)

微信给了你身份(openid)和解密钥匙(session_key),但你不能直接把它们交给前端。你需要颁发一张通行证------即你自己的登录凭证。

java 复制代码
// 1. 将 session_key 存入 Redis,以 openid 为键
redis.setex("wechat:session:" + openid, 86400, sessionKey); // 24小时过期

// 2. 生成 JWT Token
String token = Jwts.builder()
    .setSubject(openid)                    // 载荷:用户标识
    .setExpiration(new Date(System.currentTimeMillis() + 86400000)) // 24小时过期
    .signWith(SignatureAlgorithm.HS256, "your_jwt_secret")
    .compact();

// 3. 返回给前端
return new LoginResponse(token);

为什么不用 openid 直接当 Token?

  • openid 永久不变,一旦泄露,攻击者可永久冒充用户。
  • 自定义 Token 可设置过期时间、可主动注销,更安全可控。

第四步:前端存储 Token 并用于后续请求

javascript 复制代码
// 登录成功后
wx.setStorageSync('ACCESS_TOKEN', token);

// 封装带认证的请求
function authRequest(url) {
  const token = wx.getStorageSync('ACCESS_TOKEN');
  return wx.request({
    url,
    header: { Authorization: 'Bearer ' + token }
  });
}

此后,所有需要身份的 API 请求都会自动携带 Token。


三、登录状态如何维持?

场景:用户第二天打开小程序,还能自动登录吗?

可以!只要 Token 未过期。

  • 小程序启动时检查本地是否有 ACCESS_TOKEN
  • 有则直接用于 API 请求;
  • 后端验证 Token 有效 → 返回数据 → 用户无感登录。

Token 过期了怎么办?

方案一:被动刷新

  • 后端返回 401 Unauthorized
  • 前端捕获后,重新调用 wx.login() 流程。

方案二:主动静默刷新

利用 wx.checkSession() 判断微信原始会话是否仍有效:

javascript 复制代码
wx.checkSession({
  success: () => {
    // 微信会话有效 → 可静默获取新 code 换新 Token
    wx.login({ success: ({ code }) => refreshToken(code) });
  },
  fail: () => {
    // 微信会话已失效 → 必须用户重新触发登录
    doFullLogin();
  }
});

wx.checkSession() 本质是检查 session_key 是否未过期。如果有效,说明用户近期活跃,可免打扰刷新登录态。


四、后端如何验证每次请求?

通过拦截器解析并验证 Token:

java 复制代码
// 从 Header 中提取 Token
String authHeader = request.getHeader("Authorization");
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
  throw new UnauthorizedException();
}

String token = authHeader.substring(7);

// 验证 JWT
Claims claims = Jwts.parser()
    .setSigningKey("your_jwt_secret")
    .parseClaimsJws(token)
    .getBody();

String openid = claims.getSubject();

// 可选:检查 session_key 是否仍存在(增强安全性)
if (redis.get("wechat:session:" + openid) == null) {
  throw new UnauthorizedException(); // 用户已退出或会话过期
}

// 将 openid 放入上下文,供业务逻辑使用
UserContext.setCurrentUser(openid);

这样,你的业务接口就能安全地知道"当前是谁在操作"。


五、如何实现"退出登录"?

很简单:

1.让 Token 失效。

javascript 复制代码
// 前端清除本地 Token
wx.removeStorageSync('ACCESS_TOKEN');

2.销毁当前会话

java 复制代码
@PostMapping("/auth/logout")
public void logout() {
  String openid = UserContext.getCurrentUser();
  redis.delete("wechat:session:" + openid); // 删除 session_key
}

即使不销毁session_key,由于 Token 本身有过期时间,风险也是可控的。


六、总结:参数与职责一览

参数 产生方 用途 是否可暴露
code 微信 → 前端 一次性票据,用于换取用户身份 可短暂传输,但不可存储
appid 开发者配置 标识小程序身份 可公开(前端可见)
appsecret 开发者配置 证明后端合法性 绝对保密
openid 微信 → 后端 用户在本小程序的唯一 ID 可用于后端关联数据,不可直接当 Token
session_key 微信 → 后端 解密用户敏感数据 必须保密,仅后端使用
自定义 Token 后端 → 前端 维持登录状态 可传输,但需 HTTPS 保护

七、总结:

微信只负责"你是谁",你负责"你能做什么"。

通过这套机制,你既遵守了微信的安全规范,又构建了灵活、可控、可审计的用户会话体系。登录不再是黑盒,而是一条清晰、安全、可维护的信任链。

参考文章:

相关推荐
敲敲了个代码3 小时前
从硬编码到 Schema 推断:前端表单开发的工程化转型
前端·javascript·vue.js·学习·面试·职场和发展·前端框架
WanderInk4 小时前
刷新后点赞全变 0?别急着怪 Redis,这八成是 Long 被 JavaScript 偷偷“改号”了(一次线上复盘)
后端
dly_blog5 小时前
Vue 响应式陷阱与解决方案(第19节)
前端·javascript·vue.js
吴佳浩5 小时前
Python入门指南(七) - YOLO检测API进阶实战
人工智能·后端·python
消失的旧时光-19435 小时前
401 自动刷新 Token 的完整架构设计(Dio 实战版)
开发语言·前端·javascript
console.log('npc')5 小时前
Table,vue3在父组件调用子组件columns列的方法展示弹窗文件预览效果
前端·javascript·vue.js
廋到被风吹走5 小时前
【Spring】常用注解分类整理
java·后端·spring
用户47949283569155 小时前
React Hooks 的“天条”:为啥绝对不能写在 if 语句里?
前端·react.js
我命由我123455 小时前
SVG - SVG 引入(SVG 概述、SVG 基本使用、SVG 使用 CSS、SVG 使用 JavaScript、SVG 实例实操)
开发语言·前端·javascript·css·学习·ecmascript·学习方法