Spring Boot + Vue 实现微信扫码登录的详细攻略

一、准备工作

1. 注册微信开放平台账号

  • 访问 微信开放平台,注册开发者账号并完成实名认证。
  • 登录后进入「管理中心」→「网站应用」→「创建网站应用」,填写应用基本信息(如应用名称、图标、简介等)。
  • 配置授权回调域(redirect_uri 的域名,例如 yourdomain.com,需与前端域名一致)。
  • 提交审核,审核通过后获取 ​AppID​​AppSecret​(后续接口调用需要)。

2. 前端项目准备

  • 使用 Vue CLI 创建项目(或已有项目),安装依赖:

    arduino 复制代码
    npm install axios qrcodejs2 --save  # axios用于HTTP请求,qrcodejs2生成二维码

3. 后端项目准备

  • 使用 Spring Initializr 创建 Spring Boot 项目,添加依赖:

    xml 复制代码
    <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:

    yaml 复制代码
    spring:
      redis:
        host: localhost
        port: 6379
        database: 0
        timeout: 5000
    
    wechat:
      appid: 你的AppID
      secret: 你的AppSecret
      redirect-uri: http://yourdomain.com/api/wechat/login/callback  # 回调地址(需与微信开放平台配置一致)

二、核心流程概述

微信扫码登录的核心流程如下(前后端协作):

  1. ​前端请求生成二维码​ :前端调用后端接口,获取微信扫码的临时 URL 和唯一标识(uuid)。
  2. ​前端展示二维码​ :前端使用 uuid 对应的 URL 生成二维码(微信官方提供的扫码页面)。
  3. ​用户扫码并确认​:用户使用微信扫描二维码,选择「确认登录」。
  4. ​微信回调后端​ :微信服务器向配置的 redirect_uri 发送回调请求(携带 codestate)。
  5. ​后端验证并生成登录态​ :后端通过 code 换取 access_tokenopenid,验证用户身份后生成登录态(如 JWT)。
  6. ​前端轮询状态​ :前端定时调用后端接口查询 uuid 对应的扫码状态,若状态为「已确认」则获取登录态,完成登录。

三、前后端具体实现

(一)后端实现

1. 定义扫码状态枚举
arduino 复制代码
public enum WechatLoginStatus {
    NOT_SCAN,    // 未扫码
    SCANNED,     // 已扫码待确认
    CONFIRMED,   // 已确认登录
    EXPIRED      // 已过期
}
2. 生成二维码接口(/api/wechat/qrcode)

​功能​ ​:生成唯一的 uuid,并将 uuid 与初始状态(NOT_SCAN)存入 Redis(设置过期时间,如 5 分钟);返回微信扫码的临时 URL。

kotlin 复制代码
@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)

​功能​ ​:微信服务器回调此接口,携带 codestate(即之前的 uuid);后端通过 code 换取 access_tokenopenid,验证用户身份后生成登录态,并更新 Redis 中 uuid 的状态为 CONFIRMED

typescript 复制代码
// 回调接口(需在微信开放平台配置此 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)。

less 复制代码
@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。

kotlin 复制代码
<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):

javascript 复制代码
// 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;

四、注意事项

  1. ​微信开放平台配置​​:

    • 确保 redirect_uri 已在微信开放平台配置(需与后端回调地址完全一致,包括协议 http/https)。
    • 应用的「网站应用」需配置正确的「网站地址」(前端域名)。
  2. ​安全性​​:

    • state 参数需使用随机字符串(如 UUID),防止 CSRF 攻击。
    • 微信回调的 code 只能使用一次,需及时换取 access_token
    • 敏感信息(如 AppSecret)需加密存储,避免硬编码在代码中。
  3. ​缓存管理​​:

    • Redis 中存储的 uuid 需设置合理的过期时间(建议 5-10 分钟),避免内存泄漏。
    • 分布式系统中需使用共享缓存(如 Redis 集群),确保多实例后端能访问同一缓存。
  4. ​用户体验​​:

    • 前端轮询间隔建议设置为 2-3 秒,避免频繁请求。
    • 二维码过期后需提供「刷新」按钮,重新调用 /api/wechat/qrcode 获取新二维码。
  5. ​用户绑定​​:

    • 若系统需要用户先注册/绑定微信,可在回调接口中根据 openid 判断用户是否存在,不存在则跳转到绑定页面(需传递 stateuuid 关联绑定流程)。

通过以上步骤,即可实现 Spring Boot + Vue 的微信扫码登录功能。核心是理解微信扫码登录的流程,并正确处理前后端的交互和状态管理。

相关推荐
向下的大树6 分钟前
Vue 2迁移Vue 3实战:从痛点到突破
前端·javascript·vue.js
程序员阿鹏17 分钟前
SpringBoot自动装配原理
java·开发语言·spring boot·后端·spring·tomcat·maven
程序员爱钓鱼18 分钟前
Node.js 编程实战:CSV&JSON &Excel 数据处理
前端·后端·node.js
老华带你飞24 分钟前
工会管理|基于springboot 工会管理系统(源码+数据库+文档)
java·数据库·vue.js·spring boot·后端·spring
Echo flower32 分钟前
Spring Boot WebFlux 实现流式数据传输与断点续传
java·spring boot·后端
前端OnTheRun1 小时前
如何禁用项目中的ESLint配置?
javascript·vue.js·eslint
小徐Chao努力1 小时前
Go语言核心知识点底层原理教程【变量、类型与常量】
开发语言·后端·golang
锥锋骚年1 小时前
go语言异常处理方案
开发语言·后端·golang
北城以北88881 小时前
SpringBoot--Redis基础知识
java·spring boot·redis·后端·intellij-idea
superman超哥1 小时前
仓颉语言中并发集合的实现深度剖析与高性能实践
开发语言·后端·python·c#·仓颉