使用 Abp.Zero 搭建第三方登录模块(二):服务端开发
微信SDK库的集成
微信SDK库是针对微信相关 API 进行封装的模块 ,目前开源社区中微信SDK库数量真是太多了,我选了一个比较好用的EasyAbp WeChat库。
EasyAbp/Abp.WeChat: Abp 微信 SDK 模块,包含对微信小程序、公众号、企业微信、开放平台、第三方平台等相关接口封装。 (github.com)
当然这个库是ABP vNext 框架的,需要稍微改写一下。封装好后我们需要以下几个接口
小程序码生成接口:
public Task<GetUnlimitedACodeResponse> GetUnlimitedACodeAsync(string scene, string page = null, short width = 430, bool autoColor = false, LineColorModel lineColor = null, bool isHyaline = false)

获取用户OpenId与SessionKey的接口
public async Task<Code2SessionResponse> Code2SessionAsync(string jsCode, string grantType = "authorization_code")

第三方登录模块
Abp.Zero 第三方登录机制
我们先来回顾一下第三方登录在Abp.Zero 中的实现方式
AbpUserLogin表中存储第三方账户唯一Id和系统中的User的对应关系,如图

编辑
在登陆时,在ExternalAuthenticate方法中,需要传递登录凭证,也就是ProviderAccessCode,这是一个临时凭据,根据它拿到并调用对应Provider的GetUserInfo方法,获取第三方登录信息,包括第三方账户唯一Id
之后调用GetExternalUserInfo,它会去AbpUserLogin表中根据第三方账户唯一Id查找是否有已注册的用户
若有,直接返回这个用户信息;
若没有,则先注册一个用户,插入对应关系。并返回用户。
接下来就是普通登录的流程:验证用户状态,验证密码,插入登录信息等操作。
编写代码
appsettings.json中添加微信小程序的配置,配置好AppId和AppSecret
...
"WeChat": {
"MiniProgram": {
"Token": "",
"OpenAppId": "",
"AppId": "000000000000000000",
"AppSecret": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
"EncodingAesKey": ""
}
}
...

新建一个WeChatAuthProvider 并继承于ExternalAuthProviderApiBase,编写登录
internal class WeChatAuthProvider : ExternalAuthProviderApiBase
{
private readonly LoginService loginService;
public WeChatAuthProvider(LoginService loginService)
{
this.loginService = loginService;
}
public override async Task<ExternalAuthUserInfo> GetUserInfo(string accessCode)
{
var result = new ExternalAuthUserInfo();
var weChatLoginResult = await loginService.Code2SessionAsync(accessCode);
//小程序调用获取token接口 https://api.weixin.qq.com/cgi-bin/token 返回的token值无法用于网页授权接口!
//tips:https://www.cnblogs.com/remon/p/6420418.html
//var userInfo = await loginService.GetUserInfoAsync(weChatLoginResult.OpenId);
var seed = Guid.NewGuid().ToString("N").Substring(0, 7);
result.Name = seed;
result.UserName = seed;
result.Surname = "微信用户";
result.ProviderKey = weChatLoginResult.OpenId;
result.Provider = nameof(WeChatAuthProvider);
return result;
}
}

WebCore项目中,将WeChatAuthProvider 注册到Abp.Zero第三方登录的Providers中
微信的AppId和AppSecret分别对应ClientId,ClientSecret
private void ConfigureExternalAuth()
{
IocManager.Register<IExternalAuthConfiguration, ExternalAuthConfiguration>();
var externalAuthConfiguration = IocManager.Resolve<IExternalAuthConfiguration>();
var appId = _appConfiguration["WeChat:MiniProgram:AppId"];
var appSecret = _appConfiguration["WeChat:MiniProgram:AppSecret"];
externalAuthConfiguration.Providers.Add(new ExternalLoginProviderInfo(
nameof(WeChatAuthProvider), appId, appSecret, typeof(WeChatAuthProvider))
);
);
}

