使用 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.一张 ...
随机推荐
- Midjourney|文心一格prompt教程[Text Prompt(下篇)]:游戏、实物、人物、风景、动漫、邮票、海报等生成,终极模板教学
Midjourney|文心一格prompt教程[Text Prompt(下篇)]:游戏.实物.人物.风景.动漫.邮票.海报等生成,终极模板教学 场景6:游戏 Prompt 真的越长越好吗? 按照 Mi ...
- C/C++ 使用CRC检测磁盘文件完整性
当软件被开发出来时,为了增加软件的安全性,防止被破解,通常情况下都会对自身内存或磁盘文件进行完整性检查,以防止解密者修改程序,我们可以将exe与dll文件同时做校验,来达到相互认证的目的,解密者想要破 ...
- Linux基础命令 [补档-2023-06-28]
Linux基础命令 1-1.命令的基本格式 Linux系统命令的通用格式为: command [-options] [parameter] 其中 -command 命令本身 -op ...
- ::v-deep样式穿透
//如果不加样式穿透,vue永远会在input后面加唯一样式字段data-v-1d9b105c //::v-deep拼在哪个位置,哪个位置就有唯一标识data-v-1d9b105c .divBox : ...
- SpringSecurity使用步骤
一.导入jar包(使用maven构建项目导入其坐标) <dependency> <groupId>org.springframework.security</groupI ...
- vue-cropper 移动端上传图片压缩裁剪
头像裁剪压缩上传流程: 点击头像--判断是否为IOS端--若是--A,否则--BA:选择图片 --CB:弹框供用户选择从相册选择或者调用相机拍照--选择图片--CC:出现cropper裁剪框,裁剪框位 ...
- Git企业开发控制理论和实操-从入门到深入(一)|为什么需要Git|Git的安装
前言 那么这里博主先安利一些干货满满的专栏了! 首先是博主的高质量博客的汇总,这个专栏里面的博客,都是博主最最用心写的一部分,干货满满,希望对大家有帮助. 高质量博客汇总https://blog.cs ...
- SpringBoot中单元测试如何对包含AopContext.currentProxy()的方法进行测试
今天在工作中遇到一个问题,一个Service类中有一个方法,其中使用了 AopContext.currentProxy() 去访问自身的函数,例如 int result = ((OrderServic ...
- 一键跳转组件所在的文件与具体行数,提升排查效率,分享几个 React Developer Tools 使用小技巧
壹 ❀ 引 React Developer Tools对于很多开发同学可能就是一个检查组件props传递对不对的工具,但事实上它的功能比我们想象的强大.比如我们日常排查问题,常常会遇到想知道某个页面某 ...
- CSS浮动&定位&布局
浮动简介 浮动最早起设计出来是为了实现文字环绕图片或者文字环绕的效果,现在浮动是主流的页面布局方式之一 float:浮动属性,值可以是left.right对应向左和向右浮动 元素浮动之后的特点 脱离文 ...