基于IdentityServer的系统对接微信公众号

业务需求

公司有两个业务系统,A和B,AB用户之间属于多对一的关系,数据库里面也就是两张表,A表有个外键指向B。现在需要实现以下几个功能。

  1. A用户扫描B的二维码,填写相关的注册信息,注册完成之后自动属于B。也就是表A的外加字段指向B。

  2. 老用户和微信openid绑定。

  3. 用户在公众号里面自动登录。

项目结构

公司项目基于.net core 2.1 + Vue,

后端有以下几个子系统:

  1. 基于IdentityServer4 的asp.net mvc,简称account 项目,配了域名account.xxx.com

  2. 两个业务系统api。A和B。分别域名配置aapi.xxx.com 和bapi.xxx.com

  3. 其他。。

前端有以下几个系统,都是基于Vue的SPA:

  1. A业务系统,域名a.xxx.com

  2. B业务系统,域名b.xxx.com

  3. 其他。

登录这块的逻辑实现方式是类似的。都是基于IdentityModel/oidc-client-js

简单介绍一下IdentityServer这个东西。

用户登录A或B系统,就是调用A和B对应的webapi,webapi配置了自己的验证服务器是account服务器,account验证未通过,前端就得到401状态码,通过oidc-client-js的内部方法引导用户进行登录。跳转account的页面,用户输入用户名密码,登录成功,account服务器判断是A or B过来的登录请求,带上token回跳到配置的对应页面。业务系统再次用获取到的token请求api,调用成功。用一个图来说。

640?wx_fmt=png

实现方法

用了盛派微信sdk,特别感谢大佬的贡献。

推荐一下微信沙箱环境,项目做完下来除了"无法在测试的公众号里面推送小程序消息”无法实现之外(因为推送的需要公众号和小程序有一个绑定关系),其他都ok。

因为版本的关系,account系统升级了asp.net core 2.2。

先实现上面第一个需求

这里用到微信里面生成带参数的二维码功能。B系统创建了用户之后,生成一个对应的guid,然后把这个guid作为参数,调用sdk就能得到二维码的url。


var qrRstTicketRst = await QrCodeApi.CreateAsync(weixinSetting.WeixinAppId, 30, 100000,
QrCode_ActionName.QR_LIMIT_STR_SCENE, sceneId);

var url = QrCodeApi.GetShowQrCodeUrl(qrRstTicketRst.ticket);

这里我们项目中用到的是永久二维码,虽然这个二维码上限10W个,我们业务系统B用户不会超过那么多。

B用户展示二维码给A用户,A用户扫描,根据文档:

如果用户还未关注公众号,则用户可以关注公众号,关注后微信会将带场景值关注事件推送给开发者。

如果用户已经关注公众号,在用户扫描后会自动进入会话,微信也会将带场景值扫描事件推送给开发者。

触发代码里面分别对应的是OnEvent_SubscribeRequest 和OnEvent_ScanRequest,两个方法里面的代码基本上是一样的。RequestMessageEvent_Scan.EventKey可以得到上面的guid值。Subscribe事件里面得到的EventKey会比Scan的多一个qrscene前缀,处理的时候要注意一点。两个方法参数都能通过FromUserName获取到扫描的用户的openId,然后在这个方法里面返回一个带参数(A的openId,和B的guid)的注册链接,A用户注册的时候就提交了这两个参数,后台就能拿到。

顺道说一句,公众号里面用户每次操作只能被动返回一条消息。如要主动推送,需要用模板消息的方式。

实现第二个需求

对于老用户,这里需要一个账号绑定的功能。也就是业务系统的账号和openId做一个关联。绑定的关键在于这个如何获取这个openId,这里有两种方式。

  1. 用户点击公众号的菜单,后端获取到这个事件,在OnEvent_ClickRequest中,判断RequestMessageEvent_Click.EventKey==xxx,返回一个带openId的绑定页面的链接给用户。比如/bind?openId=xxx,用户点击这个链接,系统引导用户登录,然后点击绑定按钮,实现绑定。

  2. 基于微信网页授权,这个在自动登录里面也用到了,所以下面解释。

