Java微信登录案例代码

wen java案例 7

Java微信登录案例代码:从OAuth2.0到用户体系落地的全流程详解

目录导读

  1. 微信登录原理与OAuth2.0协议核心
  2. 开发前准备:AppID、AppSecret与回调域名配置
  3. 后端核心代码:获取code、换取access_token、拉取用户信息
  4. 用户体系设计:首次登录自动注册与绑定逻辑
  5. 安全防护:状态参数state与防CSRF攻击
  6. 常见异常处理与调试技巧
  7. 完整项目代码结构展示(Spring Boot + MyBatis)
  8. 高频问答与实战踩坑记录

微信登录原理与OAuth2.0协议核心

微信开放平台提供的“网站应用微信登录”功能,本质上是基于OAuth2.0授权码模式(Authorization Code)的变种实现,很多Java开发者初次接触时,常把它跟“微信支付”或“公众号OAuth”搞混,这里我们明确一下:网站应用微信登录使用的是open.weixin.qq.com的开放平台账号体系,回调协议是https://api.weixin.qq.com/sns/oauth2/access_token,与公众号网页授权的/sns/oauth2/access_token地址一致,但参数中的appid来源不同(一个是开放平台,一个是公众号)。

Java微信登录案例代码

核心流程如下:

  • 用户点击“微信登录”按钮 → 前端重定向到微信授权页
  • 用户扫码或确认授权 → 微信服务端带着code跳回你的回调URL
  • 后端用code + appid + appsecret换取access_tokenopenid
  • access_token + openid调用/sns/userinfo接口获取昵称、头像等
  • 后端基于openid完成注册/登录逻辑,返回自定义的session或JWT

关键点code只能用一次,有效期5分钟,换取access_token时,微信会返回refresh_token,但网站应用场景下一般不需要刷新,因为access_token有效期2小时,而用户登录态我们通常用自己的token机制维护。


开发前准备:AppID、AppSecret与回调域名配置

在写代码之前,必须先完成以下三步,否则会一直报40013 invalid appidredirect_uri参数错误

  1. 注册微信开放平台账号,创建“网站应用”,获取AppIDAppSecret
  2. 填写“授权回调域”,必须是域名,不能是IP,开发测试阶段可以用natappngrok做内网穿透,把本地8080映射到公网域名。
  3. 确认redirect_uri的编码方式,微信要求回调地址在重定向时必须URL编码,且域名与后台配置完全一致(包括http/https和端口)。

示例配置:

wechat:
  app-id: wx1234567890abcdef
  app-secret: 0123456789abcdef0123456789abcdef
  redirect-uri: https://yourdomain.com/api/wechat/callback
  scope: snsapi_login  # 网站应用固定值

后端核心代码:获取code、换取access_token、拉取用户信息

我们使用Spring Boot 2.7 + HttpClient(或RestTemplate)来实现完整逻辑,前端触发登录时,后端生成授权URL:

@GetMapping("/wechat/login")
public String wechatLogin(HttpServletResponse response) throws IOException {
    String state = UUID.randomUUID().toString().replace("-", "");
    // 将state存入session或redis,用于回调时校验
    httpSession.setAttribute("wechat_state", state);
    String url = "https://open.weixin.qq.com/connect/qrconnect" +
            "?appid=" + appId +
            "&redirect_uri=" + URLEncoder.encode(redirectUri, "UTF-8") +
            "&response_type=code" +
            "&scope=snsapi_login" +
            "&state=" + state +
            "#wechat_redirect";
    response.sendRedirect(url);
}

关键细节:redirect_uri必须编码,但appidstate不编码。#wechat_redirect是微信要求的固定后缀,用于识别网页登录。

接下来是回调接口,接收codestate

