一, 前言
上一篇我分享了一篇关于 ASP.NET Core 中 IdentityServer4 授权中心之应用实战 的文章, 其中有不少博友给我提了问题, 其中有一个博友问我的一个场景, 我给他解答的还不够完美, 之后我经过自己的学习查阅并阅读了相关源代码, 发现 IdentityServer4 可以实现自定义 GrantType 授权方式.
声明: 看这篇文章时如果你没有阅读我上一篇 ASP.NET Core 中 IdentityServer4 授权中心之应用实战 的文章, 那请先移步看上面的文章, 再来看这篇文章会更加清晰, 感谢支持, 感谢关注!
二, 场景模拟
上篇文章已经把电商系统从单一网关架构升级到多网关架构, 架构图如下:
然而上面的授权中心 使用的是密码授权模式, 但是对于微信小程序,
微信公众号商城
端使用的授权还不是很合适;
微信小程序和微信公众号微商城客户端的场景如下:
用户访问小程序商城或者微信公众号商城后会到微信服务端获得授权拿到相关的用户 openId,unionId,userName 等相关信息, 再携带 openId,unionId,userName 等信息访问授权中心网关, 进行授权, 如果不存在则自动注册用户, 如果存在则登录授权成功等操作. 那这个场景后我该如何改造授权中心服务网关呢? 经过研究和探讨, 我把上面的架构图细化成如下的网关架构图:
三, 授权中心改造升级
上一篇文章中我们的解决方案中已经建立了三个项目:
Jlion.NetCore.Identity.Service
: 授权中心 网关 - webApi 项目
Jlion.NetCore.Identity.UserApiService
: 用户业务网关 -WebApi 项目
Jlion.NetCore.Identity
: 基础类库, 主要用于把公共的基础设施层放到这一块
通过上面的需求场景分析, 我们目前的授权中心还不够这种需求, 故我们可以通过 IdentityServer4 自定义授权方式进行改造升级来满足上面的场景需求.
经过查看源代码我发现我们可以通过实现 IExtensionGrantValidator 抽象接口进行自定义授权方式来实现, 并且实现 ValidateAsync 方法,
现在我在之前的解决方案授权中心项目中新增 WeiXinOpenGrantValidator 类代码如下:
- public class WeiXinOpenGrantValidator : IExtensionGrantValidator
- {
- public string GrantType => GrantTypeConstants.ResourceWeixinOpen;
- public async Task ValidateAsync(ExtensionGrantValidationContext context)
- {
- try
- {
- #region 参数获取
- var openId = context.Request.Raw[ParamConstants.OpenId];
- var unionId = context.Request.Raw[ParamConstants.UnionId];
- var userName = context.Request.Raw[ParamConstants.UserName];
- #endregion
- #region 通过 openId 和 unionId 参数来进行数据库的相关验证
- var claimList = await ValidateUserAsync(openId, unionId);
- #endregion
- #region 授权通过
- // 授权通过返回
- context.Result = new GrantValidationResult
- (
- subject: openId,
- authenticationMethod: "custom",
- claims: claimList.ToArray()
- );
- #endregion
- }
- catch (Exception ex)
- {
- context.Result = new GrantValidationResult()
- {
- IsError = true,
- Error = ex.Message
- };
- }
- }
- #region Private Method
- /// <summary>
- /// 验证用户
- /// </summary>
- /// <param name="loginName"></param>
- /// <param name="password"></param>
- /// <returns></returns>
- private async Task<List<Claim>> ValidateUserAsync(string openId, string unionId)
- {
- //TODO 这里可以通过 openId 和 unionId 来查询用户信息 (数据库查询),
- // 我这里为了方便测试还是直接写测试的 openId 相关信息用户
- var user = OAuthMemoryData.GetWeiXinOpenIdTestUsers();
- if (user == null)
- {
- // 注册用户
- }
- return new List<Claim>()
- {
- new Claim(ClaimTypes.Name, $"{openId}"),
- };
- }
- #endregion
- }
GrantTypeConstants 代码是静态类, 主要用于定义 GrantType 的自定义授权类型, 可能后续还有更多的自定义授权方式所以, 统一放这里面进行管理, 方便维护, 代码如下:
- public static class GrantTypeConstants
- {
- /// <summary>
- /// GrantType - 微信端授权
- /// </summary>
- public const string ResourceWeixinOpen = "weixinopen";
- }
ParamConstants 类主要是定义自定义授权需要的参数, 代码如下:
- public class ParamConstants
- {
- public const string OpenId = "openid";
- public const string UnionId = "unionid";
- public const string UserName = "user_name";
- }
好了上面得自定义验证器已经实现了, 但是还不够, 我们还需要让客户端支持自定义的授权类型, 我们打开 OAuthMemoryData 代码中的 GetClients, 代码如下:
- public static IEnumerable<Client> GetClients()
- {
- return new List<Client>
- {
- new Client()
- {
- ClientId =OAuthConfig.UserApi.ClientId,
- AllowedGrantTypes = new List<string>()
- {
- GrantTypes.ResourceOwnerPassword.FirstOrDefault(),//Resource Owner Password 模式
- GrantTypeConstants.ResourceWeixinOpen,// 新增的自定义微信客户端的授权模式
- },
- ClientSecrets = {new Secret(OAuthConfig.UserApi.Secret.Sha256()) },
- AllowedScopes= {OAuthConfig.UserApi.ApiName},
- AccessTokenLifetime = OAuthConfig.ExpireIn,
- },
- };
- }
客户端 AllowedGrantTypes 配置新增了我刚刚自定义的授权方式 GrantTypeConstants.ResourceWeixinOpen,
现在客户端的支持也已经配置好了, 最后我们需要通过 AddExtensionGrantValidator<> 扩展方法把自定义授权验证器注册到 DI 中, 代码如下:
- public void ConfigureServices(IServiceCollection services)
- {
- services.AddControllers();
- #region 数据库存储方式
- services.AddIdentityServer()
- .AddDeveloperSigningCredential()
- .AddInMemoryApiResources(OAuthMemoryData.GetApiResources())
- //.AddInMemoryClients(OAuthMemoryData.GetClients())
- .AddClientStore<ClientStore>()
- .AddResourceOwnerValidator<ResourceOwnerPasswordValidator>()
- .AddExtensionGrantValidator<WeiXinOpenGrantValidator>();
- #endregion
- }
好了, 简单的授权中心代码升级已经完成, 我们分别通过命令行运行授权中心和用户业务网关 , 之前的用户业务网关无需改动任何代码, 运行图分别如下:
Jlion.NetCore.Identity.Server 授权中心运行如下
Jlion.NetCore.Identity.UserApiServer 用户业务网关运行如下
我们现在用 postman 模拟 openId,unionId,userName 参数来请求授权中心获得 AccessToken, 请求如下:
我们再通过 postman 携带授权信息访问用户业务网关数据, 结果图如下:
好了, 自定义授权模式已经完成, 简单的授权中心也已经升级完成, 上面 WeiXinOpenGrantValidator 验证器中我没有直接走数据库方式进行验证和注册, 简单的写了个 Demo , 大家有兴趣可以 把 TODO 那一快数据库的操作去实现, 代码我已经提交到 GitHub 上了, 这里再次分享下我博客同步实战的 demo 地址 https://github.com/a312586670/IdentityServerDemo
四, 思考与总结
本篇我介绍了自定义授权方式, 通过查看源代码及查阅资料学习了 IdentityServer4 可以通过自定义授权方式进行扩展. 这样授权中心可以扩展多套授权方式, 比如今天所分享的 自定义微信 openId 授权, 短信验证码授权等其他自定义授权, 一套 API 资源可以兼并多套授权模式, 灵活扩展, 灵活升级. 本篇涉及的知识点不多, 但是非常重要, 因为我们在使用授权中心统一身份认证时经常会遇到多种认证方式的结合, 和多套不同应用用户的使用, 在掌握了授权原理后, 就能在不同的授权方式中切换的游刃有余, 到这里有的博友会问 AccentToken 有过期时间, 会过期怎么办? 难道要重新授权一次吗? 这些问题我会安排下一篇文章分享.
灵魂一问:
上面的授权中心 例子主要是为了让大家更好的理解自定义授权的使用场景及它的灵活性, 真实的场景这样直接把 openId 等相关信息来验证授权安全吗? 大家可以可以思考下, 如果不安全大家又有什么好的解决方案呢? 自我提升在于不停的自我思考, 大家可以敬请的发挥自己的思考, 把答案留在留言板中, 以供大家参考学习, 感谢!!!
来源: https://www.cnblogs.com/jlion/p/12468365.html