【实战 Ids4】║ 又一个项目迁移完成(MVC)

迎周一,腊月十九,小年倒计时

新年还有两周时间就要到了,学习可不能停,这几天一直在加班调休,周末也如此,不过也是趁着半夜凌晨的时间,继续迁移我的项目到IdentityServer4统一认证授权中心Blog.IdentityServer上,也是基本统一了,目前进度如下:

01、前后端分离全家桶已经完成升级:Blog.Core为api,Blog.Admin为后台管理,Blog.Vue为前台信息展示已经全部搞定,具体的代码查看指定Github的分支即可,分支名基本都是Is4,Ids4等字样;

02、Nuxt.tBug项目目前正在升级中,其实和Vue的前后端分离是一样的,都是使用的同一个组件框架oidc-client,这里就不多说了,如果真的差别大,我就单写一篇文章,否则直接看我的代码就行;

03、ChristDDD MVC项目已经完成迁移,就是今天本文讲解的。

04、WPF项目在进度种,到时候简单写个小Demo就行,我会在我的视频中,给大家讲解,预计春节后出来。

上边共涉及到了我开源的六个项目,三个后端,三个前端,想想这一年也是够可以了,但是在迁移的IdentityServer4中,只用到了常用的两种模式,Implicit和Code模式,其实一般我们web开发,掌握四种就行,除了这两个,还有Hybrid和Client,其他的如果没有精力,可以放一放,那下边我们就快速的说一下如何将MVC项目迁移到Ids4上。这里就简单的说一下操作过程,不会讲解原理,原理我会在视频教程中,详细说到。

Idp项目如何配置

具体的原型图,运行原理,等我视频吧,直接看代码,这里要说一下,如果你是第一次开发学习,我建议尽量使用内存模式,这样会很好的调试,如果直接生成到数据库的话,可能有时候修改了一个配置,还需要重新生成数据库,这个有些浪费时间。

在我们的Config.cs中,新建一个Client,用来应对我们的MVC客户端:

// interactive ASP.NET Core MVC client
new Client
{ClientId = "chrisdddmvc",ClientName="Chris DDD MVC项目",ClientSecrets = { new Secret("secret".Sha256()) },AllowedGrantTypes = GrantTypes.Code,RequireConsent = false,RequirePkce = true,AlwaysIncludeUserClaimsInIdToken=true,//将用户所有的claims包含在IdToken内// 登录回调RedirectUris = { "http://ddd.neters.club/signin-oidc" },// 登出回调地址PostLogoutRedirectUris = { "http://ddd.neters.club/signout-callback-oidc" },// 注意这些scope,一定是上边已经定义好的资源AllowedScopes = new List<string>{IdentityServerConstants.StandardScopes.OpenId,IdentityServerConstants.StandardScopes.Profile,IdentityServerConstants.StandardScopes.Email,"roles","rolename",}
}

这里就强调两点,就是配置一下回调地址,然后就是AlwaysIncludeUserClaimsInIdToken要设置为true,以方便我们后边要从claims声明中获取返回的值。

当然,最后还有一个知识点,就是scope中,如果想要自定义的话,需要先在claims中注册添加,然后在GetIdentityResources中配置:

 // scopes define the resources in your systempublic static IEnumerable<IdentityResource> GetIdentityResources(){return new List<IdentityResource>{new IdentityResources.OpenId(),new IdentityResources.Profile(),new IdentityResources.Email(),new IdentityResource("roles", "角色", new List<string> { JwtClaimTypes.Role }),new IdentityResource("rolename", "角色名", new List<string> { "rolename" }),};}

这里配置就是很简单的,咱们继续看看如何在MVC中配置。

ChristDDD如何配置

如果你之前看过或者用到了我的DDD项目,会发现其实本来是用Identity写的,这次我们迁移到Ids4后,需要做一些变化,具体的直接下载我的Ids4分支就行了,修改的内容比较多。

首先我们把响应的认证服务给抽出来,单独封装,上边的是Ids4的,下边的是普通的Identity的:

然后注入服务:

 // IdentityServer4 注入services.AddId4OidcSetup();

那我们直接看看服务是如何设置的:

  private static readonly string config= "https://ids.neters.club";public static void AddId4OidcSetup(this IServiceCollection services){if (services == null) throw new ArgumentNullException(nameof(services));//关闭默认映射,否则它可能修改从授权服务返回的各种claim属性JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();//添加认证服务,并设置其有关选项services.AddAuthentication(options =>{// 客户端应用设置使用"Cookies"进行认证options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;// identityserver4设置使用"oidc"进行认证options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;}).AddCookie(CookieAuthenticationDefaults.AuthenticationScheme)// 对使用的OpenIdConnect进行设置,此设置与Identityserver的config.cs中相应client配置一致才可能登录授权成功.AddOpenIdConnect(OpenIdConnectDefaults.AuthenticationScheme, options =>{options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;options.Authority = config;options.RequireHttpsMetadata = false;//必须https协议options.ClientId = "chrisdddmvc";//idp项目中配置的clientoptions.ClientSecret = "secret";options.SaveTokens = true;options.ResponseType = "code";//响应类型// 下边是所有的scope,必须要和idp项目中一致,至少是一部分options.Scope.Clear();options.Scope.Add("roles");//"roles"options.Scope.Add("rolename");//"roles"options.Scope.Add(OidcConstants.StandardScopes.OpenId);//"openid"options.Scope.Add(OidcConstants.StandardScopes.Profile);//"profile"options.Scope.Add(OidcConstants.StandardScopes.Email);//"email"});}

这里有几个注意事项:ClientId一定要填对,Scope必须是Idp项目中配置的子集,Scope一定要写对,不然的话,会报错,比如我们随便把roles改成roles3:

当然全部粘贴过去就行,其他的都有注释,看看即可。

这里配置也是很简单的,运行到了这里,我们就可以简单的调试了,所有的地址,都可以换成localhost来调试。

没有错误的话,我们就可以正式的跳转登录,登录成功后,跳转回来MVC项目,下面我们就说说如何在MVC客户端项目中,进行策略授权。

MVC客户端做策略授权

上边我们已经登录成功,并也跳回了,那现在就要根据情况,设计授权了,毕竟有些页面是test用户不能访问的,只有超级管理员才能访问的:

首先,在声明策略,然后在控制器配置策略

services.AddAuthorization(options =>{options.AddPolicy("CanWriteStudentData", policy => policy.Requirements.Add(new ClaimRequirement("Students", "Write")));options.AddPolicy("CanRemoveStudentData", policy => policy.Requirements.Add(new ClaimRequirement("Students", "Remove")));options.AddPolicy("CanWriteOrRemoveStudentData", policy => policy.Requirements.Add(new ClaimRequirement("Students", "WriteOrRemove")));});// 这里的策略内容可以任意扩展[HttpGet][Authorize(Policy = "CanWriteStudentData")]public IActionResult Edit(Guid? id){}

接着,我们就来定义授权策略处理器

  public class ClaimsRequirementHandler : AuthorizationHandler<ClaimRequirement>{protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, ClaimRequirement requirement){var roleId = context.User.Claims.FirstOrDefault(c => c.Type == "role");var rolename = context.User.Claims.FirstOrDefault(c => c.Type == "rolename");var loginUserName = context.User.Claims.FirstOrDefault(c => c.Type == "preferred_username");if (roleId != null && roleId.Value == "4" && rolename != null && rolename.Value == "SuperAdmin"){context.Succeed(requirement);}return Task.CompletedTask;}}

复杂策略授权如何写,逻辑如何调,上下文中的claims声明如何获取,这里就不多说了,默认已经会了我的第一个项目的Blog.Core的相关内容,这里我们只是来看看是不是能获取到相应的Claims就行:

可以看到我们已经获取到了这个scope,这样我们就可以任意的扩展了。

登录与登出设计