实现第三个需求

系统中用户和微信的openId已经绑定,所以,只要知道每次访问页面的openId就应该能实现自动登录。openId是通过微信网页授权的方式获取到。流程可以看文档。简单来说,先拿code,再换token,同时拿到openId。实现步骤分以下几步。

  • 添加一个公众号菜单,type是view,也就是点了之后会打开一个页面,页面地址直接用获取code的url.

{
"type": "view",
"name": "登录A",
"url":"https://open.weixin.qq.com/connect/oauth2/authorize?appid=wxb66259f2a353&redirect_uri=http%3A%2F%2Faccount.xxx.cn%2Fweixincallback%2Fcallback&response_type=code&scope=snsapi_base&state=spa.A#wechat_redirect"
}

url中的state参数会和code一起返回给设置的redirectUrl,这个可以用来给我们在account登录中心判断是需要登录A还是B,以便最后回跳到对应的业务页面。

这里在沙箱配置跳转域名的时候注意一下,只要写域名就好。

640?wx_fmt=png

  • 打开页面,需要用户点允许授权。通过之后浏览器会把code 和 state参数带这get请求redirect_url

  • callback页面的逻辑

callback 接收code和state两个参数。

public async Task<IActionResult> Callback(string code, string state){...}

用这个code调用sdk里面的api获取token,同时可以拿到openid。

var tokenResult = await OAuthApi.GetAccessTokenAsync(AppId, AppSecret, code);
if (tokenResult.errcode != ReturnCode.请求成功)
{
throw new BizException("获取微信用户信息失败");
}
var openId = tokenResult.openid;

通过open获取用户信息。

var userInfo = await userService.GetByOpenId(openId);

然后调用HttpContext.SignInAsync登录。

public static async Task SignInAsync(this HttpContext context, string subject, string name, AuthenticationProperties properties, params Claim[] claims)
{
var clock = context.GetClock();
var user = new IdentityServerUser(subject)
{
DisplayName = name,
AdditionalClaims = claims,
AuthenticationTime = clock.UtcNow.UtcDateTime
};
await context.SignInAsync(user, properties);
}

HttpContext是当前请求的上下文。

subject可以理解为用户的标识。

name可以理解是用户显示的名字。

AuthenticationProperties是此次认证的一些配置,比如有效时长之类的。

Claim可以理解为这个subject带的一些属性。

await HttpContext.SignInAsync(userMobile, userName, props, claims);

调用完之后就登录成功。

然后通过带来的state参数判断需要跳转的client。

var client = await clientStore.FindClientByIdAsync(state);
return Redirect($"{client.PostLogoutRedirectUris.FirstOrDefault()}?logined=true");

这里带一个logined=true参数,用来给client做一些逻辑。

总结

首先要感谢的肯定是盛派微信sdk的contributors,没有他们系统对接起来应该会慢很多。

然后我想说,IdentityServer是个好东西,现在公司.NET相关的系统都已经用这个实现统一的登录逻辑了,系统维护的代价小了许多。

说起来其实也是第一次对接微信公众号相关的东西,在走通这条路之前走了不少弯路,不过好在走通了。希望对其他人有帮助。


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/316460.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

[TJOI2012] 旅游(树的直径)

problem 写的什么jb题意&#xff01;这个语文水平。。。。 洛谷的一堆题解看下来也没懂他们懂得题目大意&#xff0c;真是给我蚌埠住了 luogu评测链接 一句话题意&#xff1a;给定一个三角剖分&#xff0c;求任意两顶点穿过的最多三角形个数&#xff08;只经过某三角形顶点…

Python知识(4/20):Python条件判断

