大家好,我是小悟。
微信扫码登录是一种基于OAuth 2.0协议的无密码登录方式,核心流程可概括为“生成唯一标识→扫码关联账号→确认授权→返回登录态”。具体步骤如下:
生成唯一二维码:PC端向微信服务器发送请求,获取包含唯一UUID(或state参数,用于防CSRF)的二维码链接,前端渲染成二维码展示给用户。
扫码与关联:用户用已登录的微信APP扫描二维码,微信客户端提取二维码中的UUID,并将当前登录的微信账号(openid)与该UUID绑定,向微信服务器发送“已扫描”通知。
状态轮询:PC端通过长轮询(或WebSocket)持续向微信服务器查询该UUID的状态,若返回“已扫描待确认”,则提示用户“请在手机端确认登录”。
确认与登录:用户在手机端点击“确认登录”,微信服务器将openid、access_token(用于获取用户信息的临时凭证)返回给PC端。
PC端用access_token获取用户信息,完成登录并生成长期登录态(如JWT)。
整个过程的关键是UUID的唯一性(确保一个二维码只对应一个登录请求)和state参数的防CSRF(防止恶意伪造登录请求)。
Java实现微信扫码登录代码
1. 准备工作
在微信公众平台注册网站应用,获取以下关键参数:
APPID:应用的唯一标识(如wx1234567890abcdef);
APPSECRET:应用的密钥(用于换取access_token,需严格保密);
REDIRECT_URI:
微信回调的地址(如https://yourdomain.com/wechat/callback,需与微信公众平台配置一致)。
添加依赖(Maven):
<!-- Spring Boot Web --><dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId></dependency><!-- RESTful HTTP客户端 --><dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId></dependency><!-- JSON处理 --><dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.83</version></dependency><!-- Redis缓存(用于存储state和token) --><dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId></dependency><!-- JWT生成(用于登录态管理) --><dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version></dependency>2. 生成微信登录二维码链接
作用:构造微信扫码登录的入口URL,包含APPID、REDIRECT_URI、state(随机生成,防CSRF)等参数。
import java.net.URLEncoder;import java.util.UUID;@Servicepublic class WeChatLoginService { @Value("${wechat.appid}") private String appId; // 从配置文件读取APPID @Value("${wechat.redirect-uri}") private String redirectUri; // 从配置文件读取回调地址 /** * 生成微信扫码登录的URL * @return 包含二维码链接和state的Map */ public Map<String, String> buildQrConnectUrl() { // 生成随机state(防CSRF) String state = UUID.randomUUID().toString(); // 构造微信扫码登录URL(snsapi_login表示网页授权) String qrConnectUrl = String.format( "https://open.weixin.qq.com/connect/qrconnect?appid=%s&redirect_uri=%s&response_type=code&scope=snsapi_login&state=%s#wechat_redirect", appId, URLEncoder.encode(redirectUri, StandardCharsets.UTF_8), state ); // 将state存入Redis(标记为“未登录”,超时时间10分钟) redisTemplate.opsForValue().set("wechat_login_state:" + state, "pending", Duration.ofMinutes(10)); return Map.of("qrUrl", qrConnectUrl, "state", state); }}代码说明:
snsapi_login:微信网页授权的作用域,表示需要获取用户基本信息;
URLEncoder.encode:对回调地址进行URL编码,避免特殊字符导致请求失败;
state:随机生成的UUID,用于防止CSRF攻击(后续回调时会验证该参数);
Redis缓存:存储state和登录状态,超时时间设置为10分钟(避免长期占用内存)。
3. 处理微信回调(获取code并换取access_token)
作用:微信用户扫描二维码并授权后,微信服务器会回调REDIRECT_URI,携带code(授权码)和state参数。此处需验证state,并用code换取access_token(用于获取用户信息)。
import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.RestController;import org.springframework.web.client.RestTemplate;@RestControllerpublic class WeChatCallbackController { @Autowired private WeChatLoginService weChatLoginService; @Autowired private RestTemplate restTemplate; // Spring提供的HTTP客户端 @Value("${wechat.appsecret}") private String appSecret; // 从配置文件读取APPSECRET /** * 微信回调接口(处理扫码授权后的code) */ @GetMapping("/wechat/callback") public void wechatCallback( @RequestParam String code, @RequestParam String state, HttpServletResponse response ) throws IOException { // 1. 验证state(防止CSRF) String storedState = redisTemplate.opsForValue().get("wechat_login_state:" + state); if (storedState == null || !"pending".equals(storedState)) { response.sendRedirect("https://your-pc-site.com/error?msg=Invalid state"); return; } // 2. 用code换取access_token String tokenUrl = String.format( "https://api.weixin.qq.com/sns/oauth2/access_token?appid=%s&secret=%s&code=%s&grant_type=authorization_code", weChatLoginService.getAppId(), appSecret, code ); String tokenResponse = restTemplate.getForObject(tokenUrl, String.class); JSONObject tokenJson = JSON.parseObject(tokenResponse); // 检查是否有错误 if (tokenJson.containsKey("errcode")) { response.sendRedirect("https://your-pc-site.com/error?msg=" + tokenJson.getString("errmsg")); return; } String accessToken = tokenJson.getString("access_token"); String openId = tokenJson.getString("openid"); // 用户唯一标识 // 3. 获取用户信息 String userInfoUrl = String.format( "https://api.weixin.qq.com/sns/userinfo?access_token=%s&openid=%s&lang=zh_CN", accessToken, openId ); String userInfoResponse = restTemplate.getForObject(userInfoUrl, String.class); JSONObject userInfoJson = JSON.parseObject(userInfoResponse); // 4. 业务逻辑:查找/注册用户,生成登录态 String token = weChatLoginService.handleLogin(userInfoJson); // 5. 登录成功,跳转回原页面并携带token response.sendRedirect("https://your-pc-site.com/login-success?token=" + token); }}代码说明:
code:微信授权码,有效期为5分钟,需立即换取access_token;
state验证:确保回调请求来自微信(防止恶意伪造);
access_token:临时凭证,用于获取用户信息,有效期为2小时;
openid:用户的唯一标识(同一用户在同一个下openid不变);
userInfoUrl:获取用户信息的接口(lang=zh_CN表示返回中文信息)。
4. 获取用户信息并生成登录态
作用:用access_token和openid获取用户信息,将用户信息存入数据库(若未注册则创建),生成长期登录态(如JWT),并返回给前端。
import io.jsonwebtoken.Jwts;import io.jsonwebtoken.SignatureAlgorithm;import org.springframework.beans.factory.annotation.Value;import org.springframework.data.redis.core.RedisTemplate;import org.springframework.stereotype.Service;import java.util.Date;import java.util.HashMap;import java.util.Map;import java.util.UUID;@Servicepublic class WeChatLoginService { @Value("${wechat.appid}") private String appId; @Value("${wechat.appsecret}") private String appSecret; @Autowired private RestTemplate restTemplate; @Autowired private RedisTemplate<String, String> redisTemplate; @Value("${jwt.secret}") private String jwtSecret; // JWT密钥(需严格保密) @Value("${jwt.expiration}") private Long jwtExpiration; // JWT有效期(如2小时,单位:毫秒) /** * 处理登录逻辑:查找/注册用户,生成JWT */ public String handleLogin(JSONObject userInfoJson) { String openId = userInfoJson.getString("openid"); // 1. 查找用户(根据openid查询数据库) User user = userRepository.findByOpenId(openId); if (user == null) { // 2. 未注册则创建用户 user = new User(); user.setOpenId(openId); user.setNickname(userInfoJson.getString("nickname")); user.setHeadImg(userInfoJson.getString("headimgurl")); user.setSex(userInfoJson.getInteger("sex")); user.setState("NORMAL"); // 正常状态 userRepository.save(user); } // 3. 生成JWT(包含用户ID,有效期2小时) String token = Jwts.builder() .setSubject(user.getId().toString()) // 主题:用户ID .setIssuedAt(new Date()) // 签发时间 .setExpiration(new Date(System.currentTimeMillis() + jwtExpiration)) // 过期时间 .signWith(SignatureAlgorithm.HS256, jwtSecret) // 签名算法和密钥 .compact(); // 4. 将token存入Redis(用于校验登录态,超时时间与JWT一致) redisTemplate.opsForValue().set("user_token:" + user.getId(), token, Duration.ofMillis(jwtExpiration)); return token; } /** * 获取APPID(从配置文件) */ public String getAppId() { return appId; }}代码说明:
userRepository:用户数据访问层,用于查找/保存用户信息;
JWT生成:使用jjwt库生成JWT,包含用户ID、签发时间、过期时间,签名密钥为jwtSecret(需与前端一致);
Redis缓存:存储JWT,用于后续校验登录态(如访问其他接口时,前端携带JWT,后端通过Redis验证有效性);
用户信息:从微信返回的userInfoJson中提取,存入数据库(若未注册则创建)。
5. 前端轮询登录状态(可选)
作用:用户扫码后,PC端可通过轮询接口检查登录状态(如“已扫描”“已确认”),提升用户体验。
import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.RestController;import org.springframework.web.context.request.async.DeferredResult;import java.util.concurrent.ConcurrentHashMap;@RestControllerpublic class LoginStatusController { // 存储登录状态(key:state,value:DeferredResult) private final ConcurrentHashMap<String, DeferredResult<Map<String, Object>>> statusMap = new ConcurrentHashMap<>(); /** * 前端轮询登录状态 */ @GetMapping("/wechat/check-login") public DeferredResult<Map<String, Object>> checkLogin(@RequestParam String state) { DeferredResult<Map<String, Object>> deferredResult = new DeferredResult<>(5000L); // 超时时间5秒 statusMap.put(state, deferredResult); // 设置超时回调(若5秒内未登录,返回“登录中”) deferredResult.onTimeout(() -> { statusMap.remove(state); deferredResult.setResult(Map.of("status", "pending", "message", "登录中,请稍候...")); }); return deferredResult; } /** * 登录成功后,通知前端(调用此方法更新状态) */ public void notifyLoginSuccess(String state, String token) { DeferredResult<Map<String, Object>> deferredResult = statusMap.get(state); if (deferredResult != null) { statusMap.remove(state); deferredResult.setResult(Map.of( "status", "success", "token", token, "message", "登录成功" )); } }}代码说明:
DeferredResult:Spring提供的异步结果,用于实现长轮询(避免频繁请求阻塞线程);
statusMap:存储state和DeferredResult的映射,用于在登录成功后通知前端;
前端调用:用户扫码后,前端每隔1秒调用/wechat/check-login?state=xxx,直到返回“success”状态。
6. 配置文件(application.yml)
wechat: appid: wx1234567890abcdef # 替换为你的APPID appsecret: your_app_secret # 替换为你的APPSECRET redirect-uri: https://yourdomain.com/wechat/callback # 替换为你的回调地址jwt: secret: your_jwt_secret # 替换为你的JWT密钥(需严格保密) expiration: 7200000 # JWT有效期(2小时,单位:毫秒)spring: redis: host: localhost port: 6379 password: your_redis_password # 若有密码则填写注意事项
安全性:
APPSECRET和jwtSecret需严格保密,不要硬编码在代码中(建议使用环境变量或配置中心);
state参数必须随机生成(如UUID),防止CSRF攻击;
微信回调的REDIRECT_URI需与微信公众平台配置一致,否则无法正常回调。
错误处理:
处理微信返回的错误码(如invalid url domain表示回调地址未配置,code expired表示授权码过期),并给用户友好的提示。
性能优化:
使用Redis缓存state和token,避免频繁访问数据库;
长轮询的超时时间不宜过长(如5秒),避免占用过多资源。
用户体验:
前端展示二维码时,添加“扫码中”“请确认”等提示,提升交互体验;
登录成功后,自动跳转回原页面(如用户之前访问的“个人中心”),避免重复操作。

谢谢你看我的文章,既然看到这里了,如果觉得不错,随手点个赞、转发、在看三连吧,感谢感谢。那我们,下次再见。
您的一键三连,是我更新的最大动力,谢谢
山水有相逢,来日皆可期,谢谢阅读,我们再会
我手中的金箍棒,上能通天,下能探海
转载请注明来自德立,本文标题:《微信公众号怎样注册登录(微信扫码登录)》
 
		
 京公网安备11000000000001号
京公网安备11000000000001号 京ICP备11000001号
京ICP备11000001号
还没有评论,来说两句吧...