@GetMapping("/api/wechat/callback")
public Result<?> callback(@RequestParam("code") String code,
                          @RequestParam("state") String state) {
    // 1. 校验state,防止CSRF
    String sessionState = (String) httpSession.getAttribute("wechat_state");
    if (!state.equals(sessionState)) {
        return Result.error("state校验失败");
    }
    // 2. 用code换access_token
    String tokenUrl = "https://api.weixin.qq.com/sns/oauth2/access_token" +
            "?appid=" + appId +
            "&secret=" + appSecret +
            "&code=" + code +
            "&grant_type=authorization_code";
    String tokenJson = httpClient.get(tokenUrl);
    JSONObject tokenObj = JSON.parseObject(tokenJson);
    String accessToken = tokenObj.getString("access_token");
    String openid = tokenObj.getString("openid");
    if (openid == null) {
        return Result.error("获取openid失败: " + tokenJson);
    }
    // 3. 拉取用户信息
    String userInfoUrl = "https://api.weixin.qq.com/sns/userinfo" +
            "?access_token=" + accessToken +
            "&openid=" + openid +
            "&lang=zh_CN";
    String userJson = httpClient.get(userInfoUrl);
    JSONObject userObj = JSON.parseObject(userJson);
    String nickname = userObj.getString("nickname");
    String headImg = userObj.getString("headimgurl");
    String unionid = userObj.getString("unionid"); // 开放平台绑定后才有
    // 4. 业务逻辑:查询或注册用户,生成token
    // ...
    return Result.success(loginResult);
}

注意:/sns/userinfo接口返回的nickname可能是Base64编码的(微信对部分字符做了编码处理),如果发现昵称乱码,需要对nickname做URL解码。


用户体系设计:首次登录自动注册与绑定逻辑

大多数Java项目采用openid作为微信登录的唯一标识,但如果有多个平台(如公众号、小程序、网站),建议使用unionid,我们的sys_user表结构建议如下:

