news 2026/10/2 14:25:30

Spring Boot微信扫码登录实战:OAuth2授权码流程与开放平台配置指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Spring Boot微信扫码登录实战:OAuth2授权码流程与开放平台配置指南

标题里的So Easy不是标题党,但前提是你把流程底层先捋清楚。Spring Boot 做微信登录(准确说是微信扫码登录)这件事,拆开了看就是三个HTTP调用加一个回调接口:跳转授权页、拿code换access_token、拿access_token换用户信息,最后在自己系统里签发一个登录态。之所以那么多人被卡住,不是Spring Boot代码难写,而是把三套概念搞混了:开放平台、公众平台、小程序,这三类微信登录长得像,走错一个后面全废。这篇文章我把整个链路按“准备—配置—编码—排查”的顺序给你梳理一遍,适合正在做PC网站、管理后台、SaaS系统接入微信登录的开发者参考,不论你用的是Spring Boot 2.3.x还是2.6.x,思路完全一致。

1. 先搞明白微信登录的底层流程,再动手写代码

很多新手上来就搜“Spring Boot 微信登录代码”,拿到Demo就跑,结果换了AppID就报错。我建议先花半个小时把OAuth2授权码这套机制看清楚,后面所有代码都是它的翻译。

1.1 微信登录的本质:一次OAuth2授权码交换

微信登录不是“输入密码登录”,而是一次标准的OAuth2授权码模式(authorization code)。可以想象成用户去微信这个“保险柜”里取你的系统需要的资料,但微信不会直接把保险柜钥匙给你,而是给一个一次性领取凭证code,系统拿着code去微信后台换取临时钥匙access_token,再用这把钥匙读取用户昵称、头像、openid这些资料。

实际请求链路如下:

  1. 前端/后端生成微信授权页地址,用户打开后看到二维码。
  2. 用户用微信扫码并确认授权。
  3. 微信服务器302重定向到你的回调地址,URL上携带code和state。
  4. 后端拿code加上AppSecret调用微信接口,换取access_token和openid。
  5. 后端拿access_token调用用户信息接口,拿到昵称、头像、unionid等。
  6. 在自己的系统里查库、注册或更新用户,然后签发登录态(JWT或Session)返回前端。

这套流程里,code是最关键的临时凭证,有效期只有5分钟且只能用一次。微信设计成“用code换token”而不是直接把token给前端,就是为了避免access_token暴露在浏览器端被截获。理解这一点,后面看到“invalid code”“code已被使用”这类报错就不会慌。

1.2 三个“微信登录”别搞混:开放平台、公众平台、小程序

我在不同项目里见过有人把公众号的AppID填到扫码登录里,或者拿小程序的AppSecret调网页授权接口,最后微信返回“invalid appid”这类提示。要区分清楚:

登录类型所属平台适用范围授权scope
网站扫码登录微信开放平台PC网站、管理后台、Web应用snsapi_login
公众号网页授权微信公众平台微信内置浏览器打开的H5页面snsapi_base / snsapi_userinfo
小程序登录微信公众平台(小程序)微信小程序内wx.login + code2Session

本文讲的是第一类:开放平台的网站应用扫码登录。如果你登录页是在普通浏览器里打开的,用户需要扫码,那一定走开放平台;如果你的页面是在微信里打开的,那才走公众号网页授权;如果你做的是小程序,那又是一套完全不同的接口体系。这三个AppID不能混用,尤其是很多人手里有公众号AppID,想当然拿来扫码,一定失败。

1.3 为什么是扫码登录而不是账号密码

微信没有开放“账号密码登录”这种接口给第三方网站,这是刻意的。让用户把微信号密码交给某家SaaS系统,泄露风险太大,微信不可能承担这个责任。扫码登录的本质是把身份认证这件事委托给微信这个高可信身份源,你的系统只负责接收认证结果。这样做有三个好处:用户不用再记一套新密码,你不用设计复杂的密码找回流程,用户资料(昵称头像)由微信统一维护,拿到就是官方可信的。所以不要纠结“为什么不能直接输微信号密码”,这不是技术做不到,是商业和合规上的设计。

