[Mvp.Blazor] 集成Ids4,实现统一授权认证

(又一个客户端集成了IdentityServer4)

还是很开心的,目前已经有六个开源项目都集成到了Ids4认证中心了。

1、Blazor系列文章回顾

书接上文,关于Blazor学习呢,我也发了几篇文章了,我一般写东西都喜欢偏实战,当然也有系列教程的情节,还记得当时在群里,我说简单看看,浅尝辄止吧,没想到慢慢的发现了解的就越来越深入了,这里我我们再来一个前情回顾:

《我的『MVP.Blazor』快速创建与部署》

在这篇文章中,我们简单的了解了下,什么的Blazor,他能做些什么,以及如何快速的入门和部署,属于一个认知的阶段,熟话说万事开头难,只要有兴趣了,剩下的就是勤为径;

《最终选型 Blazor.Server:又快又稳!》

从这篇文章开始,慢慢的开始实战了,因为刚开始选型的是blazor.wasm,后来发现速度上比较慢,特别是刷新上,所以就最终选型了Blazor.Server了,速度当然没得说,和我们平时的ASP.NETCore是一样的,不过很多人说对硬件要求高,我感觉没什么感觉,2C4G的Linux服务器,几千人在线应该没问题。然后就正式开始了设计我的MVP项目;

《[号外] Blazor wasm 其实也挺快!》

选型了server版本以后,总感觉wasm版本不可能那么慢,然后就好好的深入研究了下,通过了PWA、GZIP压缩、CDN等技术,基本能保证WASM框架首屏首次刷新在3~5s之内,之后静态加载毫秒级别,动态刷新是2s以内(可以查看我文章,有具体的数据佐证);

《[Mvp.Blazor] 动态路由与钩子函数》

之前三篇文章,我们学会了组件通信、数据请求、数据绑定和继承等知识点,那这篇文章我简单的对路由和钩子函数做了说明和讲解,已经算是比较完善的项目了;

《如何给Blazor.Server加个API鉴权?》

我经常在群里说的一句话就是:没有日志的项目是没有灵魂的,没有权限的项目是裸奔的。就是这样的,所以我基本任何项目都会有权限,包括我们功能内部的一些小Portal,我都会在重要页面或数据上增加一定的权限。这篇文章我用了很简单,可以说很low的方法,对资源api实现了鉴权,当然,我在文章中也说了,这种方案肯定不靠谱。

所以,在这个端午节三天期间内,趁着没地方去,我又各种的翻看资料,这里说下,国外的资料还是比较丰富的,有条件的话,还是要科学上网更好些。

最终呢,不负众望,实现了将Blazor.Server集成到了Ids4的统一认证平台上,如果你用的是Blazor.wasm,基本差不多,甚至更简单,等你有实战项目了就知道了。


这里先说明一下,因为毕竟是集成Ids4,涉及的知识会比较多,比如如何使用oidc-client、如何c#调用js事件、如何封装service模块,不过本文就不过多的对这几个知识点讲解原理了,先列出来操作步骤和代码,毕竟篇幅有限,之后我会针对我认为比较重要的知识点稍微讲解讲解。

2、Ids4模块配置

如果你之前开发过Ids4呢,接下来已经能看懂,如果完全不会,建议还是先把Ids4学一遍吧,除非就完全copy我的代码,尽管会遇到这样那样的Bug。

涉及到的页面和模块

(蓝色背景的三个文件)

1、先在认证中心配置Client

我们既然要集成认证平台,那肯定要去认证中心,配置一个客户端,因为我们的Blazor是一个前端的框架,所以我们使用implicit简化模式,和Blog.Admin很相似,只不过一个组件安装一个是直接使用js静态文件,原理都一样。

(Blazor客户端的基本配置)


详细应该能看的懂,注意一点就是需要配置

AllowAccessTokensViaBrowser = true

这样才能有资格接收认证平台返回过来的access_token。

2、客户端配置config.js

首先需要下载或者从admin项目中拷贝出来oidc-client.js文件:

然后就是设计配置文件,我取名为app.js,主要还是连接ids4的相关内容:

 var url = window.location.origin;var settings = {authority: "https://ids.neters.club",client_id: "blazorjs",redirect_uri: url + '/callback',post_logout_redirect_uri: url,response_type: 'id_token token',scope: 'openid profile roles blog.core.api',popup_redirect_uri: url + '/callback',popup_post_logout_redirect_uri: url,silent_redirect_uri: url + '/silent',automaticSilentRenew: false,filterProtocolClaims: true,loadUserInfo: true,revokeAccessTokenOnSignout: true};var mgr = new Oidc.UserManager(settings);///// events///mgr.events.addAccessTokenExpiring(function () {console.log("token expiring");// maybe do this code manually if automaticSilentRenew doesn't work for youmgr.signinSilent().then(function (user) {console.log("silent renew success", user);}).catch(function (e) {console.error("silent renew error", e.message);})});mgr.events.addAccessTokenExpired(function () {console.log("token expired");});mgr.events.addSilentRenewError(function (e) {console.log("silent renew error", e.message);});mgr.events.addUserLoaded(function (user) {console.log("user loaded", user);mgr.getUser().then(function () {console.log("getUser loaded user after userLoaded event fired");});});mgr.events.addUserUnloaded(function (e) {console.log("user unloaded");});

这里先看看热闹即可,具体的代码我建议还是直接从我的项目中获取,具体内容不做赘述;

3、blazor项目引用

我们都知道Blazor.Server更像是一个netcore项目,那如何引用js文件呢,很简单,之前的文章中我也讲过,有一个统一的主页面,用来承载整个app,那就是_Host.cshtml,我们就这几在这里引用即可,如果你是用WASM的话,直接有一个index.html,和这个是同一个道理:

(在Blazor.Server中引用js文件)

那现在我们都配置好了客户端和连接,也引用到了Blazor项目里,那如何去调用具体的js方法呢,请往下继续看。

3、C#调用js方法模块

是不是如果你看到这个逻辑都很怪异,我们都知道c#和js完全就不是一个逻辑,那是如何相互调用的呢,不仅c#可以使用js方法,我们也同样能在js里去调用c#代码,当然这是在Blazor框架里,你用mvc还是比较复杂的,平时我们也是习惯用signalR来实现的双工通信。

这一模块对应的代码(蓝色背景部分):

那我以登录为例子,讲解如何C#调用js吧:

1、注入JS运行时

我们如果想调用js,肯定需要一个运行时环境,这里已经给我们提供给了一个封装好的接口,直接注入即可:

@inject IJSRuntime JS

然后在@code代码块中,我们使用JS,可以看到有两个异步方法:

2、封装扩展方法

这个就是用来帮助我们去Invoke脚本方法的,原理不解释,直接封装扩展:

 /// <summary>/// JSRuntime扩展类/// 用来调取app.js文件/// </summary>public static class JSRuntimeExtensions{public async static Task SignInAsync(this IJSRuntime jsRuntime){await jsRuntime.InvokeAsync<object>("users.startSigninMainWindow");}}

括号中的参数呢,是调用的js方法名称,user.xxxx,注意这个格式,下文会将如何写这个js方法,而且,也可以传递参数,像这样:

public async static Task SetUserInfoToStorage(this IJSRuntime jsRuntime, UserInfoModel userInfoModel)
{await jsRuntime.InvokeAsync<UserInfoModel>("users.setUserInfoToStorage", userInfoModel);
}

当然也可以用返回值,不过这里有一个小坑,js时间转成c#时间的时候,会少八个小时,自己注意一下就行:

 public async static Task<UserInfoModel> GetUserInfoFromStorage(this IJSRuntime jsRuntime){return await jsRuntime.InvokeAsync<UserInfoModel>("users.getUserInfoFromStorage");}

具体的还是看我的源码吧,否则文章会比较长。

3、然后,C#调用扩展

其实也不一定需要封装扩展,直接用原生的invoke也是一样的,不过现在我通过开源了Blog.Core项目以后,越来越多封装情有独钟了。

@code {protected override async Task OnAfterRenderAsync(bool firstRender){await JS.SignInAsync();}
}

是不是很简单,这样就直接可以在c#中,调用js脚本方法了,但是这个js方法任意写function就行了么,并不是。

4、最后,封装js方法

