微信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 搭建第三方登录模块(二):服务端开发的更多相关文章

  1. 使用 Abp.Zero 搭建第三方登录模块(一):原理篇

    ​第三方登录是基于用户在第三方平台上(如微信,QQ, 百度)已有的账号来快速完成系统的登录.注册-登录等功能. 微信的鉴权 以微信的鉴权为例: 假如你的网站有一个扫码登录的功能,会弹出一个由微信提供的 ...

  2. 使用 Abp.Zero 搭建第三方登录模块(三):网页端开发

    ​简短回顾一下网页端的流程,总的来说网页端的职责有三: 生成一个随机字符作为鉴权会话的临时Token, 生成一个小程序码, Token作为参数固化于小程序码当中 监控整个鉴权过程状态,一旦状态变为AU ...

  3. Day01_搭建环境&CMS服务端开发

    学成在线 第1天 讲义-项目概述 CMS接口开发 1 项目的功能构架 1.1 项目背景 受互联网+概念的催化,当今中国在线教育市场的发展可谓是百花齐放.如火如荼. 按照市场领域细分为:学前教育.K12 ...

  4. Swift3.0服务端开发(一) 完整示例概述及Perfect环境搭建与配置(服务端+iOS端)

    本篇博客算是一个开头,接下来会持续更新使用Swift3.0开发服务端相关的博客.当然,我们使用目前使用Swift开发服务端较为成熟的框架Perfect来实现.Perfect框架是加拿大一个创业团队开发 ...

  5. node.js中ws模块创建服务端和客户端,网页WebSocket客户端

    首先下载websocket模块,命令行输入 npm install ws 1.node.js中ws模块创建服务端 // 加载node上websocket模块 ws; var ws = require( ...

  6. Photon Server 实现注册与登录(二) --- 服务端代码整理

    一.有的代码前端和后端都会用到.比如一些请求的Code.使用需要新建项目存放公共代码. 新建项目Common存放公共代码: EventCode :存放服务端自动发送信息给客户端的code Operat ...

  7. go语言游戏服务端开发(二)——网络通信

    一.网络层 网络游戏客户端除了全局登录使用http请求外,一般通过socket长连接与服务端保持连接.go语言的net包提供网络socket长连接相关操作. 对于服务端,一般经历 Listen.Acc ...

  8. 使用.NET开发搭建OpenAI模型的中间服务端

    前言:前不久微信上大家玩ChatGPT聊天机器人玩的不亦乐乎:不过随着ChatGPT被封杀,所以用微信聊天机器人有可能导致封号的风险.那如果自己不想每次都去OpenAI官网上进行对话[PS:官网上面聊 ...

  9. Swift3.0服务端开发(二) 静态文件添加、路由配置以及表单提交

    今天博客中就来聊一下Perfect框架的静态文件的添加与访问,路由的配置以及表单的提交.虽然官网上有聊静态文件的访问的部分,但是在使用Perfect框架来访问静态文件时还是有些点需要注意的,这些关键点 ...

  10. MVC系列学习(十二)-服务端的验证

    在前一讲,提到过,客户端的东西永远可以造假,所以我们还要在服务端进行验证 注意:先加载表单,后添加js文件,才能有效:而先加载js,后添加表单,是没有效果的 1.视图与Model中的代码如下 2.一张 ...

随机推荐

  1. vim 从嫌弃到依赖(20)——global 命令

    在前面的文章中,我们介绍了如何进行查找和替换,而替换是建立在查找基础之上的一个简单的应用,它只是将匹配文本修改为另一个.那么vim中还能针对匹配上的文本做哪些操作呢?在本篇文章中我们来对这个问题进行探 ...

  2. 17.3 实现无管道反向CMD

    WSASocket无管道反向CMD,与无管道正向CMD相反,这种方式是在远程主机上创建一个TCP套接字,并绑定到一个本地地址和端口上.然后在本地主机上,使用WSASocket函数连接到远程主机的套接字 ...

  3. PXE+Kickstart 自动化部署系统

    PXE 预启动执行环境是由Intel开发的技术,可以让计算机通过网络来启动操作系统(前提是计算机上安装的网卡支持PXE技术),主要用于在无人值守安装系统中引导客户端主机安装Linux操作系统. Kic ...

  4. C/C++ 感染标志与空字节感染

    C/C++ 通过搜索PE结构中的空隙部分,对指定文件写入感染标志,作用是,如果程序被感染过则不再继续感染,而搜索空字节,则是要将恶意代码动态的填充到可执行文件中,并劫持执行流,以下代码就是这两种代码的 ...

  5. 万字剖析OpenFeign整合Ribbon实现负载均衡的原理

    大家好,前面我已经剖析了OpenFeign的动态代理生成原理和Ribbon的运行原理,这篇文章来继续剖析SpringCloud组件原理,来看一看OpenFeign是如何基于Ribbon来实现负载均衡的 ...

  6. 8、数据库学习规划:MS SQL Server - 学习规划系列文章

    微软的SQL Server数据库是笔者最先接触的数据库,虽然之前有Access,但是那个是学校里知道的,没实际去开发基于Access的程序.SQL Server发展到现在已经有很多个版本了,其功能也非 ...

  7. 十八张图带你入门实时监控系统HertzBeat

    我们经常讲:研发人员有两只眼睛,一只是监控平台,另一只是日志平台.在对性能和高可用讲究的场景里,监控平台的重要性再怎么强调也不过分. 这篇文章,我们聊聊开源实时监控告警系统 HertzBeat 赫兹跳 ...

  8. mybatis SQL in() 为什么要在 mapper.xml里 用 foreach

    结论: 若存在 in () 语句,要使用 #{} 预编译入参的方式,需要在 mapper.xml里 使用 foreach ======================================= ...

  9. Iot学习笔记记录

    前言 2024.1.13 沙青图书馆 甚至一开始打成了2023年.各位新年快乐.有时间会写下2023的年度总结.不过在此要提前开一个博客,记录一下接下来学习Iot安全的记录了.实在是再不学就要被学弟学 ...

  10. Java并发编程-CompletableFuture(上)

    大家好,我是小高先生,这篇文章我将和大家一起学习Java并发编程中很重要的一个类-CompletableFuture. 在Java的并发编程领域,Future接口一直扮演着关键的角色,它定义了一组与异 ...