2. 账号准备:开放平台、网站应用、回调域名一个都不能少

代码什么时候都能写,但账号和域名配置错了,代码写得再对也调不通。我把前期准备分成三步,每一步都是踩坑换来的。

2.1 创建开放平台网站应用,拿到AppID和AppSecret

首先去微信开放平台注册开发者账号,然后在“管理中心—网站应用”里创建应用,填好网站信息后就能拿到AppID和AppSecret。这里有一个很容易忽略的点:新注册的开放平台账号创建的应用,能否调用完整的用户信息接口,和账号认证状态有很大关系。如果应用未完成认证,可能只能拿到openid,拿不到昵称、头像这些字段,或者接口返回受限。不同时期的策略有差异,以你开发者后台看到的权限状态为准。我的建议是:先走完开发流程,确认业务没问题后再考虑认证;如果测试阶段就拿不到用户资料,优先检查账号认证状态,别怀疑代码。

2.2 配置授权回调域名,记住这三点

开放平台后台的“网站应用—接口信息”里,有一项“授权回调域名”。这里很多人填错,微信在回调阶段会严格校验这个值和实际回调地址是否匹配。需要记住三点:

第一,只填域名,不要带协议和路径。比如你有回调地址是https://example.com/wx/callback,后台填的是example.com,不要把https://或/wx/callback填进去。

第二,域名要和实际请求的Host完全匹配,包括端口都要一致。微信公众号网页授权对端口也有严格限制,网站应用同理,如果你本地起的是8080端口,微信服务器访问不到localhost:8080,这一点在开发调试时特别麻烦。

第三,域名需要按微信要求完成合规备案。微信对授权回调域名的备案状态有要求,个人开发者在测试阶段最好用一个已经备案好的域名,临时域名或未备案域名很容易在回调校验时被拦下来。

2.3 先把配置参数整理成一张清单

不管用什么语言写,微信登录最终依赖的配置就这几项。写代码之前先列出来,后面会反复用到:

配置项示例值说明
appIdwxf9b8...开放平台网站应用的AppID
appSecret32位字符串网站应用对应的AppSecret
redirectUrihttps://example.com/wx/callback必须是授权回调域名下的地址
scopesnsapi_login网站扫码登录固定值
jwtSecret自定义长字符串用于给用户签发登录态

这些参数建议放到Spring Boot的配置文件里,不要写死在业务代码中。原因有两个:一是测试环境和生产环境的回调域名大概率不同,二是AppSecret这种敏感信息如果你哪天不小心把代码提交到公开仓库,整个账号的安全性就没了。

3. Spring Boot工程配置:依赖、yml、参数读取

3.1 选择技术栈:Spring Boot 2.x + Hutool + JWT + Redis

本文的实现基于Spring Boot 2.6.x,在2.3.x上一样能运行,代码没有用到版本特有API。HTTP调用我用Hutool的HttpUtil,相比手写RestTemplate能省掉不少样板代码,返回的JSON也用Hutool的JSONUtil解析。用户登录态签发我用JWT,用户表存储用MyBatis-Plus,分布式场景下的state校验和code防重依赖Redis。如果你项目里没有Redis,用本地Map加分布式锁也能撑过单机测试,但生产环境最好还是按本文方案来。

pom.xml中核心依赖如下,按需裁剪即可:

<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.3</version> </dependency> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.8.25</version> </dependency> <dependency> <groupId>com.auth0</groupId> <artifactId>java-jwt</artifactId> <version>4.4.0</version> </dependency>

3.2 application.yml配置示例与说明

很多教程喜欢把微信参数一股脑扔在application.yml的wx节点下,我也一样,但会单独用一个配置类去读取,而不是在Controller里写@Value满天飞。下面的配置可以直接抄:

server: port: 8080 spring: redis: host: 127.0.0.1 port: 6379 wx: login: app-id: wx1234567890abcdef app-secret: 你的AppSecret redirect-uri: https://example.com/wx/callback state-expire-minutes: 5 jwt: secret: change-me-to-a-long-random-string expire-hours: 24

