源宝导读:微软跨平台技术框架—.NET Core已经日趋成熟,已经具备了支撑大型系统稳定运行的条件。本文将介绍明源云ERP平台从.NET Framework向.NET Core迁移过程中的实践经验。

一、背景

随着ERP的产品线越来越多,业务关联也日益复杂,应用间依赖关系也变得错综复杂,单体架构的弱点日趋明显。19年初,由于平台底层支持了分应用部署模式,将ERP从应用子系统层面进行了切割分离,迈出了从单体架构向微服务架构转型的坚实一步。不久的将来,ERP会进一步将各业务拆分成众多的微服务,而微服务势必需要进行容器化部署和运行管理,这就要求ERP技术底层必须支持跨平台,所以将现有ERP系统从.NET Framework迁移到 .NET Core平台势在必行。

上一篇我们讲述了Erp改造.Net Core之静态文件,这一篇我们将讲述在认证和授权改造过程中遇到的问题和解决思路。

二、关于认证和授权

权限控制是应用程序中最常见的需求,无论是在Asp.Net还是Asp.Net Core中都有相应的支持,而权限控制在框架层面支持主要是两部分,身份认证和身份授权:

  • 身份认证:识别当前请求的用户信息,一般是通过加密的Cookies实现。

  • 身份授权:识别当前请求是否有访问指定资源的权限,一般是根据当前请求识别的用户信息,结合角色权限相关配置来判断。

三、Asp.Net认证和授权的实现

这里不区分Asp.Net WebForm和Asp.Net MVC,因为两者都是基于HttpModule来进行身份认证和授权。

在Asp.Net中认证是通过FormsAuthenticationModule实现,授权是通过UrlAuthorizationModule实现,需要在web.config中做如下配置:

<system.web><authentication mode="Forms"><forms loginUrl="/PubPlatform/Login/index.aspx" name="userToken" defaultUrl="/PubPlatform/Nav/Home/Default.aspx" timeout="300" /></authentication><authorization><deny users="?" /><!--禁止匿名用户的访问--></authorization>
</system.web><!--管理员有modeling目录的访问权限-->
<location path="modeling"><system.web><authorization><allow roles="Admin"/><deny users="*"/></authorization></system.web>
</location>

简述一下用户访问网站授权一个流程:

  1. 假设用户访问首页/PubPlatform/Nav/Home/Default.aspx。

  2. UrlAuthorizationModule 根据禁止匿名用户访问的配置,要求用户进行身份认证。

  3. 跳转到登陆页面/ PubPlatform/ Login/index.aspx进行身份认证。

  4. 服务端判断用户输入用户密码信息。

  5. 如果验证通过将认证信息写入Repsonse的Cookies中并跳转到/PubPlatform/Nav/Home/Default.aspx。

  6. UrlAuthorizationModule 根据配置发现已经有用户信息直接,开始进入到HttpHandler处理程序。

PS:整个认证过程远比这个复杂,因为本文重点不在这里,只做简单的描述,了解基本流程即可,有兴趣可以参考https://www.cnblogs.com /fish-li/archive/2012/04/15/2450571.html。

第二段配置中,通过用户所在的组来控制是否有modeling目录的访问权限,如果需要更多的权限配置可以添加更多类似的配置,实际在使用过程中很少使用这种方式,因为用户角色和页面会有很多,配置会很多而且不能实现动态配置,所以一般是在应用程序中处理,在Webform中一般采用类似如下方式:

public class BacePage:Page
{protected override void OnLoad(EventArgs e){//在这里根据HttpContext.User来获取用户信息然后获取角色权限来判断当前资源可否访问base.OnLoad(e);}
}

在Mvc中一般采用继承自AuthorizationFilterAttribute来处理,逻辑跟上述代码中演示的类似。

四、Asp.Net Core

在Core中引入Authentication需要在Startup.cs进行引入,一般和老版本一样使用Cookies做认证信息的承载,承载的代码如下:

public void ConfigureServices(IServiceCollection services)
{...services.AddAuthentication("Cookies").AddCookie("Cookies");...
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{...app.UseAuthentication();...
}

在ConfigureServices(应用初始化过程),引入了Cookies作为默认的认证方式,在Configure(管道初始化)过程中引入认证的中间件。下面会简要分析下认证和授权的过程:

  1. 请求进入Authentication Middleware中间件之后首先会IAuthentication RequestHandler 接口做权限判断,这个组件主要使用来做远程认证的,例如SSO登录的场景。然后再做默认的认证:以Cookies认证为例,主要是读取Cookies的信息到HttpContext.User中。

  2. 经过中间件之后会进入AuthorizeFilter中(这里和认证和授权的核心地方)。

  3. 获取全局,Controller,Action之上的元数据计算之后得出AuthorizationPolicy。

  4. 通过IPolicyEvaluator调用Authentication模块进行认证信息解析。

  5. 通过IPolicyEvaluator携带Authentication结果调用Authentization模块进行授权信息解析。

核心代码如下:

//AuthenticationMiddleware 部分代码
public async Task Invoke(HttpContext context)
{context.Features.Set(new AuthenticationFeature{OriginalPath = context.Request.Path,OriginalPathBase = context.Request.PathBase});// Give any IAuthenticationRequestHandler schemes a chance to handle the requestvar handlers = context.RequestServices.GetRequiredService<IAuthenticationHandlerProvider>();foreach (var scheme in await Schemes.GetRequestHandlerSchemesAsync()){var handler = await handlers.GetHandlerAsync(context, scheme.Name) as IAuthenticationRequestHandler;if (handler != null && await handler.HandleRequestAsync()){return;}}var defaultAuthenticate = await Schemes.GetDefaultAuthenticateSchemeAsync();if (defaultAuthenticate != null){var result = await context.AuthenticateAsync(defaultAuthenticate.Name);if (result?.Principal != null){context.User = result.Principal;}}await _next(context);
}
// AuthorizeFilte部分代码
public virtual async Task OnAuthorizationAsync(AuthorizationFilterContext context)
{if(context == null){throw new ArgumentNullException(nameof(context));}if (!context.IsEffectivePolicy(this)){return;}// IMPORTANT: Changes to authorization logic should be mirrored in security's AuthorizationMiddlewarevar effectivePolicy = await GetEffectivePolicyAsync(context);if (effectivePolicy == null){return;}var policyEvaluator = context.HttpContext.RequestServices.GetRequiredService<IPolicyEvaluator>();var authenticateResult = await policyEvaluator.AuthenticateAsync(effectivePolicy, context.HttpContext);// Allow Anonymous skips all authorizationif (HasAllowAnonymous(context)){return;}var authorizeResult = await policyEvaluator.AuthorizeAsync(effectivePolicy, authenticateResult, context.HttpContext, context);if (authorizeResult.Challenged){context.Result = new ChallengeResult(effectivePolicy.AuthenticationSchemes.ToArray());}else if (authorizeResult.Forbidden){context.Result = new ForbidResult(effectivePolicy.AuthenticationSchemes.ToArray());}
}

如果有兴趣的话可以继续往里面跟踪一下代码发现最终起作用的是IAuthentication Handler和IAuthorizationHandler,而最终这两个Handler的引入在Core中对应两个概念:

  • AuthenticationScheme 认证计划。

  • AuthorizationRequirement 授权条件。

最终Core的认证和授权一般都是通过这两个来进行扩展,这里特别说明一点,在Mvc Core中将身份认证和授权进行了统一的规划。因为在认证过程中,MVC Core只是只是解析,但并不实际将Http请求进行返回处理。实际过程是在AuthorizationRequirement进行处理的,这里特别提到Mvc Core中的默认授权DenyAnonymousAuthorizationRequirement,通过这个授权条件判断是否有认证的用户信息来进行权限的判断。

资源能否访问这个理解为授权,那么身份认证只是授权的一种方式。

在Asp.Net WebFrom中使用基于HttpModule进行身份认证会丢失掉很多元数据的信息,因为最终方法的执行是在HttpHandler上,而HttpModule并不知道那个Handler也不知道是那个方法,就会大量使用HttpContext的解析,提高了复杂度。

如果在HttpModule中进行,如果有多种身份认证方式,那么无论最终资源需要何种方式都会经过很多HttpModule,走了无谓的分支。

另外Webfrom中授权一般写在基类中,基类必定会引入不同的继承分支,这样也不便于维护.对比Mvc Core中使用的特性标记+策略者的使用方式,更加利于维护和扩展,而不用对原有逻辑进行变动。

对比Asp.Net MVC中其中有IAuthenticationFilter和IAuthorizationFilter来处理授权和认证,然而在Asp.Net MVC Core中 只有IAuthorizationFilter。

对于资源的访问应该只有可否访问的判断即授权,而是否身份认证只能说是判断授权一种常用方式,在MVC Core中将认证通过DenyAnonymousAuthorizationRequirement融入授权以后,真正的将授权和认证融为一体,而非之前看似两个并行的概念,相互不同的处理逻辑。

在Mvc Core通过AOP进行接入,然后一系列的封装,最终将身份和授权通过Scheme + IAuthrenticationHandler和Requirement + AuthorizationHandler,这种类似于策略者模式的方式,对外提供扩展。更加易于扩展,而实际并不用太多考虑AOP部分的逻辑。最终授权和认证之需要我们进行Attribute标记然后MVC Core会自动加载到自己的元数据中,另外在RazorPage还可以主动调用如下方法进行授权的添加:

//对目录进行授权
RazorPageOptions.Conventions.AuthorizeFolder();
//对页面进行授权
RazorPageOptions.Convertions.AuthorizePage();

五、ERP认证和授权

ERP页面的部分使用传统webform的认证体系,而提供的接口部分使用了自定义HttpModule的方式,由于很多ERP演化升级过程中被动接受了很多身份认证方式的接入,所以原本实现授权的HttoModule的逻辑变得异常复杂,而这一次在改造Core的过程中进行了全面的改造后面会进行讲述。同样授权也是一样,将原本实现在Page基类中的复杂的判断逻辑进行了改造。

ERP在pub接口的身份认证方面需要实现对如下不同的方式:

  • Pub接口

  • 内部集成

  • 接口管家

  • Mip集成

  • OA集成

之前讲过ERP是通过元数据来驱动的页面逻辑,权限实际是通过通过元数据配置到界面元素上的权限点来进行标识。通过常用的用户-角色-权限的模型模型来关联用户和权限点,然后通过在Page的基类中通过拿到用户的权限来判断是否有当前URL的访问权限,在按钮渲染的时候来获取是否有该按钮的权限点来控制是否显示。

在这次的改造过程中我们进行了如下改造:

  1. Mvc Core CookiesAuthentication中间件来实现页面的身份认证。

  2. 扩展MVC Core 的 AuthenticationScheme来实现接口的身份认证。

  3. 扩展MVC Core 的 Authentization Policy +Requirement来实现页面的身份授权。

下面通过这三部分进行讲述。

5.1、CookiesAuthentication身份认证

MVC Core 自带的基于Cookies的身份认证已经能满足我们的需求,只需要稍微进行一下配置即可。通过一张图我们了解下整个的身份认证流程,使用代码参考本章开头Asp.Net Core对认证和授权进行分析的部分。

因为只是使用来说已经比较简单这里不做过多的描述。这里有一个点需要注意,在Cookie的认证中如果是负载均衡的场景,两个不同进程的引用需要都可以进行客户端Cookies的解密。那么就需要共享加解密的key,这里我们使用MVC Core自带的基于Entity Framework Core 的密钥持久化方式,通过存放到数据库来实现密钥的共享。这部分内容可以参考:https://docs.microsoft.com/zh-cn/aspnet/core/security/data-protection/implementation/key-storage-providers?view=aspnetcore-3.1&tabs=visual-studio。

5.2、Pub接口的身份认证

除了页面访问以及页面ajax请求这些事基于Cookies的身份认证之外,在提供的接口用了其他的认证方式,这次将所有接口的认证方式进行了抽象,统一使用OpenApi的Scheme进行认证,而实际各种不同的认证方式我们是通过实入AuthenticationMiddleware中调用的IAuthenticationRequestHandler来实现的。首先我们看一下执行的流程:

之前有一篇关于Mvc Core的Controller改造的文章,其中将pub接口的发现和特性路由扩展进行了扩展,这里我们在扩展的地方添加OpenApi类型Scheme的元数据。

因为我们的接口统一是使用/pub/开头的所以我们选择使用扩展IAuthentication RequestHandler来实现,这样在判断是否要进行认证的时候需要判断url是否以pub开头即可,下面是类图:

其中核心就是MockUserAuthHandler,因为在Erp的接口中都会用到用户相关的信息,所以我们所有的pub接口操作实际的身份认证都是模拟了用户的认证,这里将这个概念显式的移动到基类之中,我们通过如下代码进行接入:

public static AuthenticationBuilder AddOpenApiAuth(this AuthenticationBuilder builder)
{builder.AddScheme(AppConst.ApiSchemeName, AppConst.ApiSchemeName,null);return builder.AddScheme<ApiAuthOptions, OpenApiAuthHandler>(AppConst.OpenApi, AppConst.OpenApi, null).AddScheme<ApiAuthOptions, AccessTokenAuthHandler>(AppConst.AccessToken, AppConst.AccessToken, null).AddScheme<ApiAuthOptions, OAuthV1AuthHandler>(AppConst.OAuthV1, AppConst.OAuthV1, null).AddScheme<ApiAuthOptions, OAuthV2AuthHandler>(AppConst.OAuthV2, AppConst.OAuthV2, null).AddScheme<ApiAuthOptions, InternalIntegrationAuthHandler>(AppConst.InternalIntegration, AppConst.InternalIntegration, null);
}

这里我们添加了众多的Scheme只是为了在AuthenticationMiddleware中处理请求并进行模拟用户登录,并未有实际的Action上有此Sheme,在Action上使用的只有AppConst.ApiSchemeName(“OpenApiScheme”)。

在Cookies的认证的流程中因为是在登录过程中将认证信息写到Cookies中,而其他请求只需要进行Cookies解析即可获得用户信息。而pub接口请求是在一个用户请求过程中完成整个过程,实际是通过解析Header的特定内容,然后跟保存在Erp中的认证信息进行对比来确定身份,最后在模拟用户之后,进行登录操作来完成整个身份认证的流程。

这里我们采用扩展IAuthentication RequestHandler的方式有如下考虑:

  1. 如果采用IAuthenticationHandler + Sheme,Core的标准是按照不同的请求使用不同Scheme无法支持,而实际来说都是通过pub接口进行区分,并没有区分到具体的请求。

  2. 目前ERP的认证方式跟行业标准方案不同,以后如果接入标准方案,增加Scheme更加容易扩展并兼容现有方案。

  3. IAuthenticationRequestHandler官方给出是远程认证时候使用,在以后前后端分离加上微服务的升级过程中,只需要修改这部分请求中认证信息发送给远程服务器认证即可,更加符合官方推荐使用的风格。

综合来说采取IAuthentication RequestHandler而不采用IAuthenticationHandler + Sheme 方式,还是基于官方给出的推荐使用风格和Erp扩展需求综合考虑而来。

5.3、Razor页面授权

在页面改造篇我们讲述了页面改造的过程,我们知道RazorPage实际是类似CodeBehind的模式,通过PageModel的OnGet方法,每个页面都有对应的PageModel,所以我们这里定义的BasePageModel基类如下:

//其他Attribute
[Authorize(Policy = AppConst.UserAuth,AuthenticationSchemes = “Cookies”)]
public abstract class BasePageModel : Microsoft.AspNetCore.Mvc.RazorPages.PageModel
{protected readonly MapPageContext _mapPageContext;protected BasePageModel(MapPageContext mapPageContext){_mapPageContext = mapPageContext;VerifyApplication.CheckStatus();}
}

这里我们使用Cookies的Scheme进行授权,特别的是我们还增加了AuthentizationPolicy的定义,通过定义的Policy加上PolicyProvider来实现不同的Policy的切换,Policy的使用代码如下:

public static IMvcBuilder AddFrontend(this IMvcBuilder builder)
{builder.AddRazorPagesOptions(options =>{//…//建模的权限options.Conventions.AuthorizeFolder(“/modeling”, AppConst.ModelingAuth);});//页面权限builder.Services.AddMapAuth();//...
}
//注册
private static void AddMapAuth(this IServiceCollection services)
{services.AddSingleton();services.TryAddEnumerable(ServiceDescriptor.Transient());services.TryAddEnumerable(ServiceDescriptor.Transient());services.TryAddEnumerable(ServiceDescriptor.Transient());
}//Policy实现
internal class MapAuthPolicyProvider : IAuthorizationPolicyProvider{private DefaultAuthorizationPolicyProvider FallbackPolicyProvider { get; }public MapAuthPolicyProvider(IOptions<AuthorizationOptions> options){FallbackPolicyProvider = new DefaultAuthorizationPolicyProvider(options);}public Task<AuthorizationPolicy> GetPolicyAsync(string policyName){if (policyName.StartsWith(AppConst.ModelingAuth, StringComparison.OrdinalIgnoreCase)){var policy = new AuthorizationPolicyBuilder();policy.AddRequirements(new ModelingRequirement());return Task.FromResult(policy.Build());}if (policyName.StartsWith(AppConst.AppAuth, StringComparison.OrdinalIgnoreCase)){var policy = new AuthorizationPolicyBuilder();policy.AddRequirements(new AppAuthRequirement());return Task.FromResult(policy.Build());}if (policyName.StartsWith(AppConst.UserAuth, StringComparison.OrdinalIgnoreCase)){var policy = new AuthorizationPolicyBuilder();policy.AddRequirements(new UserAuthRequirement());return Task.FromResult(policy.Build());}//返回默认的认证return FallbackPolicyProvider.GetPolicyAsync(policyName);}public Task<AuthorizationPolicy> GetDefaultPolicyAsync(){return FallbackPolicyProvider.GetDefaultPolicyAsync();}
}

通过上述代码实现了不同策略使用不同的Requirement进行授权的校验,至于授权校验逻辑就是常见的用户权限判断。下面是整体的执行流程:

六、总结

Core认证和授权改造主要是要对Core的认证和授权的逻辑要清晰,这里核心就是去扒源码,由于之前对Asp.Net MVC的了解,直接就从AuthorizeFilter入手很快就可以找到切入点,然后了解到整个Core认证和授权的核心。

权限这部分的改造不仅仅只是代码的迁移,也是风格的一个改造,将原来认证和授权的大泥球的设计,通过Core提供的Scheme+Requirement的扩展进行结合的重新设计。

------ END ------

作者简介

熊同学: 研发工程师,目前负责ERP运行平台的设计与开发工作。

也许您还想看

【复杂系统迁移 .NET Core平台系列】之迁移项目工程

【复杂系统迁移 .NET Core平台系列】之界面层

【复杂系统迁移 .NET Core平台系列】之静态文件

招商城科走进武汉研发中心,现场编码解锁平台内核技术

如何解决大批量数据保存的性能问题

【复杂系统迁移 .NET Core平台系列】之认证和授权相关推荐

  1. 【复杂系统迁移 .NET Core平台系列】之调度服务改造

    源宝导读:微软跨平台技术框架-.NET Core已经日趋成熟,已经具备了支撑大型系统稳定运行的条件.本文将介绍明源云ERP平台从.NET Framework向.NET Core迁移过程中的实践经验. ...

  2. 【复杂系统迁移 .NET Core平台系列】之静态文件

    源宝导读:微软跨平台技术框架-.NET Core已经日趋成熟,已经具备了支撑大型系统稳定运行的条件.本文将介绍明源云ERP平台从.NET Framework向.NET Core迁移过程中的实践经验. ...

  3. 【复杂系统迁移 .NET Core平台系列】之界面层

    源宝导读:微软跨平台技术框架-.NET Core已经日趋成熟,已经具备了支撑大型系统稳定运行的条件.本文将介绍明源云ERP平台从.NET Framework向.NET Core迁移过程中的实践经验. ...

  4. 【复杂系统迁移 .NET Core平台系列】之应用发布与部署

    源宝导读:微软跨平台技术框架-.NET Core已经日趋成熟,已经具备了支撑大型系统稳定运行的条件.本文将介绍明源云ERP平台从.NET Framework向.NET Core迁移过程中的实践经验. ...

  5. rsviwe32 7.6 授权_「复杂系统迁移 .NET Core平台系列」之认证和授权

    源宝导读:微软跨平台技术框架-.NET Core已经日趋成熟,已经具备了支撑大型系统稳定运行的条件.本文将介绍明源云ERP平台从.NET Framework向.NET Core迁移过程中的实践经验. ...

  6. 【复杂系统迁移 .NET Core平台系列】之迁移项目工程

    源宝导读:微软跨平台技术框架-.NET Core已经日趋成熟,已经具备了支撑大型系统稳定运行的条件.本文将介绍明源云ERP平台从.NET Framework向.NET Core迁移过程中的实践经验. ...

  7. 蝉知门户系统迁移到SAE平台-对蝉知2.5版本部分功能的限制

    蝉知2.5版本加入了部分新功能,使用起来更加方便.但在sae平台上受限于平台环境,其中的插件安装.模板安装功能由于没有写权限无法使用.需要在迁移至sae平台时做出限制,提示用户进行其他方式的安装. 1 ...

  8. 蝉知门户系统迁移到SAE平台-File模块扩展

    安装完成后虽然可以正常浏览网站了,但是由于upload目录没有写权限,还需要对文件管理模块进行修改以适应sae的环境,经过导师指点查看了禅道sae3.0版本的迁移方案,初步确定修改思路.也使用了部分原 ...

  9. 查缺补漏系统学习 EF Core 6 - 原始 SQL 查询

    推荐关注「码侠江湖」加星标,时刻不忘江湖事 这是 EF Core 系列的第五篇文章,上一篇文章盘点了 EF Core 中的几种数据查询方式. 但是有有时候,我们可能无法用标准的 LINQ 方法完成查询 ...

最新文章

  1. VS2015编译32位Opencv310(动态库+静态库,文末有下载链接)
  2. WEB框架原理(socket)
  3. PMCAFF高端俱乐部首次集结,最顶级产品人的私密俱乐部!
  4. GDCM:gdcm::EncapsulatedDocument的测试程序
  5. c# datagridview 相关操作。
  6. javascript放在head和body的区别(w3c建议放在head标签中)
  7. 就业阶段-java语言进价_day06
  8. 【项目管理】人力资源计划和组建项目团队
  9. “我不是个优秀的 Web 开发人员,我只是擅长搜索谷歌”
  10. 初等数论中的欧拉公式
  11. Ionic3与Angular4新特性
  12. 论文指导教师评语计算机,毕业论文指导教师评语
  13. 微信网页授权流程 时序图
  14. 相比于 Java 10 的 var, 更期待 Java 11 支持多行字符串
  15. Office Open XML 的测量单位
  16. 药到病除?功能性食品到底有没有用?关键还要看你的肠道微生物
  17. 2017计算机知识竞赛题,2017年《西游记》知识竞赛试题100题附答案.doc
  18. 相约3.8 罗姆EEPROM在线研讨会
  19. 百度网盘真实地址解析(告别下载百度网盘)--修改版
  20. asp动态网站编程课程体系

热门文章

  1. (转)matlab各类数学公式
  2. 国服服务器_《Minecraft我的世界》第三方服务器的基本储备
  3. win8下cocos2dx-3.2+VS2012环境配置及项目创建
  4. windows10加载动画_如何关闭动画并使Windows 10看起来更快
  5. outlook存档邮件_如何在Outlook 2013中存档电子邮件
  6. Python_list部分功能介绍
  7. 一个countDown在多线程调度下使用不当的分享
  8. Navicat Premium 怎么安装比较快
  9. php中使用exec,system等函数调用系统命令
  10. 关于一个js栈溢出的异常