从零玩转第三方登录之WeChat公众号扫码关注登陆 -wechatgzh
title: 从零玩转第三方登录之WeChat公众号扫码关注登陆
date: 2022-09-27 22:46:53.362
updated: 2023-03-30 13:28:41.359
url: https://www.yby6.com/archives/wechatgzh
categories:
- 从零玩转系列
tags:
- 第三方登录
- 从零玩转系列
前言
由于看见了面试鸭的登陆方式,我也想来整一个.注意: 只能微信认证的公众号才能有二维码扫码的权限,那么我们将使用 微信的测试账户来玩转扫码(沙箱)
1. 大致流程思路:
一、用户打开网页进行登陆/注册 扫码(微信的)
二、用户扫码成功后 微信会根据我们配置的回调地址访问我们的回调并且传递某些参数
三、用户扫码成功并且进行了关注我们的公众号 微信也会访问回调 传递参数
四、++域名使用内网穿透(我这里使用花生壳)++
思路地址: 接收事件推送
在微信用户和公众号产生交互的过程中,用户的某些操作会使得微信服务器通过事件推送的形式通知到开发者在开发者中心处设置的服务器地址,从而开发者可以获取到该信息。其中,某些事件推送在发生后,是允许开发者回复用户的,某些则不允许,详细内容如下:
1、关注/取消关注事件
2、 扫描带参数二维码事件
3、上报地理位置事件
4、自定义菜单事件
5、点击菜单拉取消息时的事件推送
6、点击菜单跳转链接时的事件推送
根据上述六点我们PC端只需要 1、2点即可只是来扫码公众号并且关注后登录
2. 进入测试号页面
测试号接口配置
接口信息配置: 将会get方法来进行验签你服务器的请求 和 post来回调推送信息到服务器
参考: 接口信息配置
JS接口安全配置:我们在日常当中经常可以看见js接口安全域名。那么,js接口安全域名是什么?js接口安全域名主要用于微信公众号,如果大家要进行微信的开发,创建公众号是需要填写js接口安全域名的。当我们运用程序的时候,网络是会自动验证安全域名的,它可以解决服务器终端的语言问题,能够让访问正常的运行,只有使用好js接口安全域名,网上的用户才能够访问到网页。
参考:JS接口安全配置