任何一个复杂的系统都是由三种基本结构组成&#xff1a;顺序结构、分支结构、循环结构。其中 顺序结构最简单&#xff0c;程序从上到下依次执行&#xff0c;就如同生活中一条笔直的大马路&#xff0c;一路畅行无阻&#xff1b; 分支结构是指当程序执行到某步时&#xff0c;需根…

Ocelot Api网关教程(9)- QoS

本文介绍Ocelot中的QoS(Quality of Service)&#xff0c;其使用了Polly对超时等请求下游失败等情况进行熔断。1、添加Nuget包添加 Ocelot.Provider.Polly 到OcelotGetway项目中2、修改 Startup.ConfigureServices 如下来添加Polly&#xff1a;services .AddOcelot(new Confi…

cf1553F. Pairwise Modulo

cf1553F. Pairwise Modulo 题意&#xff1a; 给你一个数组a&#xff0c;a由n个不同的数组成,让你求出对应的数组p 数组p的定义为&#xff1a; pk∑1≤i,j≤kaimodajp_{k}\sum_{1\leq i,j\leq k}a_{i} \mod a_{j}pk​∑1≤i,j≤k​ai​modaj​ 题解&#xff1a; 官方题解 首…

[ZJOI2011]营救皮卡丘(费用流 + 最短路)

problem luogu-P4542 solution 刚开始就直观感觉 dpdpdp 不动&#xff0c;却有个看似“理所当然”的贪心&#xff1a;每次跑 kkk 个人所在点到扩展据点的最短距离&#xff0c;然后让最近的人去破环那个据点。 啪啪敲完后小样例&#xff08;实在太水&#xff09;就过了&…

Deltix Round, Spring 2021 (open for everyone, rated, Div. 1 + Div. 2)

Deltix Round, Spring 2021 (open for everyone, rated, Div. 1 Div. 2) 题号题目知识点AGame of LifeBLord of the ValuesCCompression and ExpansionDLove-HateECrypto LightsFFavorite GameGTry BookingHHopping Around the Array

Abp框架准备加入.NET Foundation

Abp团队正准备尝试将Abp项目加入.NET Foundation!既然申请了,必然有很大的自信能够成功,请大家等待好消息!Abp中文网会第一时间跟进.点击阅读原文查看最新进展.

[ZJOI2011] 道馆之战(树链剖分)

problem luogu-P4679 理解清楚题意又是一个世纪的更迭了 给定一个树&#xff0c;每个节点位置上实际放了两个节点。 然后若干次修改和查询。... 能走&#xff0c;#\## 不能走。 询问 u→vu\rightarrow vu→v 的简单路径上最长能走的距离。&#xff08;是强制从 uuu 开始&a…

[NOI2008] 志愿者招募(线性规划-对偶问题-费用流)

problem luogu-P3980 solution 志愿者连续工作 [si,ti][s_i,t_i][si​,ti​] 天&#xff0c;我们可以提炼出网络流二十四题中《最长k可重区间集问题》的模型。 同样地&#xff0c;把 1∼n1\sim n1∼n 天抽象成一条 1∼n11\sim n11∼n1 个点的链条。 源点 s→1s\rightarrow…

互达的集合(线段树)

problem 给定数组 l,rl,rl,r。求有多少个非空集合 SSS&#xff0c;满足 ∀i,j∈Sli≤j≤ri\forall_{i,j\in S}\ l_i\le j\le r_i∀i,j∈S​ li​≤j≤ri​。 集合内对于任意一个点而言&#xff0c;其余点均能被自己的范围覆盖到。 n≤2e5n\le 2e5n≤2e5。 solution 分享一下…

【学习笔记】线性规划与对偶问题和LP对偶费用流([ZJOI2013]防守战线题解)

线性规划与对偶问题 原问题&#xff1a; min⁡{7x1x25x3}s.t.{x1−x23x3≥105x12x2−x3≥6xi≥0\min\{7x_1x_25x_3\} \\ s.t.\begin{cases} x_1-x_23x_3\ge 10\\ 5x_12x_2-x_3\ge 6\\ x_i\ge 0\end{cases} min{7x1​x2​5x3​}s.t.⎩⎪⎨⎪⎧​x1​−x2​3x3​≥105x1​2x2​−…

