网站首页 > 博客文章 正文
一、准备工作
1. 注册微信开放平台账号
- 访问 微信开放平台,注册开发者账号并完成实名认证。
- 登录后进入「管理中心」→「网站应用」→「创建网站应用」,填写应用基本信息(如应用名称、图标、简介等)。
- 配置授权回调域(redirect_uri 的域名,例如 yourdomain.com,需与前端域名一致)。
- 提交审核,审核通过后获取 AppID 和 AppSecret(后续接口调用需要)。
2. 前端项目准备
- 使用 Vue CLI 创建项目(或已有项目),安装依赖:
npm install axios qrcodejs2 --save # axios用于HTTP请求,qrcodejs2生成二维码
3. 后端项目准备
- 使用 Spring Initializr 创建 Spring Boot 项目,添加依赖:
<dependencies>
<!-- Spring Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Redis 缓存(存储扫码状态) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- Lombok(简化代码) -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- JSON 工具 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.83</version>
</dependency>
</dependencies>
- 配置 application.yml,设置 Redis 连接和微信 AppID/AppSecret:
spring:
redis:
host: localhost
port: 6379
database: 0
timeout: 5000
wechat:
appid: 你的AppID
secret: 你的AppSecret
redirect-uri: http://yourdomain.com/api/wechat/login/callback # 回调地址(需与微信开放平台配置一致)
二、核心流程概述
微信扫码登录的核心流程如下(前后端协作):
- 前端请求生成二维码:前端调用后端接口,获取微信扫码的临时 URL 和唯一标识(uuid)。
- 前端展示二维码:前端使用 uuid 对应的 URL 生成二维码(微信官方提供的扫码页面)。
- 用户扫码并确认:用户使用微信扫描二维码,选择「确认登录」。
- 微信回调后端:微信服务器向配置的 redirect_uri 发送回调请求(携带 code 和 state)。
- 后端验证并生成登录态:后端通过 code 换取 access_token 和 openid,验证用户身份后生成登录态(如 JWT)。
- 前端轮询状态:前端定时调用后端接口查询 uuid 对应的扫码状态,若状态为「已确认」则获取登录态,完成登录。
三、前后端具体实现
(一)后端实现
1. 定义扫码状态枚举
public enum WechatLoginStatus {
NOT_SCAN, // 未扫码
SCANNED, // 已扫码待确认
CONFIRMED, // 已确认登录
EXPIRED // 已过期
}
2. 生成二维码接口(/api/wechat/qrcode)
功能:生成唯一的 uuid,并将 uuid 与初始状态(NOT_SCAN)存入 Redis(设置过期时间,如 5 分钟);返回微信扫码的临时 URL。
@RestController
@RequestMapping("/api/wechat")
@Slf4j
public class WechatLoginController {
@Value("${wechat.appid}")
private String appid;
@Value("${wechat.secret}")
private String secret;
@Value("${wechat.redirect-uri}")
private String redirectUri;
@Autowired
private RedisTemplate<String, Object> redisTemplate;
// 生成二维码接口
@GetMapping("/qrcode")
public Result getQrCode() {
// 生成唯一 uuid(作为 state 参数)
String uuid = UUID.randomUUID().toString();
// 微信扫码临时 URL(用户扫码后会跳转到该 URL,携带 state=uuid)
String qrcodeUrl = "https://open.weixin.qq.com/connect/qrconnect?" +
"appid=" + appid +
"&redirect_uri=" + URLEncoder.encode(redirectUri, StandardCharsets.UTF_8) +
"&response_type=code" +
"&scope=snsapi_login" +
"&state=" + uuid +
"#wechat_redirect";
// 将 uuid 存入 Redis(状态为 NOT_SCAN,过期时间 5 分钟)
redisTemplate.opsForValue().set("wechat_login:" + uuid, WechatLoginStatus.NOT_SCAN, 5, TimeUnit.MINUTES);
return Result.success(qrcodeUrl);
}
}
3. 处理微信回调接口(/api/wechat/login/callback)
功能:微信服务器回调此接口,携带 code 和 state(即之前的 uuid);后端通过 code 换取 access_token 和 openid,验证用户身份后生成登录态,并更新 Redis 中 uuid 的状态为 CONFIRMED。
// 回调接口(需在微信开放平台配置此 URL)
@GetMapping("/login/callback")
public Result handleCallback(String code, String state) {
log.info("微信回调:code={}, state={}", code, state);
// 校验 state 是否存在(防止伪造)
String key = "wechat_login:" + state;
WechatLoginStatus status = (WechatLoginStatus) redisTemplate.opsForValue().get(key);
if (status == null || status != WechatLoginStatus.SCANNED) {
return Result.error("无效的扫码状态");
}
// 通过 code 换取 access_token(微信接口)
String tokenUrl = "https://api.weixin.qq.com/sns/oauth2/access_token?" +
"appid=" + appid +
"&secret=" + secret +
"&code=" + code +
"&grant_type=authorization_code";
ResponseEntity<String> response = new RestTemplate().getForEntity(tokenUrl, String.class);
JSONObject tokenResult = JSON.parseObject(response.getBody());
if (tokenResult.containsKey("errcode")) {
return Result.error("获取 access_token 失败:" + tokenResult.getString("errmsg"));
}
String accessToken = tokenResult.getString("access_token");
String openid = tokenResult.getString("openid");
// 校验 openid 是否绑定过本系统用户(根据业务需求,可跳过直接注册新用户)
// 示例:假设 openid 对应系统用户 ID 为 123
Long userId = userService.getUserIdByOpenid(openid);
if (userId == null) {
return Result.error("用户未绑定");
}
// 生成登录态(如 JWT)
String token = JwtUtil.generateToken(userId);
// 更新 Redis 状态为 CONFIRMED,并存储 token(或其他用户信息)
redisTemplate.opsForValue().set(key, WechatLoginStatus.CONFIRMED, 30, TimeUnit.MINUTES); // 延长过期时间
redisTemplate.opsForValue().set("user_token:" + userId, token, 30, TimeUnit.MINUTES);
return Result.success(token);
}
4. 查询扫码状态接口(/api/wechat/check-status)
功能:前端定时调用此接口,传入 uuid,查询扫码状态(
NOT_SCAN/SCANNED/CONFIRMED/EXPIRED)。
@GetMapping("/check-status/{uuid}")
public Result checkStatus(@PathVariable String uuid) {
String key = "wechat_login:" + uuid;
WechatLoginStatus status = (WechatLoginStatus) redisTemplate.opsForValue().get(key);
if (status == null) {
return Result.error("二维码已过期");
}
return Result.success(status);
}
(二)前端实现
1. 生成并展示二维码(Vue 组件)
使用 qrcodejs2 生成二维码,调用后端 /api/wechat/qrcode 获取临时 URL。
<template>
<div class="qrcode-container">
<div id="qrcode" ref="qrcode"></div>
<p v-if="status === 'NOT_SCAN'">请使用微信扫描二维码登录</p>
<p v-if="status === 'SCANNED'">请在微信中确认登录</p>
<p v-if="status === 'CONFIRMED'">登录成功!跳转中...</p>
<p v-if="status === 'EXPIRED'">二维码已过期,请重新获取</p>
</div>
</template>
<script>
import QRCode from 'qrcodejs2';
import axios from 'axios';
export default {
data() {
return {
qrcodeUrl: '', // 微信扫码临时 URL
uuid: '', // 唯一标识
status: 'NOT_SCAN', // 扫码状态:NOT_SCAN/SCANNED/CONFIRMED/EXPIRED
qrCodeTimer: null, // 二维码实例(用于销毁)
checkStatusTimer: null // 轮询状态定时器
};
},
mounted() {
this.getQrCode();
},
methods: {
// 获取二维码
async getQrCode() {
try {
const res = await axios.get('/api/wechat/qrcode');
this.qrcodeUrl = res.data.data;
this.uuid = this.qrcodeUrl.split('state=')[1].split('#')[0]; // 从 URL 中提取 uuid
this.renderQRCode();
this.startPolling();
} catch (error) {
console.error('获取二维码失败', error);
}
},
// 渲染二维码
renderQRCode() {
if (this.qrCodeTimer) {
this.qrCodeTimer.clear(); // 销毁旧二维码
}
this.qrCodeTimer = new QRCode(this.$refs.qrcode, {
text: this.qrcodeUrl,
width: 200,
height: 200
});
},
// 开始轮询状态
startPolling() {
this.checkStatusTimer = setInterval(async () => {
try {
const res = await axios.get(`/api/wechat/check-status/${this.uuid}`);
this.status = res.data.data;
if (this.status === 'CONFIRMED') {
clearInterval(this.checkStatusTimer);
this.qrCodeTimer.clear();
// 登录成功,保存 token 并跳转
localStorage.setItem('token', res.data.data); // 假设返回的 token 在 data.data 中
this.$router.push('/home');
} else if (this.status === 'EXPIRED') {
clearInterval(this.checkStatusTimer);
this.qrCodeTimer.clear();
alert('二维码已过期,请重新获取');
}
} catch (error) {
console.error('查询状态失败', error);
}
}, 2000); // 每 2 秒轮询一次
}
},
beforeDestroy() {
// 组件销毁时清理定时器
if (this.qrCodeTimer) this.qrCodeTimer.clear();
if (this.checkStatusTimer) clearInterval(this.checkStatusTimer);
}
};
</script>
2. 处理登录态(路由守卫)
在 Vue 路由中添加守卫,验证用户是否已登录(通过 localStorage 中的 token):
// router.js
import Vue from 'vue';
import Router from 'vue-router';
import Home from './views/Home.vue';
import Login from './views/Login.vue';
Vue.use(Router);
const router = new Router({
routes: [
{ path: '/', redirect: '/home' },
{ path: '/home', component: Home, meta: { requiresAuth: true } },
{ path: '/login', component: Login }
]
});
router.beforeEach((to, from, next) => {
const token = localStorage.getItem('token');
if (to.meta.requiresAuth && !token) {
next('/login'); // 未登录跳转到扫码登录页
} else {
next();
}
});
export default router;
四、注意事项
- 微信开放平台配置:
- 确保 redirect_uri 已在微信开放平台配置(需与后端回调地址完全一致,包括协议 http/https)。
- 应用的「网站应用」需配置正确的「网站地址」(前端域名)。
- 安全性:
- state 参数需使用随机字符串(如 UUID),防止 CSRF 攻击。
- 微信回调的 code 只能使用一次,需及时换取 access_token。
- 敏感信息(如 AppSecret)需加密存储,避免硬编码在代码中。
- 缓存管理:
- Redis 中存储的 uuid 需设置合理的过期时间(建议 5-10 分钟),避免内存泄漏。
- 分布式系统中需使用共享缓存(如 Redis 集群),确保多实例后端能访问同一缓存。
- 用户体验:
- 前端轮询间隔建议设置为 2-3 秒,避免频繁请求。
- 二维码过期后需提供「刷新」按钮,重新调用 /api/wechat/qrcode 获取新二维码。
- 用户绑定:
- 若系统需要用户先注册/绑定微信,可在回调接口中根据 openid 判断用户是否存在,不存在则跳转到绑定页面(需传递 state 或 uuid 关联绑定流程)。
通过以上步骤,即可实现 Spring Boot + Vue 的微信扫码登录功能。核心是理解微信扫码登录的流程,并正确处理前后端的交互和状态管理。
猜你喜欢
- 2025-07-28 告别频繁登录!Nuxt3 + TS + Vue3实战:双Token无感刷新方案全解析
- 2025-07-28 SpringBoot实现单点登录(SSO)的4种方案
- 2025-07-28 随机密聊 匿名聊天室程序源码(随机匿名聊天在线)
- 2025-07-28 SpringBoot大文件上传卡死?分块切割术搞定GB级传输,速度飙升!
- 2025-07-28 Java 微服务从源码实战开始 | Gitee 项目推荐
- 2025-07-28 轻量级埋点sdk搭建,便捷更全面(埋点sdk是什么)
- 2025-07-28 Spring Boot 实现文件秒传功能(springboot上传文件到指定文件夹)
- 2025-07-28 项目中不用redis分布式锁,怎么防止用户重复提交?
- 2025-07-28 SpringBoot项目日志打印traceId生成
- 2025-07-28 如何实现PC端网站扫码登录操作?(网页 扫码)
你 发表评论:
欢迎- 08-06nginx 反向代理
- 08-06跨表插入连续的日期,sheetsname函数#excel技巧
- 08-06初中生也能学的编程,不走弯路,先用后学
- 08-06find命令的“七种武器”:远不止-name和-type
- 08-06恶意代码常见的编程方式
- 08-06kali2021ping 外网不通
- 08-06因为一个函数strtok踩坑,我被老工程师无情嘲笑了
- 08-06hadoop集群搭建详细方法
- 23℃nginx 反向代理
- 最近发表
- 标签列表
-
- ifneq (61)
- 字符串长度在线 (61)
- googlecloud (64)
- powershellfor (73)
- messagesource (71)
- plsql64位 (73)
- vueproxytable (64)
- npminstallsave (63)
- #NAME? (61)
- promise.race (63)
- 2019cad序列号和密钥激活码 (62)
- window.performance (66)
- qt删除文件夹 (72)
- mysqlcaching_sha2_password (64)
- nacos启动失败 (64)
- ssh-add (70)
- yarnnode (62)
- abstractqueuedsynchronizer (64)
- source~/.bashrc没有那个文件或目录 (65)
- springboot整合activiti工作流 (70)
- jmeter插件下载 (61)
- 抓包分析 (60)
- idea创建mavenweb项目 (65)
- qcombobox样式表 (68)
- pastemac (61)
本文暂时没有评论,来添加一个吧(●'◡'●)