这个其实就很简单了,我们在客户端里,直接登出就行,我写的比较low,当然你可以自己找找例子,我就简单的写了写:

 [Authorize]public IActionResult Login(){return Redirect("index");}public async Task<IActionResult> Logout(){await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);await HttpContext.SignOutAsync(OpenIdConnectDefaults.AuthenticationScheme);return View("Index");}

登录取了个巧,不写内容,直接加了个Authorize,这样肯定就跳转到登录页了。

然后设计下UI展示 _LoginPartial.cshtml ,注入服务就行:

@inject Christ3D.Domain.Interfaces.IUser SignInManager@if (SignInManager.IsAuthenticated())
{<form asp-area="" asp-controller="Home" asp-action="Logout" method="post" id="logoutForm" class="navbar-right"><ul class="nav navbar-nav navbar-right"><li><a asp-area="" asp-controller="Manage" asp-action="Index" title="Manage">Hello @SignInManager.Name!</a></li><li><button type="submit" class="btn btn-link navbar-btn navbar-link">Log out</button></li></ul></form>
}
else
{<ul class="nav navbar-nav navbar-right"><li><a asp-area="" asp-controller="Home" asp-action="Login">Log in</a></li></ul>
}

最终的展示效果是酱紫的,登出:


登录:

到了这里,我们就已经完成了整体流程了!下边就是部署了。

生产环境部署联调

现在还是两个后端项目,一个是IdentityServer4的部署,很简单的,我目前用的是Nginx部署的,Https安全协议。

客户端是MVC项目,但是用的IIS部署的,因为如何也用Nginx部署的话,客户端向授权中心认证的时候,一直报错,错误是回调地址不匹配,因为nginx部署,显示的地址还是本地的:

但是我在idp项目里,明明配置的是ddd域名:

错误信息是这样的:

但是在IIS中配置,是一切正常的,真的是我学术不精啊,有小伙伴知道的,欢迎给我留言私信拍砖,这里我来个赏金(20大洋),给开源事业做贡献了。

这个时候,PC端已经一切正常了,正当高兴的时候,手机访问,又不行了,这次我很机智,有了上次的JS客户端经验,我直接加了一个Cookie

手机移动端适配