改写TokenAuthController.cs 中的ExternalAuthenticate方法
private async Task<ExternalAuthenticateResultModel> ExternalAuthenticate(ExternalAuthenticateModel model)
{
var externalUser = await GetExternalUserInfo(model);
//将openId传给ProviderKey
model.ProviderKey = externalUser.ProviderKey;
var loginResult = await _logInManager.LoginAsync(new UserLoginInfo(model.AuthProvider, model.ProviderKey, model.AuthProvider), GetTenancyNameOrNull());
switch (loginResult.Result)
{
case AbpLoginResultType.Success:
{
var accessToken = CreateAccessToken(CreateJwtClaims(loginResult.Identity));
return new ExternalAuthenticateResultModel
{
AccessToken = accessToken,
EncryptedAccessToken = GetEncryptedAccessToken(accessToken),
ExpireInSeconds = (int)_configuration.Expiration.TotalSeconds
};
}
case AbpLoginResultType.UnknownExternalLogin:
{
var newUser = await RegisterExternalUserAsync(externalUser);
if (!newUser.IsActive)
{
return new ExternalAuthenticateResultModel
{
WaitingForActivation = true
};
}
// Try to login again with newly registered user!
loginResult = await _logInManager.LoginAsync(new UserLoginInfo(model.AuthProvider, model.ProviderKey, model.AuthProvider), GetTenancyNameOrNull());
if (loginResult.Result != AbpLoginResultType.Success)
{
throw _abpLoginResultTypeHelper.CreateExceptionForFailedLoginAttempt(
loginResult.Result,
model.ProviderKey,
GetTenancyNameOrNull()
);
}
return new ExternalAuthenticateResultModel
{
AccessToken = CreateAccessToken(CreateJwtClaims(loginResult.Identity)),
ExpireInSeconds = (int)_configuration.Expiration.TotalSeconds
};
}
default:
{
throw _abpLoginResultTypeHelper.CreateExceptionForFailedLoginAttempt(
loginResult.Result,
model.ProviderKey,
GetTenancyNameOrNull()
);
}
}
}

改写TokenAuthController.cs 中的GetExternalUserInfo方法
private async Task<ExternalAuthUserInfo> GetExternalUserInfo(ExternalAuthenticateModel model)
{
var userInfo = await _externalAuthManager.GetUserInfo(model.AuthProvider, model.ProviderAccessCode);
//if (userInfo.ProviderKey != model.ProviderKey)
//{
// throw new UserFriendlyException(L("CouldNotValidateExternalUser"));
//}
return userInfo;
}

鉴权状态验证模块
整个鉴权登录的过程我们需要维护鉴权状态(Status),在获取到登录凭证AccessCode 后及时写入值。
鉴权状态将有:
CREATED: 已建立,等待用户扫码
ACCESSED: 已扫码,等待用户确认授权
AUTHORIZED: 已授权完成
EXPIRED: 小程序码过期,已失效
我们需要建立一个缓存,来存储上述值
建立WechatMiniappLoginTokenCacheItem, 分别创建Status属性和ProviderAccessCode
public class WechatMiniappLoginTokenCacheItem
{
public string Status { get; set; }
public string ProviderAccessCode { get; set; }
}

建立缓存类型WechatMiniappLoginTokenCache。
public class WechatMiniappLoginTokenCache : MemoryCacheBase<WechatMiniappLoginTokenCacheItem>, ISingletonDependency
{
public WechatMiniappLoginTokenCache() : base(nameof(WechatMiniappLoginTokenCache))
{
}
}

在Domain项目中新建MiniappManager类作为领域服务,并注入ACodeService微信小程序码生成服务 和WechatMiniappLoginTokenCache缓存对象
public class MiniappManager : DomainService
{
private readonly ACodeService aCodeService;
private readonly WechatMiniappLoginTokenCache wechatMiniappLoginTokenCache;
public MiniappManager(ACodeService aCodeService,
WechatMiniappLoginTokenCache wechatMiniappLoginTokenCache)
{
this.aCodeService=aCodeService;
this.wechatMiniappLoginTokenCache=wechatMiniappLoginTokenCache;
}
...
}

分别建立SetTokenAsync,GetTokenAsync和CheckTokenAsync,分别用于设置Token对应值,获取Token对应值和Token对应值合法性校验
public virtual async Task<WechatMiniappLoginTokenCacheItem> GetTokenAsync(string token)
{
var cacheItem = await wechatMiniappLoginTokenCache.GetAsync(token, null);
return cacheItem;
}
public virtual async Task SetTokenAsync(string token, string status, string providerAccessCode, bool isCheckToken = true, DateTimeOffset? absoluteExpireTime = null)
{
if (isCheckToken)
{
await this.CheckTokenAsync(token);
}
await wechatMiniappLoginTokenCache.SetAsync(token, new WechatMiniappLoginTokenCacheItem()
{
Status=status,
ProviderAccessCode=providerAccessCode
}, absoluteExpireTime: absoluteExpireTime);
}
public virtual async Task CheckTokenAsync(string token)
{
var cacheItem = await wechatMiniappLoginTokenCache.GetAsync(token, null);
if (cacheItem == null)
{
throw new UserFriendlyException("WechatMiniappLoginInvalidToken",
"微信小程序登录Token不合法");
}
else
{
if (cacheItem.Status=="AUTHORIZED")
{
throw new UserFriendlyException("WechatMiniappLoginInvalidToken",
"微信小程序登录Token已失效");
}
}
}