关于redirect-uri有一点必须强调:这里的值不要预先URL编码。因为代码里拼接授权地址时,我会单独对redirect_uri这个参数做一次编码,如果你在yaml里已经写成了https%3A%2F%2F...,后面再编码一次就会变成双重编码,微信回调时比对不通过,报“redirect_uri参数错误”。我在项目里见过至少三次这个坑。

3.3 参数绑定与Bean注入控制

Spring Boot读取自定义配置最常见的方式是@ConfigurationProperties,它和@Component配合后,配置项会被自动绑定到Bean属性上,你需要使用时直接注入这个Bean即可,依赖注入完全交给Spring容器控制,不需要手动new。

@Component @ConfigurationProperties(prefix = "wx.login") public class WxLoginProperties { private String appId; private String appSecret; private String redirectUri; private Integer stateExpireMinutes; // getter/setter 省略,IDE自动生成即可 }

这里有个细节,yaml里app-id这样的短横线写法,Spring Boot在绑定到appId时是能自动匹配的,不用额外加@Value("${wx.login.app-id}")。如果你在多环境部署,还可以在这个类上配合@Profile实现不同环境的配置隔离,基础的Bean注入控制逻辑就是这些,和普通业务Bean没有任何区别。

4. 核心代码落地:从授权二维码到用户自动注册

账号和配置都准备好后,编码就没多少东西了。我按一个完整闭环来写:后端生成授权URL、前端展示二维码、用户扫码、微信回调、后端换token、拉取用户信息、自动注册登录、返回登录态。

4.1 生成授权URL,并处理redirect_uri编码

微信网站应用的授权地址固定是https://open.weixin.qq.com/connect/qrconnect,核心参数有五个。注意结尾必须带#wechat_redirect,这是微信要求的固定锚点,少了它页面会显示异常。

@Service public class WxLoginService { private final WxLoginProperties wxProperties; public WxLoginService(WxLoginProperties wxProperties) { this.wxProperties = wxProperties; } public String buildAuthUrl(String state) { String encodedRedirectUri = URLEncoder.encode( wxProperties.getRedirectUri(), StandardCharsets.UTF_8 ); return "https://open.weixin.qq.com/connect/qrconnect?appid=" + wxProperties.getAppId() + "&redirect_uri=" + encodedRedirectUri + "&response_type=code" + "&scope=snsapi_login" + "&state=" + state + "#wechat_redirect"; } }

这段代码里的关键点有两个。第一,redirect_uri只编码一次,别双重编码;第二,state参数不要用固定字符串,最好每次请求生成一个随机值并保存下来,防CSRF的作用我后面专门讲。实际生成二维码时,一般由后端接口返回这个完整URL,前端拿到后用qrcode.js渲染成二维码图片,也可以直接在新窗口打开这个URL看微信官方的二维码页。

4.2 微信回调接口:接收code,换取access_token

用户扫码确认后,微信会GET请求你的回调地址,带code和state两个参数。回调接口要做三步:校验state、用code换token、用token换用户信息。第一步先看代码,重点在后面。

@RestController @RequestMapping("/wx") public class WxCallbackController { private final WxLoginService wxLoginService; private final WxUserService userService; private final TokenService tokenService; private final StateCacheService stateCacheService; @GetMapping("/callback") public void callback(@RequestParam("code") String code, @RequestParam("state") String state, HttpServletResponse response) throws IOException { if (!stateCacheService.consume(state)) { throw new BizException("state校验失败,请求可能被伪造"); } WxToken wxToken = wxLoginService.getAccessToken(code); WxUserInfo wxUserInfo = wxLoginService.getUserInfo(wxToken); SysUser user = userService.loginByWx(wxUserInfo); String token = tokenService.createToken(user.getId(), user.getOpenId()); String html = buildCallbackHtml(token); response.setContentType("text/html;charset=UTF-8"); response.getWriter().write(html); } }

换token的接口封装在WxLoginService里,调用的是微信官方地址https://api.weixin.qq.com/sns/oauth2/access_token:

public WxToken getAccessToken(String code) { Map<String, Object> params = new HashMap<>(); params.put("appid", wxProperties.getAppId()); params.put("secret", wxProperties.getAppSecret()); params.put("code", code); params.put("grant_type", "authorization_code"); String resp = HttpUtil.get("https://api.weixin.qq.com/sns/oauth2/access_token", params); JSONObject json = JSONUtil.parseObj(resp); if (json.containsKey("errcode") && json.getInt("errcode") != 0) { throw new BizException("微信换token失败:" + json.getStr("errmsg")); } return new WxToken(json.getStr("access_token"), json.getStr("openid"), json.getStr("unionid")); }

每次换token返回的access_token有效期约2小时,但我们不需要存库,因为紧接着就要用它去拿用户信息,属于“用完即弃”的临时票据。真正要长期保存的是openid、unionid和用户资料。

4.3 获取用户信息与自动注册登录逻辑

拿到access_token和openid之后,再调一次https://api.weixin.qq.com/sns/userinfo接口,把微信用户资料拉下来:

public WxUserInfo getUserInfo(WxToken wxToken) { Map<String, Object> params = new HashMap<>(); params.put("access_token", wxToken.getAccessToken()); params.put("openid", wxToken.getOpenid()); params.put("lang", "zh_CN"); String resp = HttpUtil.get("https://api.weixin.qq.com/sns/userinfo", params); JSONObject json = JSONUtil.parseObj(resp); if (json.containsKey("errcode") && json.getInt("errcode") != 0) { throw new BizException("获取微信用户信息失败:" + json.getStr("errmsg")); } WxUserInfo info = new WxUserInfo(); info.setOpenid(json.getStr("openid")); info.setUnionid(json.getStr("unionid")); info.setNickname(json.getStr("nickname")); info.setHeadimgurl(json.getStr("headimgurl")); return info; }

这里返回的openid是用户在微信下的唯一标识,同一个用户在你的同一个网站应用下,openid永远不变,所以可以直接作为用户表的唯一键。接着就是自动注册逻辑,这也是登录功能的核心:

@Transactional public SysUser loginByWx(WxUserInfo wxInfo) { SysUser user = userMapper.selectOne(new LambdaQueryWrapper<SysUser>() .eq(SysUser::getOpenId, wxInfo.getOpenid())); if (user == null) { user = new SysUser(); user.setOpenId(wxInfo.getOpenid()); user.setUnionId(wxInfo.getUnionid()); user.setNickname(wxInfo.getNickname()); user.setAvatar(wxInfo.getHeadimgurl()); userMapper.insert(user); } else { user.setNickname(wxInfo.getNickname()); user.setAvatar(wxInfo.getHeadimgurl()); userMapper.updateById(user); } return user; }

首次登录自动注册,非首次登录就更新一下头像昵称,这个逻辑没有半点多余的地方。用户表至少要有这几个字段,openid加上唯一索引,防止重复用户:

CREATE TABLE sys_user ( id BIGINT PRIMARY KEY AUTO_INCREMENT, openid VARCHAR(64) NOT NULL COMMENT '微信openid', unionid VARCHAR(64) DEFAULT NULL COMMENT '微信unionid', nickname VARCHAR(128) DEFAULT NULL COMMENT '微信昵称', avatar VARCHAR(512) DEFAULT NULL COMMENT '微信头像', create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, UNIQUE KEY uk_openid (openid) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='微信登录用户表';

4.4 前端闭环:弹窗回传token与内嵌二维码两种衔接方式

Spring Boot后端把token返回给前端有几种方式,最常用的是弹窗方式。点击“微信登录”后,前端新开一个窗口指向授权URL,用户扫码完成后微信回调后端,后端返回一个极简HTML页面,页面里通过postMessage把token传递给父窗口,然后自动关闭。上面的回调接口里buildCallbackHtml就是这个作用:

private String buildCallbackHtml(String token) { return "<!DOCTYPE html><html><head><meta charset='utf-8'></head><body>" + "<script>" + "window.opener.postMessage({type:'wx-login', token:'" + token + "'}, '*');" + "window.close();" + "</script></body></html>"; }

父窗口在页面里监听message事件,收到token后存起来,刷新用户登录状态。这个方案简单直接,很多中大型项目都在用。注意token是后端生成的随机JWT字符串,本身不含HTML特殊字符,拼进页面不会有XSS风险;但生产环境更严谨的做法是让后端先返回一个一次性票据,前端再拿票据调一次换取token的接口,避免token出现在URL或HTML中。

另一种方式是让后端只返回授权URL,前端用qrcode.js把URL画成二维码内嵌在登录页里,这种方案需要同时提供一个扫码状态查询接口,让前端轮询“用户有没有扫完”。实现上多一个接口和多一个状态存储,适合不想弹窗的产品设计。两种方式后端代码几乎一样,差别只在前端展示层。

5. 把登录态玩明白:state防CSRF、Redis缓存、JWT签发

扫码登录的微信接口部分到这里已经通了,但真正上线前还有几个安全细节绕不开,我把它们单独拎出来讲,因为每个都对应一个真实事故。

5.1 为什么必须加state参数:防CSRF实战

微信授权URL里的state参数是官方定义的“用于保持请求和回调的状态,授权请求后原样带回”,实际价值就是防CSRF。试想一个场景:攻击者构造自己的微信授权URL,诱骗一个已登录你系统的用户去扫码授权,然后攻击者带着code访问你的回调接口。如果不校验state,你的系统会拿攻击者的code去处理,最终把攻击者的微信账号绑定到受害者的本地账号上,这等于白送攻击者一个账号权限。

加了state之后,后端在生成授权URL时给每个请求分配一个随机状态值,存入Redis并设置5分钟过期;回调时拿回来的state必须在Redis里存在,并且只能被消费一次。这样攻击者伪造请求时拿不出对应的state,回调直接拒绝。生成授权URL时的完整逻辑:

@GetMapping("/wx/qrcode") public Result<WxQrCodeVO> qrcode() { String state = UUID.randomUUID().toString().replace("-", ""); stateCacheService.save(state, wxProperties.getStateExpireMinutes()); String authUrl = wxLoginService.buildAuthUrl(state); return Result.ok(new WxQrCodeVO(authUrl, state)); }

5.2 code的一次性消费与分布式环境下的缓存策略

微信的code本身只能用一次,理论上不需要我们限制重复使用,但回调接口可能因为网络重试或者用户手动刷新被触发多次,导致第二次调用微信接口时报invalid code。为了幂等,可以在Redis里对code做一层消费标记。回调处理的第一步同时校验state和code,两个都删除成功才继续:

public boolean consumeStateAndCode(String state, String code) { Boolean stateOk = stringRedisTemplate.delete("wx:login:state:" + state); Boolean codeOk = stringRedisTemplate.delete("wx:login:code:" + code); return Boolean.TRUE.equals(stateOk) && Boolean.TRUE.equals(codeOk); }

很多人问:为什么state和code要放Redis,而不是用用户Session?因为扫码登录的回调请求和前端页面的Session不一定绑定,而且在生产环境通常有多台后端实例,第一个实例生成state,回调可能落在第二个实例上,Session不共享就校验失败。Redis天然解决分布式状态共享的问题,这个方案能从单机平滑扩展到集群。如果你项目里还没引Redis,用数据库也行,但性能和时效性都会差一些。

5.3 登录态方案:JWT签发与用户识别

自动注册完成后,需要给前端一个登录凭证。这里用JWT比较省事,不依赖Session存储,后端无状态,对前后端分离和集群部署都友好。签发逻辑很简单:

@Service public class TokenService { @Value("${jwt.secret}") private String jwtSecret; @Value("${jwt.expire-hours}") private Integer expireHours; public String createToken(Long userId, String openId) { return JWT.create() .withSubject(String.valueOf(userId)) .withClaim("openId", openId) .withIssuedAt(new Date()) .withExpiresAt(new Date(System.currentTimeMillis() + expireHours * 3600 * 1000L)) .sign(Algorithm.HMAC256(jwtSecret)); } }

之后用户在访问业务接口时,前端在Header里带上Authorization: Bearer <token>,后端用一个拦截器解析JWT、校验签名和有效期,再往当前线程上下文里放入userId。这样整个系统就拥有了“微信登录过的人是谁”的能力。JWT的secret一定要足够长,并且不要硬编码在代码里,放进配置中心或者环境变量更稳妥。

5.4 用unionid做多应用用户打通

同一个微信用户在你的网站应用下的openid是固定的,但如果你们公司还有公众号、小程序,用户在不同应用下的openid互不相同,想识别“这是同一个人”就得靠unionid。前提是这些应用都绑定在同一个微信开放平台账号下,并且用户授权后微信才会返回unionid。表设计里预留了unionid字段,在loginByWx时一起落库。如果后续要做多端用户统一,就以unionid为主键合并账号,而不是用openid。这一点现在不提,等你真正遇到“网页登录的A用户和小程序里的B用户其实是同一个人”时,会感谢这个字段。

6. 常见问题排查手册与避坑指南

微信登录的报错看似千奇百怪,其实绝大多数集中在AppID、回调地址、scope这三个区域。我把高频问题整理成速查表,建议收藏一份。

6.1 高频报错速查表

现象大概率原因处理建议
报redirect_uri参数错误授权URL中redirect_uri编码错误、和后台回调域名不匹配、双重编码检查配置文件里的redirectUri是否未编码,后台只填域名且与Host完全匹配
报scope参数错误网站应用填了snsapi_userinfo,或者在公众号授权里填了snsapi_login网站扫码登录固定使用snsapi_login
小程序报appid不合法用了开放平台AppID去调小程序登录接口小程序登录必须用小程序的AppID和AppSecret
拿到code但换token失败AppSecret填错,或被调用的code已过期/已使用检查AppSecret;用户重扫一次生成新code
拿不到昵称头像开放平台应用未认证,或调用接口顺序不对查看开发者后台应用认证状态,确保scope为snsapi_login
回调请求一直收不到回调域名未备案,或域名解析到无法访问的地址用已备案域名,部署到公网可访问环境
state校验失败回调里state和生成时不一致,或Redis中已过期/已被消费检查Redis存储和过期时间,确保生成与回调同一套值
重复扫码导致invalid codecode被消费过或者前端重复提交回调接口对code做一次性消费标记,并给出友好提示

这里再单独说一个很多人忽略的点:不要用公众号的“网页授权域名”替代开放平台的“授权回调域名”,两个配置入口不同,校验逻辑也不同。每次遇到回调相关报错,先把前后台域名配置截个图对比一遍,比胡乱改代码有效得多。

6.2 开发调试时的三个实操建议

第一个建议是回调地址一定要用公网可访问的域名。扫码登录必须由微信服务器主动回调你的后端,微信访问不到localhost。开发阶段最省事的办法是把应用部署到一台有公网域名的测试服务器上,域名配好HTTPS再测,本地只做普通业务调试。

第二个建议是日志要把关键参数记录下来。回调接口建议至少打印code、state、openid、返回的errcode和errmsg,排查问题时有据可查,尤其是微信接口偶发失败时。日志格式我习惯写成wx callback|code=xxx|state=xxx|resp=xxx,一行一个请求,好检索。

第三个建议是善用微信接口调试工具和开发者文档。微信公众平台和开放平台后台都有在线接口调试能力,可以手动填参数看返回,遇到问题先在工具里复现一遍,能快速确认是微信侧参数问题还是你代码问题。工具返回和你代码返回一致,那就是代码没传对;工具返回正常而你代码报错,八成是参数拼接或编码问题。

6.3 踩坑实录:从报错到解决的三段真实经历

第一段经历发生在很多年前第一次做扫码登录时,我拿着公众号的AppID往开放平台授权URL里填,扫码后微信返回“invalid appid”,我一度怀疑是签名算法问题,查了半天才意识到两个平台的AppID体系根本不是一回事。后来我把这个教训做成了一张配置检查单:先确认平台类型,再填AppID,再配回调域名,顺序不能乱。

第二段经历是redirect_uri双重编码。当时在配置中心里已经把域名写成了URL编码形态,代码里又做了一次URLEncoder.encode,结果就是用户扫码后微信一直报redirect_uri参数错误。排查时我把授权URL复制到浏览器里手动改参数,改掉编码后立刻通了,才定位到问题。这个坑极容易踩,因为配置文件不像代码那么容易被审查。

第三段经历是关于开放平台认证的。测账号阶段发现用户信息接口返回的数据里昵称头像全是空的,代码反复审核都没问题,最后登录开发者后台一看,应用没认证,部分用户信息接口根本不给完整返回。所以如果你按本文流程走到“换token成功,拉用户信息为空”这一步,先别改代码,去后台看认证状态。

这几段经历给我最深的感受是:微信登录的代码部分真的不难,难的是对平台规则的理解。按照开放平台、网站应用、回调域名、scope、state这条线走下来,基本半小时就能跑通整个闭环。等你看到用户的微信头像真正落进数据库的那个瞬间,会觉得标题里的So Easy其实挺诚实。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/2 14:24:51

W25Q256JV的QE位陷阱:QSPI模式失效排查与状态寄存器配置指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/2 14:24:04

霍夫丁不等式:机器学习泛化分析的有限样本基石

1. 为什么一个“看起来很弱”的不等式&#xff0c;成了机器学习理论的基石&#xff1f;你第一次在《统计学习方法》或《Learning from Data》里看到霍夫丁不等式时&#xff0c;大概率会愣一下&#xff1a;设 $X_1, \dots, X_n$ 是独立随机变量&#xff0c;且对每个 $i$&#xf…

作者头像 李华
网站建设 2026/10/2 14:23:52

基于Spring Boot的小区物业管理系统源码解析与二次开发指南

简介&#xff1a;这是一份基于Spring Boot的小区物业管理系统完整源码与使用文档&#xff0c;主要面向计算机相关专业毕业设计学生及需要项目实战的开发者&#xff0c;也可用于课程设计或期末大作业。系统包含业主管理、车位管理、小区管理、管理员管理等后台功能&#xff0c;同…

作者头像 李华
网站建设 2026/10/2 14:23:47

less命令详解:大文件与日志排查的高效利器

做了十年的Linux运维&#xff0c;摸过的发行版从CentOS 6一路到Rocky 9&#xff0c;看过上千份日志、排过几万个故障&#xff0c;如果用一句话总结最离不开的命令&#xff0c;我会先投less一票。这命令看起来平平无奇&#xff0c;就是个翻页工具&#xff0c;但真把它用透的人不…

作者头像 李华
网站建设 2026/10/2 14:22:55

Vision Transformer与预训练权重:原理、选型与微调实践

上周帮朋友调一个图像分类项目&#xff0c;他问了我一句&#xff1a;“同样是20多层网络&#xff0c;为什么大家都在折腾这个什么Vision Transformer&#xff0c;不老老实实用ResNet&#xff1f;”我想了下&#xff0c;这个问题还真不是一两句话能说清。Vision Transformer&…

作者头像 李华
网站建设 2026/10/2 14:21:46

IDEA鼠标悬停不显示方法信息?全套设置与快捷键实操指南

在IDEA里看代码&#xff0c;尤其是翻框架源码、接手别人的老项目时&#xff0c;我有个习惯动作&#xff1a;鼠标一悬停&#xff0c;眼睛就习惯性地扫到方法名上方&#xff0c;想看它到底接收什么参数、返回什么类型、内部干了什么。这个动作看起来简单&#xff0c;但很多人的ID…

作者头像 李华