在DDD项目中,新建一个扩展:

 public static class SameSiteHandlingExtensions{public static IServiceCollection AddSameSiteCookiePolicy(this IServiceCollection services){services.Configure<CookiePolicyOptions>(options =>{options.MinimumSameSitePolicy = (SameSiteMode)(-1);options.OnAppendCookie = cookieContext => CheckSameSite(cookieContext.Context, cookieContext.CookieOptions);options.OnDeleteCookie = cookieContext => CheckSameSite(cookieContext.Context, cookieContext.CookieOptions);});return services;}private static void CheckSameSite(HttpContext httpContext, CookieOptions options){if (options.SameSite == SameSiteMode.None){var userAgent = httpContext.Request.Headers["User-Agent"].ToString();if (DisallowsSameSiteNone(userAgent)){// For .NET Core < 3.1 set SameSite = (SameSiteMode)(-1)options.SameSite = (SameSiteMode)(-1);}}}private static bool DisallowsSameSiteNone(string userAgent){// Cover all iOS based browsers here. This includes:// - Safari on iOS 12 for iPhone, iPod Touch, iPad// - WkWebview on iOS 12 for iPhone, iPod Touch, iPad// - Chrome on iOS 12 for iPhone, iPod Touch, iPad// All of which are broken by SameSite=None, because they use the iOS networking stackif (userAgent.Contains("CPU iPhone OS 12") || userAgent.Contains("iPad; CPU OS 12")){return true;}// Cover Mac OS X based browsers that use the Mac OS networking stack. This includes:// - Safari on Mac OS X.// This does not include:// - Chrome on Mac OS X// Because they do not use the Mac OS networking stack.if (userAgent.Contains("Macintosh; Intel Mac OS X 10_14") && userAgent.Contains("Version/") && userAgent.Contains("Safari")){return true;}// Cover Chrome 50-69, because some versions are broken by SameSite=None, // and none in this range require it.// Note: this covers some pre-Chromium Edge versions, // but pre-Chromium Edge does not require SameSite=None.if (userAgent.Contains("Chrome/5") || userAgent.Contains("Chrome/6")){return true;}return false;}}

然后服务配置:

常见的错误

刚刚上边我们已经遇到了两个错误,其实总的来说,都是配置的问题,我会在博客园单写一篇文章,来总结IdentityServer4的所有错误,目前还没有,过一段时间查看就行,现在开发的还比较少。注意这两个错误,然后会调试就行,调试主要在F12,去查看network,看看请求的数据是否异常即可。

到了这里,基本就结束了,还是建议大家多看看官网和官方Demo,真的很有用。

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

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

相关文章

C++string容器-赋值操作

功能描述&#xff1a; 给string字符串进行赋值 代码如下&#xff1a; #include <iostream> using namespace std; #include <cstring>void test01() {string str1;str1 "hello world";cout << "str1 " << str1 << endl;…

为什么说云原生会成为未来企业技术变迁的趋势

云原生是当下的热点话题&#xff0c;但是很多人对云原生有很多误解&#xff0c;特别是传统产业物联网或工控、物联网行业对云原生显得"后知后觉"。与其在这里说是预测&#xff0c;不如说是现在进行时&#xff0c;只是由于传统产业本身的技术包袱和组织个人认识程度差…

C++vector容器-构造函数

vector数据结构和数组非常相似&#xff0c;也称为单端数组&#xff0c;在数组的尾段可以做插入&#xff0c;删除操作 vector不同于普通数组&#xff0c;vector可以动态扩展 动态扩展&#xff1a;并不是在原空间之后续接新空间&#xff0c;而是找更大的内存空间&#xff0c;然…

BeetleX网关之请求聚合

在网关服务中请求聚合是允许把多个请求打包成一个响应给请求方&#xff0c;这样不仅可以节省请求方的请求数量&#xff0c;还可以根据需求的情况整合不同业务数据响应请求。BeetleX.Bumblebee虽然并没有内置这一功能&#xff0c;但可以通过扩展的形式来支持请求聚合&#xff0c…

C++vector容器-赋值操作

vector赋值操作 功能描述&#xff1a; 给vector容器进行赋值 函数原型&#xff1a; 代码如下&#xff1a; #include <iostream> using namespace std; #include <vector> //vector赋值操作void printVector(vector<int> &v) {for (vector<int >…

EntityFramework Core表名原理解析,让我来,揭开你神秘的面纱

上一节我们针对最开始抛出的异常只是进行了浅尝辄止的解析&#xff0c;是不是有点意犹未尽的感觉&#xff0c;是的&#xff0c;我也有这种感觉&#xff0c;看到这里相信您和我会有一些疑惑&#xff0c;要是我们接下来通过注解、Fluent APi、DbSet分别对表名进行如下设置&#x…

C++vector容器-容量和大小

vector容量和大小 功能描述&#xff1a; 对vector容器的容量和大小操作 函数原型&#xff1a; 代码如下&#xff1a; #include <iostream> using namespace std; #include <vector> //vector容器的容量和大小操作void printVector(vector<int > &v) {…

openresty+mysql+乱码_openresty记录响应body乱码问题

问题背景最近新上了一个功能&#xff0c;openresty通过syslog记录请求日志&#xff0c;然后由logstash推送至ES。测试上线时未发现这个问题&#xff0c;在日常查看日志的过程中&#xff0c;发现logstash推送有错误日志&#xff0c;错误内容为&#xff1a;Error parsing json&am…

【Azure学习.01】先从账号注册开始

本文部分内容配套视频&#xff1a;https://www.bilibili.com/video/av82898957马上要放假了&#xff0c;决定在家里简单了解一下Azure云服务&#xff0c;虽然公司其他部分用到了这个Azure&#xff0c;但是我还是没有接触到&#xff0c;只是听说很贵&#xff0c;好几千每天&…

C++vector容器-插入和删除

vector插入和删除 功能描述&#xff1a; 对vector容器进行插入&#xff0c;删除操作 函数原型&#xff1a; 代码如下&#xff1a; #include <iostream> using namespace std; #include <vector> //vector插入和删除void printVector(vector<int > &v…

C++vector容器-数据存取

vector数据存取 功能描述&#xff1a; 对vector中的数据的存取操作 函数原型&#xff1a; 代码如下&#xff1a; #include <iostream> using namespace std; #include <vector>//vector容器 数据存取 void test01() {vector<int >v1;for (int i 0; i &l…

如何快速融入团队(四)

作者&#xff1a;邹溪源&#xff0c;长沙资深互联网从业者&#xff0c;架构师社区特邀嘉宾&#xff01;01不知不觉这个系列已经开始第四篇的&#xff0c;其实我的原始意图只是思考一下如果有幸加入一个新团队&#xff0c;我们在思想和行动上该做哪些准备呢。不过随着内容的逐渐…

C++vector容器-互换容器

vector容器互换 功能描述&#xff1a; 实现两个容器内元素进行互换 函数原型&#xff1a; swap(vec);//将vec与本身的元素互换 1.基本使用 代码如下: #include <iostream> using namespace std; //vector容器互换 #include <vector> //1.基本使用void printVect…

.NET 状态机Automatonymous快速入门

介绍 Automatonymous是.NET开发人员的状态机库。它提供了一种流畅的语法来声明状态机&#xff0c;包括状态&#xff0c;事件&#xff08;支持触发器和数据事件&#xff09;以及状态/事件活动。尽管Automatonymous在简单的状态机上非常容易使用&#xff0c;但它具有许多高级功能…

BeetleX实现HTTP协议详解

在传统网络服务中扩展中需要处理Bytes来进行协议的读写&#xff0c;这种原始的处理方式让工作变得相当繁琐复杂&#xff0c;出错和调试的工作量都非常大&#xff1b;组件为了解决这一问题引用Stream读写方式&#xff0c;这种方式可以极大的简化网络协议读写的工作量&#xff0c…

euclidea4攻略_Euclidea几何构建11.4通关攻略

Euclidea几何构建10.2通关攻略Euclidea游戏10.2怎么过&#xff1f;下面小编为大家带来Euclidea几何构建10.2通关攻略&#xff1a;更多攻略不断更新中——Euclidea游戏全关卡通关攻略大全分两次做图第一次&#xff0c;画圆就成了&#xff0c;具体看图应该能懂。第二次&#xff0…

C++set容器-构造和赋值

set基本概念 简介&#xff1a; 所有元素都会自动在插入时自动被排序&#xff0c;set容器也叫集合容器 本质&#xff1a; set/multiset属于关联式容器&#xff0c;底层结构是用二叉树排序 set和multiset区别&#xff1a; 1.set不允许容器中有重复的元素 2.multiset允许容器中有…

(1)解锁MongoDB replica set核心姿势

本文倒腾目前大热的MongoDB Replica Set集群&#xff0c;在倒腾的同时串讲一些 MongoDB特性。 副本集Replica Set是一个术语&#xff0c;定义具有多节点的数据库集群&#xff0c;这些节点具有主从复制(master-slave replication) 且节点之间实现了自动故障转移。 这样的结构通常…

java写dnf外掛_dnf卡盟_Java的泛型详解(一)

Java实现DDD中UnitOfWorkdnf卡盟Java的泛型详解泛型的利益编写的代码可以被差别类型的工具所重用。由于上面的一个优点&#xff0c;泛型也可以削减代码的编写。泛型的使用简朴泛型类public class Pair {private T first;private T second;public Pair() {first null;second n…

C++set容器-大小和交换

set大小和交换 功能描述&#xff1a; 统计set容器大小以及交换set容器 函数原型&#xff1a; 代码如下&#xff1a; #include <iostream> using namespace std; #include <set> //set容器 大小和容器void printSet(set<int > &s) {for (set<int …