CREATE TABLE `sys_user` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `openid` varchar(64) DEFAULT NULL COMMENT '微信openid',
  `unionid` varchar(64) DEFAULT NULL COMMENT '开放平台unionid',
  `nickname` varchar(100) DEFAULT NULL,
  `avatar` varchar(500) DEFAULT NULL,
  `phone` varchar(20) DEFAULT NULL,
  `status` tinyint(1) DEFAULT '1',
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_openid` (`openid`)
) ENGINE=InnoDB;

注册/登录逻辑:

User user = userMapper.selectByOpenid(openid);
if (user == null) {
    // 首次登录,自动注册
    user = new User();
    user.setOpenid(openid);
    user.setNickname(nickname != null ? nickname : "微信用户" + openid.substring(0, 6));
    user.setAvatar(headImg);
    userMapper.insert(user);
}
// 更新头像昵称(如果允许用户修改昵称,此处可跳过)
userMapper.updateWxInfo(user.getId(), nickname, headImg);
// 生成自定义登录态
String token = jwtUtil.createToken(user.getId());

这里有个深度坑:同一微信用户,通过网站登录和公众号登录拿到的openid不同,如果项目既要公众号又要网站,必须绑定到unionid,开放平台绑定流程:将公众号、小程序、网站应用都关联到同一个开放平台账号下,然后/sns/userinfo接口才会返回unionid


安全防护:状态参数state与防CSRF攻击

state参数不是可选的,它用于防御跨站请求伪造(CSRF),攻击者可以构造一个带合法code的URL诱导用户访问,如果没有state,攻击者可以伪造成登录成功。

推荐做法:

  • 生成随机state存入Redis,设置5分钟过期,回调时取出比对并删除(一次性使用)。
  • state不要跟用户身份绑定,因为此时用户还没登录。

access_tokenopenid必须由后端获取,绝对不能经前端传输,如果通过JS-SDK获取,等于泄露了appsecret


常见异常处理与调试技巧

错误码 信息 原因与解决
40029 invalid code code过期或已使用,重新发起登录
40163 code been used 重复使用code,检查回调是否被二次触发
41001 missing access_token 前端误把token传给后端,或参数名错误
40003 invalid openid openid格式错误,确认是否来自同一appid
40013 invalid appid appid无效,检查是否错用公众号appid

调试技巧

  • 用Postman模拟回调时,code必须真实,可以抓取微信重定向的URL。
  • 查看微信返回的完整JSON,不要只打印errcode,尤其当access_token换取成功,但userinfo请求返回errcode: 40001时,说明access_token被占用(注意微信的access_token有调用频率限制,且不能并发获取用户信息)。
  • 使用HTTPS回调,微信要求生产环境必须HTTPS,否则会跳转失败。

完整项目代码结构展示(Spring Boot + MyBatis)

src/main/java
├─ com.example.wechatlogin
│  ├─ controller
│  │  ├─ WechatController.java      // 登录、回调接口
│  │  └─ UserController.java        // 获取当前用户信息
│  ├─ service
│  │  ├─ WechatService.java         // 微信API调用封装
│  │  └─ UserService.java           // 用户注册登录业务
│  ├─ config
│  │  ├─ HttpClientConfig.java      // HTTP客户端配置
│  │  └─ WebMvcConfig.java          // 拦截器配置
│  ├─ entity
│  │  └─ User.java
│  ├─ mapper
│  │  └─ UserMapper.java
│  ├─ util
│  │  ├─ JwtUtil.java
│  │  └─ JsonUtil.java
│  └─ WechatLoginApplication.java
└─ resources
    ├─ application.yml
    └─ mapper/UserMapper.xml

关键依赖(pom.xml):

<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
    <version>4.5.13</version>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.83</version>
</dependency>

高频问答与实战踩坑记录

问:我用的是前后端分离架构,前端怎么处理回调?
答:最佳实践是后端生成授权URL后,前端直接window.location.href跳转,回调接口返回302重定向到前端页面(如https://yourfrontend.com/login-success?token=xxx),千万别让前端自己构造code换token,必须后端完成

问:获得了access_token,但调用userinfo接口报错“invalid credential”
答:第一,确认你用的是snsapi_login的token,而不是基础access_token(/cgi-bin/token),第二,检查access_token是否尚未过期,且openid与token匹配。

问:微信昵称出现乱码,ð¥ð·”
答:微信返回的nickname在部分情况下是Emoji或特殊字符,经过URL编码+JSON解析后,必须先URLDecoder.decode(nickname, "UTF-8"),如果还不行,直接存储原始值,展示时前端处理。

问:登录成功后,用户每次刷新页面都要求重新登录,很烦
答:这是没有做静默续期,用JWT时,建议设置2小时过期,配合refresh_token(自己实现的)来滑动续期,或者简单点,把JWT的有效期设为7天,并保存在localStorage(但xss风险要注意)。

问:微信登录和手机号登录能绑定同一个账号吗?
答:可以,在用户已登录状态下,提供“绑定微信”功能,调用/sns/userinfo获取openid后,更新当前用户的openid字段,注意:一个openid只能绑定一个用户,反之一个用户可以有多个openid(不同平台)。

问:生产环境日志显示“redirect_uri域名与后台配置不一致”
答:微信后台配置的回调域名,必须是不带路径的根域名,例如https://www.example.com,如果回调URL是https://www.example.com/api/wechat/callback,配置根域即可,如果是IP或者带端口(非443),必须用https://ip:8443这种形式,且IP也要在后台配置为“授权域名”(微信不太支持IP,建议用域名)。

问:如何测试回调?没有公网环境。
答:推荐使用natappcpolar做内网穿透,启动隧道后,会得到一个.natapp.cc.cpolar.io的二级域名,把这个域名填入微信后台,同时把redirect-uri改成该域名,注意,免费版隧道每次启动域名会变,需要同步修改代码配置。

问:服务器时间不准确,会不会影响code验证?
答:会,微信服务端生成code时依赖时间戳,如果本地服务器时间偏差大于5分钟,即使code未过期,也可能被判定无效,用ntpdate ntp.aliyun.com同步时间。


Java实现微信登录,本质是OAuth2.0的“三腿流程”落地,难点不在API调用,而在用户体系设计、state安全校验、以及回调URL配置,强烈建议把WechatService抽象成独立模块,隔离微信API变更带来的影响,最后提醒一点,永远不要在前端代码中暴露AppSecret,这是事故高发区。

希望以上代码片段和踩坑总结能帮你少走弯路,如果有具体报错,欢迎在评论区贴出日志,我会帮你分析。

抱歉,评论功能暂时关闭!