公司有两个业务系统,A和B,AB用户之间属于多对一的关系,数据库里面也就是两张表,A表有个外键指向B。如今须要实现如下几个功能。前端
公司项目基于.net core 2.1 + Vue,git
后端有如下几个子系统:github
前端有如下几个系统,都是基于Vue的SPA:web
登陆这块的逻辑实现方式是相似的。都是基于IdentityModel/oidc-client-js数据库
简单介绍一下IdentityServer这个东西。json
用户登陆A或B系统,就是调用A和B对应的webapi,webapi配置了本身的验证服务器是account服务器,account验证未经过,前端就获得401状态码,经过oidc-client-js的内部方法引导用户进行登陆。跳转account的页面,用户输入用户名密码,登陆成功,account服务器判断是A or B过来的登陆请求,带上token回跳到配置的对应页面。业务系统再次用获取到的token请求api,调用成功。用一个图来讲。小程序
用了盛派微信sdk,特别感谢大佬的贡献。c#
推荐一下微信沙箱环境,项目作完下来除了"没法在测试的公众号里面推送小程序消息”没法实现以外(由于推送的须要公众号和小程序有一个绑定关系),其余都ok。后端
由于版本的关系,account系统升级了asp.net core 2.2。api
这里用到微信里面生成带参数的二维码功能。B系统建立了用户以后,生成一个对应的guid,而后把这个guid做为参数,调用sdk就能获得二维码的url。
//建立ticket var qrRstTicketRst = await QrCodeApi.CreateAsync(weixinSetting.WeixinAppId, 30, 100000, QrCode_ActionName.QR_LIMIT_STR_SCENE, sceneId); //经过ticket获取二维码对应的url 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,这里有两种方式。
OnEvent_ClickRequest
中,判断RequestMessageEvent_Click.EventKey==xxx
,返回一个带openId的绑定页面的连接给用户。好比/bind?openId=xxx,用户点击这个连接,系统引导用户登陆,而后点击绑定按钮,实现绑定。系统中用户和微信的openId已经绑定,因此,只要知道每次访问页面的openId就应该能实现自动登陆。openId是经过微信网页受权的方式获取到。流程能够看文档。简单来讲,先拿code,再换token,同时拿到openId。实现步骤分如下几步。
{ "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,以便最后回跳到对应的业务页面。
这里在沙箱配置跳转域名的时候注意一下,只要写域名就好。
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);//userService是本身的业务service
而后调用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相关的系统都已经用这个实现统一的登陆逻辑了,系统维护的代价小了许多。
提及来其实也是第一次对接微信公众号相关的东西,在走通这条路以前走了很多弯路,不过好在走通了。但愿对其余人有帮助。