编写GetACodeAsync,生成微信小程序码
public async Task<byte[]> GetACodeAsync(string token, string page, DateTimeOffset? absoluteExpireTime = null)
{
await wechatMiniappLoginTokenCache.SetAsync(token, new WechatMiniappLoginTokenCacheItem()
{
Status="CREATED",
},
absoluteExpireTime: absoluteExpireTime);
var result = await aCodeService.GetUnlimitedACodeAsync(token, page);
return result.BinaryData;
}

生成后会将Token值写入缓存,此时状态为CREATED,对应的页面为“已扫码”
之后以byte[]方式返回小程序码图片
编写Api接口
在Application项目中新建MiniappAppService 类作为领域服务,并注入MiniappManager对象
[AbpAllowAnonymous]
//[AbpAuthorize(PermissionNames.Pages_Wechat)]
public class MiniappAppService : AppServiceBase
{
public static TimeSpan TokenCacheDuration = TimeSpan.FromMinutes(5);
public static TimeSpan AuthCacheDuration = TimeSpan.FromMinutes(5);
private readonly MiniappManager miniappManager;
public MiniappAppService(MiniappManager miniappManager)
{
this.miniappManager=miniappManager;
}
...
}

编写各方法
[HttpGet]
[WrapResult(WrapOnSuccess = false, WrapOnError = false)]
public async Task<IActionResult> GetACodeAsync(GetACodeAsyncInput input)
{
var mode = input.Mode;
var result = await miniappManager.GetACodeAsync(input.Scene, input.Page, DateTimeOffset.Now.Add(TokenCacheDuration));
return new FileContentResult(result, MimeTypeNames.ImagePng);
}
[HttpGet]
[AbpAllowAnonymous]
public virtual async Task<WechatMiniappLoginTokenCacheItem> GetTokenAsync(string token)
{
var cacheItem = await miniappManager.GetTokenAsync(token);
return cacheItem;
}
[AbpAllowAnonymous]
public virtual async Task AccessAsync(string token)
{
await miniappManager.SetTokenAsync(token, "ACCESSED", null, true, DateTimeOffset.Now.Add(AuthCacheDuration));
}
[AbpAllowAnonymous]
public virtual async Task AuthenticateAsync(ChangeStatusInput input)
{
await miniappManager.SetTokenAsync(input.Token, "AUTHORIZED", input.ProviderAccessCode, true, DateTimeOffset.Now.Add(TimeSpan.FromMinutes(1)));
}

GetACodeAsync:获取小程序码Api,
GetTokenAsync:获取Token对应值Api,
AccessAsync:已扫码调用的Api,
AuthenticateAsync:已授权调用的Api,