还是用上边的例子:users.startSigninMainWindow 这个方法,对应的js是这样的:

 window.users = {startSigninMainWindow: function () {// ...},}

里边的内容很简单,就是调用上一节的oidc-client的方法,主要是外边的结构,自己把握一下就明白了,对应在浏览器中是这样的,相当于给window窗体增加一个属性:

这个我用着还挺好上手的,如果很多小伙伴不懂的话,以后在单独写文章吧。

到了这里,我们已经配置了ids4模块、c#调用模块,那就剩下最后一个模块:调用资源服务器的service服务模块了

4、调用service模块

不知道大家还记得不记得,在之前的简单的鉴权中,我是通过一个input输入框,手动输入token的方案,还是很low的:

那现在我们就不需要手动配置了,用了ids4后,一切都是自动的,所以还需要继续做封装。

这一部分涉及的代码:

1、获取访问状态——token

在上一节中,我们说到了用c#来调用js,在用户登录成功后,获取用户信息,然后保存到了localstorage里,现在我们如果要发送http请求,就肯定每次获取access_token然后添加到htpp报头里。

  public async Task<string> GetAccessToken(){var userInfo = await _jS.GetUserInfoFromStorage();if (!IsLogin(userInfo)){await _jS.SignInAsync();}return userInfo.AccessToken;}public bool IsLogin(UserInfoModel UserInfo) =>UserInfo != null && UserInfo.AccessToken.IsNotEmptyOrNull() && !UserInfo.IsExpired();

我们这里做了封装,等token失效的时候,会重新去ids4认证中心拉取新的令牌。

2、封装Http操作

上边我们已经获取到了token,接下来就需要发送了,使用的是HttpClient,那每次都设置肯定比较麻烦,感觉再来个封装:

 public abstract class BaseService{protected BaseService(IServiceProvider serviceProvider){ServiceProvider = serviceProvider;}protected IServiceProvider ServiceProvider { get; }protected HttpClient HttpClient => ServiceProvider.GetService<HttpClient>();protected IJSRuntime JS => ServiceProvider.GetService<IJSRuntime>();protected AccessState AccessState => ServiceProvider.GetService<AccessState>();protected async Task<HttpClient> SecurityHttpClientAsync(){var httpClient = ServiceProvider.GetService<HttpClient>();httpClient.DefaultRequestHeaders.Remove("Authorization");var token = await AccessState.GetAccessToken();httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {token}");httpClient.BaseAddress = new Uri("http://apk.neters.club");return httpClient;}}

这是一个抽象基类,然后每个服务继承了就行了。

PS:这里的资源服务用的Blog.Core的接口,你可以用自己的各种服务,无论是webservice,restful还是grpc。

3、定义Blog具体服务

有了服务基类以后,我们在定义每一个基础服务的时候,就简单了不少,只关注业务逻辑即可,不用关心令牌权限了:

    /// <summary>/// 服务基类/// 主要用来对Http请求的基础封装/// </summary>public class BlogService : BaseService{/// <summary>/// 构造函数/// </summary>/// <param name="serviceProvider"></param>public BlogService(IServiceProvider serviceProvider) : base(serviceProvider){}/// <summary>/// 获取全部博文/// </summary>/// <param name="types"></param>/// <param name="page"></param>/// <returns></returns>public async Task<MessageModel<List<BlogArticle>>> GetBlogs(string types, int page = 1){var httpClient = await SecurityHttpClientAsync();return await httpClient.GetFromJsonAsync<MessageModel<List<BlogArticle>>>($"/api/Blog/GetBlogsByTypesForMVP?types={types}&page={page}");}}     

是不是就是很普通的调用接口了!

4、前端调用

前端就很简单了,注入我们的blogservice,然后发送请求即可:

@inject BlogService BlogService@using  Blog.MVP.Blazor.SSR.Pages.Post.component<h1>编辑</h1><Editor BlogArticle="BlogArticle" OnSaveCallback="OnSaveAsync"></Editor><div class="text-danger">@_errmsg
</div>@code {[Parameter]public int Id { get; set; }private BlogArticle BlogArticle { get; set; }private string _errmsg = "";protected override async Task OnInitializedAsync(){BlogArticle = (await BlogService.GetBlogByIdForMVP(Id)).response;}private async Task OnSaveAsync(BlogArticle blogArticle){BlogArticle = blogArticle;var result = await BlogService.UpdateBlog(BlogArticle);if (result.IsSuccessStatusCode){NavManager.NavigateTo("/blog/list");}else{_errmsg = "保存失败! 错误原因:" + result.ReasonPhrase + "。请重试或登录";}}
}

最后别忘了startup注册服务

 // services and stateservices.AddScoped<BlogService>();services.AddScoped<AccessState>();

5、总结

经过上边几步的操作,我们已经可以发送请求了,来先看看效果:

(这里有一个小瑕疵,登录后右上角个人信息需要刷新,以后再优化)

已经实现了单点登录、注销,授权验证等等功能,如果没有权限,就提示无权限:

重要说明

虽然我们已经写完了,也很流畅,但是这里有一个问题:

如果想要在页面进入的时候初始化就调用js事件。

比如:如果你想在进入一个页面的时候,就需要权限需要去登录,就比如我的blog/list页面,我在获取service的时候,会先判断access_token,如果不存在就去登录,那这个时候肯定需要调用js事件。

你可能会这么写:

    protected override async Task OnInitializedAsync(){BlogArticle = (await BlogService.GetBlogByIdForMVP(Id)).response;}

但是只要去调用或者去刷新,可能会遇到这个一个问题:

它的意思是,我们不能在初始化的时候对页面进行js操作,必须要页面渲染完成才可以,

那这个时候就要考虑那三个阶段六个钩子了,官方已经提醒我们使用OnAfterRenderAsync了,但是又有一个问题是,如果你这么写,页面的data就无法渲染,已经我们这是在页面加载完成了才会获取的service。

经过我搜索stack overflow,发现已经有人趟过了:

https://stackoverflow.com/questions/61438796/javascript-interop-error-when-calling-javascript-from-oninitializedasync-blazor

可见生命周期还是要好好学学的。

好啦,假期也结束了,该收收心了,记得我的DDD领域驱动设计概论视频也发布了,记得去看看,有问题尽量视频下边留言,群里讨论太乱了。

拜拜。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/309072.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

重学ASP.NET Core 中的标记帮助程序

标记帮助程序是什么标记帮助程序使服务器端代码可以在 Razor 文件中参与创建和呈现 HTML 元素。 例如&#xff0c;内置的 ImageTagHelper 可以将版本号追加到图片名称。 每当图片发生变化时&#xff0c;服务器都会为图像生成一个新的唯一版本号&#xff0c;因此客户端总能获得…

小型数字系统---运动码表设计

运动码表设计中码表控制器是最难的&#xff0c;所以主要介绍如何设计码表控制器。 我们先给这个时序电路设计状态以及编码 先设计状态转换函数 首先画出状态图: (其中我们让其一到000状态就立马变为001状态&#xff0c;110状态存储完数据立马变成100状态) 根据状态图&#…

用 aforge.net 小试一下验证码识别

今天来小玩一下 aforge.net 套用官方的话就是一个专门为开发者和研究者基于C#框架设计的&#xff0c;这个框架提供了不同的类库和关于类库的资源&#xff0c;还有很多应用程序例子&#xff0c;包括计算机视觉与人工智能&#xff0c;图像处理&#xff0c;神经网络&#xff0c;遗…

国标转区位码电路设计及汉字显示

区位码国际码-2020H&#xff0c;由于采用加法器实现&#xff0c;因此用补码形式进行表示&#xff0c;区位码 国际码FFFF-2020H0001H国际码dfe0。 从网上找下机内码转化的软件&#xff0c;将一段自选文字输入该软件&#xff0c;进行十六进制的机内码转化。由于一个汉字是由四个十…

【WPF】DataGrid多表头的样式设计

需求在使用WPF开发时&#xff0c;使用DataGrid列表显示数据时&#xff0c;有些字段可以进行分组显示&#xff0c;用于更好的表达它们之间存在的某种关系&#xff0c;因此就考虑到要对DataGrid的表头进行扩展&#xff0c;可以显示多行表头&#xff0c;让这些有关联的字段内容显示…

如何使用ABP进行软件开发之基础概览

ABP框架简述1&#xff09;简介在.NET众多的技术框架中&#xff0c;ABP框架&#xff08;本系列中指aspnetboilerplate项目&#xff09;以其独特的魅力吸引了一群优秀开发者广泛的使用。在该框架的赋能之下&#xff0c;开发者可根据需求通过官方网站【https://aspnetboilerplate.…

模2加法,模2减法,模2除法

十进制的除法&#xff0c;大家都会做&#xff1a;列个竖式&#xff0c;商&#xff0c;写在上面&#xff0c;上个几&#xff0c;再用被除数减去积&#xff0c;求得余数…。 二进制的除法&#xff0c;和十进制的计算方法相同&#xff0c;也要列出个竖式计算。 二进制的除法&…

《Unit Testing》2.1 伦敦学派如何做隔离

针对单元测试的定义&#xff0c;主要有两种看法&#xff1a;经典学派。经典学派之所以经典&#xff0c;是因为这原本就是人们做单元测试和测试驱动开发的方式伦敦学派。伦敦学派扎根于伦敦的编程社区。单元测试的定义单元测试有很多定义&#xff0c;但是所有的定义都有三个重要…

C++实现数组模拟链表(实现链表的增删功能)

代码如下: #include <iostream> using namespace std; const int N 100;struct Node {int data;int next; };class ArrayList { private:Node node[N];int maxSize;//数组容量int idx;//接下来要插入的结点的下标int len;//链表长度public:void initList(){node[0].nex…

Gartner:容器采用将迅速增长,但不会很快有利可图

导语容器的未来可期&#xff0c;到2024年&#xff0c;所有应用程序中的15&#xff05;将在容器中运行&#xff0c;而这一数据今天仅为5&#xff05;&#xff0c;但当前变现还比较难。正文近日&#xff0c;Gartner公司发表其首次为软件容器管理软件和服务市场做的预测。Gartner说…

【Azure Show】|第三期 人工智能大咖与您分享!嘉宾陈海平胡浩陈堰平

欢迎来到Azure Show!Azure ShowHello,大家好&#xff0c;又来到新的一期的Azure Show!本期是人工智能专场&#xff0c;我们邀请到微软Data&AI的解决方案架构师陈堰平&#xff0c;Tensorflow.NET 作者陈海平&#xff0c;还有微软人工智能方向最有价值专家胡浩和大家分享人工…

深入探究ASP.NET Core异常处理中间件

前言全局异常处理是我们编程过程中不可或缺的重要环节。有了全局异常处理机制给我们带来了很多便捷&#xff0c;首先我们不用满屏幕处理程序可能出现的异常&#xff0c;其次我们可以对异常进行统一的处理&#xff0c;比如收集异常信息或者返回统一的格式等等。ASP.NET Core为我…

.NET Core加解密实战系列之——消息摘要与数字签名算法

简介加解密现状&#xff0c;编写此系列文章的背景&#xff1a;需要考虑系统环境兼容性问题&#xff08;Linux、Windows&#xff09;语言互通问题&#xff08;如C#、Java等&#xff09;&#xff08;加解密本质上没有语言之分&#xff0c;所以原则上不存在互通性问题&#xff09;…

造轮子-AgileConfig一个基于.NetCore开发的轻量级配置中心

微服务确实是行业的一个趋势&#xff0c;我自己也在把一些项目往微服务架构迁移。玩微服务架构配置中心是一个绕不过去的东西&#xff0c;有很多大牌的组件可以选&#xff0c;比如spring-cloud-config&#xff0c;apoll&#xff0c;disconf等等。而我为什么还要造一个轮子呢&am…

SQL Server 分页+json分享

1。SQL Server 版本2012 新增SQL分页的写法最近封装一个轻量级的ORM用到了分页&#xff0c;以前只知道使用Row_Number函数&#xff0c;现在发现sqlserver 新增的 {orderBy} offset {start} rows fetch next {pageSize} rows only 也挺好用的。简单回顾下 sqlserver 各个版本支持…

用十行代码快速创建权限管理系统

&#xff08;坚持做自己&#xff09;为了防止说是标题党&#xff0c;我先展示下真是就需要十行代码&#xff1a;当然还有appsettings.json配置文件&#xff0c;和种子数据文件&#xff0c;这个不算代码之内。1、项目背景介绍Blog.Core项目开源也两年了&#xff0c;经过了很多许…

ERP的配置管理实践

源宝导读&#xff1a;随着ERP系统的日益复杂&#xff0c;应用部署的方式越来越复杂&#xff0c;应用的配置也变得越来越庞杂&#xff0c;难以维护和管理。本文将介绍配置中心服务通过集中化、可离线的架构设计&#xff0c;解决ERP配置问题的实践经验。一、背景随着ERP业务的日益…

《LIO-SAM阅读笔记》1.IMU预积分模块

前言&#xff1a; LIO-SAM是一个多传感器融合的紧耦合SLAM框架&#xff0c;融合的传感器类型有雷达、IMU和GPS&#xff0c;其中雷达和IMU在LIO-SAM框架中必须使用的。LIO-SAM的优化策略采用了GTSAM库&#xff0c;GTSAM库采用了因子图的优化方法&#xff0c;其提供了一些列C的外…

EntityFramework Core 迁移忽略主外键关系

【导读】本文来源于一位公众号童鞋私信我的问题&#xff0c;在我稍加思索后给出了如下一种方案&#xff0c;在此之前我也思考过这个问题&#xff0c;借此机会我稍微看了下&#xff0c;目前能够想到的也只是本文所述方案。为何要忽略主外键关系我们不仅疑惑为何要忽略主外键关系…