3. 介绍
获取 AccessToken
用于请求微信API 需要用到的认证信息
参考: 获取AccessToken
临时二维码
- 用户扫描带场景值二维码时,可能推送以下两种事件:
如果用户还未关注公众号,则用户可以关注公众号,关注后微信会将带场景值关注事件推送给开发者。- 如果用户已经关注公众号,在用户扫描后会自动进入会话,微信也会将带场景值扫描事件推送给开发者。
- 获取带参数的二维码的过程包括两步,首先创建二维码ticket,然后凭借 ticket 到指定 URL 换取二维码。
正确的 Json 返回结果:
{"ticket":"gQH47joAAAAAAAAAASxodHRwOi8vd2VpeGluLnFxLmNvbS9xL2taZ2Z3TVRtNzJXV1Brb3ZhYmJJAAIEZ23sUwMEmm
3sUw==","expire_seconds":60,"url":"http://weixin.qq.com/q/kZgfwMTm72WWPkovabbI"}- 参考: 临时二维码
4. 代码操作
编写接口配置以便能修改接口
/***
* 微信服务器触发get请求用于检测签名-
* 如果需要绝对的安全就按照微信来进行验签
*/
@GetMapping("/weChatScanCodeCallback")
@ResponseBody
public String weChatScan(HttpServletRequest request) {
log.info("验签章:{}", request.getParameterMap());
return request.getParameter("echostr");
}
解析微信返回参数
使用DOM4J将微信返回XML格式转换一下
/**
* @Author yang shuai
* @Date 2022/9/3
*/
public class XmlUtil {
/**
* 读取xml标签内容存放map当中
*/
public static Map<String,Object> parseXML(InputStream in){
Map<String,Object> map=new HashMap<>();
try {
SAXReader saxReader = new SAXReader();
Document document = saxReader.read(in);
Element root = document.getRootElement();
Iterator iterator = root.elementIterator();
while (iterator.hasNext()){
Element element = (Element) iterator.next();
map.put(element.getName(),element.getStringValue());
}
} catch (DocumentException e) {
e.printStackTrace();
}
return map;
}
}
接收微信回调
/**
* 接收微信推送事件
*/
@PostMapping("/weChatScanCodeCallback")
@ResponseBody
public String weChatCallback(HttpServletRequest request) {
try {
InputStream inputStream = request.getInputStream();
Map<String, Object> map = XmlUtil.parseXML(inputStream);
log.info("接收参数:{}", map);
} catch (IOException e) {
e.printStackTrace();
}
return "success";
Last
注入restTemplate请求
/**
* @Author yang shuai
* @Date 2022/9/3
* 注入restTemplate用于http请求
*/
@Configuration
public class RestTemplateConfig {
@Resource
private RestTemplateBuilder templateBuilder;
@Bean
public RestTemplate restTemplate(){
return templateBuilder.build();
}
}
生成微信二维码
/**
* @Author yang shuai
* @Date 2022/9/3
*/
public interface WeChatService {
/**
* 获取token
*
* @return
*/
String getAccessToken();
/**
* 获取生成二维码参数
*
* @return
*/
Map<String, Object> getQrCode();
}
实现
/**
* @Author yang shuai
* @Date 2022/9/3
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class WeChatServiceImpl implements WeChatService {
@Value("${weChat.gzh.appid:''}")
private String appid;
@Value("${weChat.gzh.secret:''}")
private String secret;
private final RestTemplate restTemplate;
private final RedisCache redisCacheManager;
/**
* 获取token用于操作微信接口
*/
@Override
public String getAccessToken() {
String key = "wx_access_token";
if (redisCacheManager.hashKey(key)) {
return redisCacheManager.getCacheObject(key);
}
// 获取微信扫码 token
String url = String.format("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s", appid, secret);
ResponseEntity<String> result = restTemplate.getForEntity(url, String.class);
if (result.getStatusCode() == HttpStatus.OK) {
JSONObject jsonObject = JSON.parseObject(result.getBody());
String access_token = jsonObject.getString("access_token");
Long expires_in = jsonObject.getLong("expires_in");
redisCacheManager.setCacheObject(key, access_token, expires_in, TimeUnit.SECONDS);
return access_token;
}
return null;
}
/**
* 获取微信公众号二维码
*/
@Override
public Map<String, Object> getQrCode() {
// 获取临时二维码
String url = String.format("https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=%s", getAccessToken());
ResponseEntity<String> result = restTemplate.postForEntity(url, "{\"expire_seconds\": 604800, \"action_name\": \"QR_STR_SCENE\", \"action_info\": {\"scene\": {\"scene_str\": \"test\"}}}", String.class);
log.info("二维码:{}", result.getBody());
JSONObject jsonObject = JSON.parseObject(result.getBody());
Map<String, Object> map = new HashMap<>();
map.put("ticket", jsonObject.getString("ticket"));
map.put("url", jsonObject.getString("url"));
return map;
}
}
5. 改造Controller
新增获取二维码
/**
* 获取二维码参数
*
* @return
*/
@GetMapping("/getQrCode")
@ResponseBody
public Object getQrCode() {
return weChatService.getQrCode();
}
6.编写前段Demo
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>登陆</title>
</head>
<body>
<div style="width: 200px;margin: 50px auto">
<div id="qrcode"></div>
<div id="msg" style="display: none">
扫码成功!
</div>
</div>
<script type='text/javascript' src='http://cdn.staticfile.org/jquery/2.1.1/jquery.min.js'></script>
<script src="https://cdn.bootcss.com/jquery.qrcode/1.0/jquery.qrcode.min.js"></script>
<script>
$(function () {
let count = 0;
//获取二维码参数
$.get('https://34i33045l8.oicp.vip/weChat/getQrCode', function (res) {
//生成二维码
$('#qrcode').qrcode(res.url);
})
})
</script>
</body>
</html>
7.启动后端查看效果
1、使用idea打开html挂载一个node

2、打开前面要求设置的内网穿透用于接收微信的回调

3、进行扫码-查看后台打印参数数据

4、扫码后查看控制台

推送 XML 数据包示例:
- 用户未关注时,进行关注后的事件推送
<xml>
<ToUserName><![CDATA[toUser]]></ToUserName>
<FromUserName><![CDATA[FromUser]]></FromUserName>
<CreateTime>123456789</CreateTime>
<MsgType><![CDATA[event]]></MsgType>
<Event><![CDATA[subscribe]]></Event>
<EventKey><![CDATA[qrscene_123123]]></EventKey>
<Ticket><![CDATA[TICKET]]></Ticket>
</xml>
示例:

在这里大家应该大致的知道下面的该如何实现了!
- 微信回调会一直存在
Ticket字段 用于表示每次二维码的唯一标识
我们将它进行存储redis当中并且可以看到Event我们利用它来区分当前是否为扫码还是关注的推送- 则前段进行段轮训来请求校验当前为什么状态?
参数说明:
| 参数 | 描述 |
|---|---|
| ToUserName | 开发者微信号 |
| FromUserName | 发送方帐号(一个OpenID) |
| CreateTime | 消息创建时间 (整型) |
| MsgType | 消息类型,event |
| Event | 事件类型,subscribe(扫码关注) or SCAN (扫码) |
| EventKey | 事件 KEY 值,qrscene_为前缀,后面为二维码的参数值 |
| Ticket | 二维码的ticket,可用来换取二维码图片 |
8. 改造Controller
新增短轮询检查扫码状态
/**
* 用于检测扫码和关注状态
*
* @return
*/
@PostMapping("/checkLogin")
@ResponseBody
public Object checkLogin(String ticket) {
// 存在该信息并且为关注了公众号
if (redisCache.hashKey(ticket)) {
if (!redisCache.getCacheObject(ticket).equals("subscribe")) {
return AjaxResult.error(201, "扫码成功");
}
//扫码通过则删除
redisCache.deleteObject(ticket);
return AjaxResult.success();
}
return AjaxResult.error("无动作");
}
修改微信回调完善业务
/**
* 接收微信推送事件
*
* @param request
* @return
*/
@PostMapping("/weChatScanCodeCallback")
@ResponseBody
public String weChatCallback(HttpServletRequest request) {
try {
InputStream inputStream = request.getInputStream();
Map<String, Object> map = XmlUtil.parseXML(inputStream);
log.info("接收参数:{}", map);
String userOpenId = (String) map.get("FromUserName");
String event = (String) map.get("Event");
// 自己生成的二维码不管是关注还是扫码都能取到ticket凭证,这里我使用Ticket作为每次二维码的唯一标识
String ticket = (String) map.get("Ticket");
if ("subscribe".equals(event)) {
// 根据openid判断用户是否存在,不存在则获取新增用户
// 或者根据前段传递手机号或者用户名称来进行openId绑定 看你自己的业务.
redisCache.setCacheObject(ticket, "subscribe", (long) (10 * 60), TimeUnit.SECONDS);
log.info("用户关注:{}", userOpenId);
} else if ("SCAN".equals(event)) {
redisCache.setCacheObject(ticket, "scan", (long) (10 * 60), TimeUnit.SECONDS);
log.info("用户扫码:{}", userOpenId);
}
} catch (IOException e) {
log.error("回调异常:",e);
}
return "success";
}
新增前段短轮询
替换你自己的内网穿透
$(function () {
let count = 0;
//获取二维码参数
$.get('https://34i33045l8.oicp.vip/weChat/getQrCode', function (res) {
//生成二维码
$('#qrcode').qrcode(res.url);
// 轮训获取用户扫码登陆状态
let task = setInterval(function () {
$.post('https://34i33045l8.oicp.vip/weChat/checkLogin', {ticket: res.ticket}, function ({code,msg}) {
console.log(code);
if (code === 200) { // 扫码并且关注成功
clearInterval(task)
location.href = 'http://yby6.com'
} else if (code === 201) { // 扫码成功
$("#msg").text(msg);
document.querySelector("#msg").style.display = "block"
} else {
}
count ++;
})
}, 2000)
})
})
最后操作流程

注: 前端记得整扫码超时!
从零玩转第三方登录之WeChat公众号扫码关注登陆 -wechatgzh的更多相关文章
- 从零玩转第三方登录之QQ登录
从零玩转第三方登录之QQ登录 前言 在真正开始对接之前,我们先来聊一聊后台的方案设计.既然是对接第三方登录,那就免不了如何将用户信息保存.首先需要明确一点的是,用户在第三方登录成功之后, 我们能拿到的 ...
- 初涉扫码登录:edusoho实现客户端扫码登录(简版)
一.项目简介及需求 edusoho是一套商业版的在线教育平台,项目本身基于symfony2框架开发,现在有一款自己的APP,要求在不多修改edusoho自身代码的基础上,实现客户端对PC端扫码登录.不 ...
- 微信第三方平台开头篇--MVC代码(第三方获取ticket和公众号授权)
微信公众号授权给开放平台 公众号授权给第三方平台的技术实现流程比较简单 这个步骤遗漏了开头获取第三方平台自己的accessToken 先说下流程 如何注册开放平台的第三方信息看截图 其他不说了,此文只 ...
- dedecms织梦第三方登录插件-QQ登录、微博登录、微信登录
织梦程序集成第三方QQ登录.微博登录.微信登录,获取QQ.微博.微信,并存储至数据库,一键注册为网站会员,不用再次填写绑定信息,方便粘贴用户更强. 织梦第三方登录效果 第三方登录插件特点 1.所有文件 ...
- 第三方登录:微信扫码登录(OAuth2.0)
1.OAuth2.0 OAuth(开放授权)是一个开放标准,允许用户让第三方应用访问该用户在某一网站上存储的私密的资源(如照片,视频,联系人列表),而无需将用户名和密码提供给第三方应用. 允许用户提供 ...
- php微信开放平台--第三方网页微信扫码登录(OAuth2.0)
第一.OAuth2.0 OAuth(开放授权)是一个开放标准,允许用户让第三方应用访问该用户在某一网站上存储的私密的资源(如照片,视频,联系人列表),而无需将用户名和密码提供给第三方应用. 允许用户提 ...
- 使用 Abp.Zero 搭建第三方登录模块(一):原理篇
第三方登录是基于用户在第三方平台上(如微信,QQ, 百度)已有的账号来快速完成系统的登录.注册-登录等功能. 微信的鉴权 以微信的鉴权为例: 假如你的网站有一个扫码登录的功能,会弹出一个由微信提供的 ...
- C#开发微信门户及应用(41)--基于微信开放平台的扫码登录处理
在现今很多网站里面,都使用了微信开放平台的扫码登录认证处理,这样做相当于把身份认证交给较为权威的第三方进行认证,在应用网站里面可以不需要存储用户的密码了.本篇介绍如何基于微信开放平台的扫码进行网站的登 ...
- 微信开放平台开发——网页微信扫码登录(OAuth2.0)
1.OAuth2.0 OAuth(开放授权)是一个开放标准,允许用户让第三方应用访问该用户在某一网站上存储的私密的资源(如照片,视频,联系人列表),而无需将用户名和密码提供给第三方应用. 允许用户提供 ...
- 微信开放平台 公众号第三方平台开发 教程四 代公众号调用接口的SDK和demo
原文:微信开放平台 公众号第三方平台开发 教程四 代公众号调用接口的SDK和demo 教程导航: 微信开放平台 公众号第三方平台开发 教程一 平台介绍 微信开放平台 公众号第三方平台开发 教程二 创建 ...
随机推荐
- 循序渐进介绍基于CommunityToolkit.Mvvm 和HandyControl的WPF应用端开发(3)--自定义用户控件
在我们创建界面元素的时候,不管在Vue3+ElementPlus的前端上,还是Winform桌面端上,都是会利用自定义用户控件来快速重用一些自定义的界面内容,对自定义用户控件的封装处理,也是我们开发W ...
- Solution -「洛谷 P3267」「JLOI 2016」「SHOI 2016」侦察守卫
Description Link. 给你一棵树,放置守卫在某个点上面需要一定代价和一定的有效范围.让你覆盖若干指定点,求最小代价 Solution 算法标签: $\ \ \ \ \ \ \ \ \ $ ...
- SQL连接符Left Join小实例
在一数据移植项目中,Left Join的应用 项目要求根据卡号获取最终用户号,规则如下: 1.根据card查询tbl_TestA表,获取userid,根据userid作为id查询tbl_TestB获 ...
- ASP.NET 6启动时自动创建MongoDB索引
大家好,我是Edison. 最近,在使用MongoDB时,碰到这样的一个需求:针对某个Collection手动在开发环境创建了索引,但在测试环境和生产环境不想再手动操作了,于是就想着通过代码的方式在A ...
- Django框架项目——BBS项目介绍、表设计、表创建同步、注册、登录功能、登录功能、首页搭建、admin、头像、图片防盗、个人站点、侧边栏筛选、文章的详情页、点赞点踩、评论、后台管理、添加文章、头像
文章目录 1 BBS项目介绍.表设计 项目开发流程 表设计 2 表创建同步.注册.登录功能 数据库表创建及同步 注册功能 登陆功能 3 登录功能.首页搭建.admin.头像.图片防盗.个人站点.侧边栏 ...
- OTOCI 题解
OTOCI 题目大意 给定 \(n\) 个带权的点,需要进行四种操作:查询两点连通性:加边:修改点权:查询两点路径的权值和. 思路分析 首先观察题目,我们会发现,在所有的操作结束后,所有的点构成一个森 ...
- 从0到1实现 OpenTiny 组件库跨框架技术
本文分享自华为云社区<从0到1实现 OpenTiny 组件库跨框架技术>,作者:华为云社区精选 . 在华为云<DTSE Tech Talk>技术直播第44期<0基础玩转 ...
- js滚动条滚动到底部和顶部
<!DOCTYPE html> <html> <head> <title></title> <style type="tex ...
- ExtJS的使用方法汇总(1)——配置和表格控件使用
在网上差一些关于ExtJS的相关资料,看到这篇博客写的不错,拿出来分享一下! 博客文章:ExtJS的使用方法汇总(1)--配置和表格控件使用 ExtJS的使用方法汇总(2)- ...
- c# 使用打印机打印并设置打印位置及宽高
1.在界面中使用自带的控件printDocument 2.将以下函数绑定到控件的PrintPage事件 private void printDocument1_PrintPage(object sen ...