编辑
至此,完成了所有服务端接口
使用 Abp.Zero 搭建第三方登录模块(二):服务端开发的更多相关文章
- 使用 Abp.Zero 搭建第三方登录模块(一):原理篇
第三方登录是基于用户在第三方平台上(如微信,QQ, 百度)已有的账号来快速完成系统的登录.注册-登录等功能. 微信的鉴权 以微信的鉴权为例: 假如你的网站有一个扫码登录的功能,会弹出一个由微信提供的 ...
- 使用 Abp.Zero 搭建第三方登录模块(三):网页端开发
简短回顾一下网页端的流程,总的来说网页端的职责有三: 生成一个随机字符作为鉴权会话的临时Token, 生成一个小程序码, Token作为参数固化于小程序码当中 监控整个鉴权过程状态,一旦状态变为AU ...
- Day01_搭建环境&CMS服务端开发
学成在线 第1天 讲义-项目概述 CMS接口开发 1 项目的功能构架 1.1 项目背景 受互联网+概念的催化,当今中国在线教育市场的发展可谓是百花齐放.如火如荼. 按照市场领域细分为:学前教育.K12 ...
- Swift3.0服务端开发(一) 完整示例概述及Perfect环境搭建与配置(服务端+iOS端)
本篇博客算是一个开头,接下来会持续更新使用Swift3.0开发服务端相关的博客.当然,我们使用目前使用Swift开发服务端较为成熟的框架Perfect来实现.Perfect框架是加拿大一个创业团队开发 ...
- node.js中ws模块创建服务端和客户端,网页WebSocket客户端
首先下载websocket模块,命令行输入 npm install ws 1.node.js中ws模块创建服务端 // 加载node上websocket模块 ws; var ws = require( ...
- Photon Server 实现注册与登录(二) --- 服务端代码整理
一.有的代码前端和后端都会用到.比如一些请求的Code.使用需要新建项目存放公共代码. 新建项目Common存放公共代码: EventCode :存放服务端自动发送信息给客户端的code Operat ...
- go语言游戏服务端开发(二)——网络通信
一.网络层 网络游戏客户端除了全局登录使用http请求外,一般通过socket长连接与服务端保持连接.go语言的net包提供网络socket长连接相关操作. 对于服务端,一般经历 Listen.Acc ...
- 使用.NET开发搭建OpenAI模型的中间服务端
前言:前不久微信上大家玩ChatGPT聊天机器人玩的不亦乐乎:不过随着ChatGPT被封杀,所以用微信聊天机器人有可能导致封号的风险.那如果自己不想每次都去OpenAI官网上进行对话[PS:官网上面聊 ...
- Swift3.0服务端开发(二) 静态文件添加、路由配置以及表单提交
今天博客中就来聊一下Perfect框架的静态文件的添加与访问,路由的配置以及表单的提交.虽然官网上有聊静态文件的访问的部分,但是在使用Perfect框架来访问静态文件时还是有些点需要注意的,这些关键点 ...
- MVC系列学习(十二)-服务端的验证
在前一讲,提到过,客户端的东西永远可以造假,所以我们还要在服务端进行验证 注意:先加载表单,后添加js文件,才能有效:而先加载js,后添加表单,是没有效果的 1.视图与Model中的代码如下 2.一张 ...
随机推荐
- 从零开始配置 vim(12)——主题配置
在我们进一步增强vim的功能之前,我们先为vim准备一个漂亮的主题,毕竟对着一个丑陋原始的界面多少有点提不起劲来进行编程.长时间对着丑陋的界面多多少少会产生抑郁情绪的.下面推荐几款我觉得还不错的主题插 ...
- 利用显卡的SR-IOV虚拟GPU技术,实现一台电脑当七台用
背景 虚拟桌面基础设施(VDI)技术一般部署在服务器,可以实现多个用户连接到服务器上的虚拟桌面.随着桌面计算机性能的日益提升,桌面计算机在性能在很多场景下已经非常富余,足够同时满足多个用户同时使用的需 ...
- 2.3 实验:用linxerUnpack进行通用脱壳--《恶意代码分析实战》
Lab01-03.exe 实验内容: 1.将文件上传到http://www.VirusTotal.com 进行分析并查看报告.文件匹配到了已有的反病毒软件特征吗? 2.是否有这个文件被 ...
- 用superxmlparser.pas的XMLParseString----XML转Json注意
了解XML转成Json时候用的时候多了个#号: ---------------------------------------------------------------------------- ...
- 《ASP.NET Core 微服务实战》-- 读书笔记(第3章)
第 3 章 使用 ASP.NET Core 开发微服务 微服务定义 微服务是一个支持特定业务场景的独立部署单元.它借助语义化版本管理.定义良好的 API 与其他后端服务交互.它的天然特点就是严格遵守单 ...
- NC200324 魔改森林
题目链接 题目 题目描述 曾经有一道叫做迷雾森林的题目,然而牛牛认为地图中的障碍太多,实在是太难了,所以删去了很多点,出了这道题. 牛牛给出了一个n行m列的网格图 初始牛牛处在最左下角的格点上(n+1 ...
- NC16681 [NOIP2003]加分二叉树
题目链接 题目 题目描述 设一个n个节点的二叉树tree的中序遍历为(l,2,3,-,n),其中数字1,2,3,-,n为节点编号.每个节点都有一个分数(均为正整数),记第j个节点的分数为di,tr ...
- SATA 学习笔记——Frame/Primitive解析
一.故事前传 我们之前说到Link layer的结构,link layer的作用大致可以包括以下几点: Frame flow control CRC的生成与检测(已解析,详细见历史文章) 对数据与控制 ...
- centos7源码方式安装zabbix-4.0
1.关闭防火墙 systemctl stop firewalld.service #临时关闭firewall systemctl disable firewalld.service #禁止firewa ...
- virtualapp 应用启动源码分析
应用启动源码分析 在HomeActvity中的OnCreate方法会调用initLaunchpad private void initLaunchpad() { mLauncherView.setHa ...