【学习笔记】DAG / 一般有向图的支配树 / 灭绝树

定义与声明 一个有向图 GGG。给定一个起点 sss&#xff0c;假设 sss 能到达所有点。 若去掉某个点 iii 后&#xff0c;sss 无法到达 jjj&#xff0c;则称 iii 为 jjj 的支配点。 显然支配点存在传递关系。 以 sss 为根&#xff0c;使得对于任意节点 iii&#xff0c;其树上祖…

[ZJOI2014] 星系调查(树上差分 + 数学推式子)

problem luogu-P3340 题面写得那么长&#xff0c;其实说白了就是求一条直线&#xff0c;使得若干个点到这条直线的距离平方的和最小&#xff0c;求这个最小值。 solution 我超爱数学&#xff0c;数学就是我的命&#xff0c;我一天不学数学我就难受&#xff01; 假设拟合出…

[ZJOI2014] 璀璨光华(bfs建图 + dfs搜索)

problem luogu-P3342 solution 你感觉这道题没考什么&#xff0c;又感觉考了什么 通过样例以及题面&#xff0c;我们并未获取到『立方体每个小方块的编号是按一定规则命名』的信息。 也就是说&#xff0c;我们需要通过输入的每个小方块相邻的编号的信息来建出这个立方体的…

cf1523A. Game of Life

cf1523A. Game of Life 题意&#xff1a; 包含n个元素的数组&#xff0c;数值为1或0&#xff0c;如果一个元素为0&#xff0c;并且其周围正好只有一个为1的元素&#xff0c;那么下一刻本元素也会变成1. 给你一个数值&#xff0c;问你m次时刻后数组的状态 题解&#xff1a; …

SignalR 中丰富多彩的消息推送方式

在上一篇 SignalR 文章中&#xff0c;演示了如何通过 SignalR 实现了简单的聊天室功能&#xff1b;本着简洁就是美的原则&#xff0c;这一篇我们也来聊聊在 SignalR 中的用户和组的概念&#xff0c;理解这些基础知识有助于更好的开发基于 SignalR 的应用&#xff0c;通过对用户…

Visual Studio 2019 使用 Live Share

一.前言Visual Studio 2019 在今天发布&#xff08;北京时间&#xff09;了&#xff0c;这次带来了一个比较有趣的 Live Share 功能&#xff0c;使用它可以进行更好的协作开发。主要功能&#xff1a;更多资料可看官方介绍&#xff1a;Visual Studio 实时共享什么是Visual Studi…

cf1523B. Lord of the Values

cf1523B. Lord of the Values 题意&#xff1a; 给你一个数组&#xff0c;有n个数&#xff0c;n为偶数&#xff0c;a1&#xff0c;a2…an 现在有两个操作&#xff1a; 对于i<j 操作1&#xff1a;aiaiaj 操作2&#xff1a;ajaj-ai 把原数组转换为-a1,-a2,-a3… 题解&#…

Asp.Net Core WebAPI使用Swagger时API隐藏与分组

1、前言为什么我们要隐藏部分接口&#xff1f;因为我们在用swagger代替接口的时候&#xff0c;难免有些接口会直观的暴露出来&#xff0c;比如我们结合Consul一起使用的时候&#xff0c;会将健康检查接口以及报警通知接口暴露出来&#xff0c;这些接口有时候会出于方便考虑&…

cf1526E. Oolimry and Suffix Array(未解决)

E. Oolimry and Suffix Array 题意&#xff1a; 给定n&#xff0c;k和长度为n的后缀数组si(0<si<n-1),求长度为n的由k种字母构成的字符串种&#xff0c;后缀数组为si的有多少种 题解&#xff1a; 题解看懂了一半。。。等会了再更新 代码&#xff1a;