ASP.NET Core 项目简单实现身份验证及鉴权

环境

  • VS 2017

  • ASP.NET Core 2.2

目标

  以相对简单优雅的方式实现用户身份验证和鉴权,解决以下两个问题:

  • 无状态的身份验证服务,使用请求头附加访问令牌,几乎适用于手机、网页、桌面应用等所有客户端

  • 基于功能点的权限访问控制,可以将任意功能点权限集合授予用户或角色,无需硬编码角色权限,非常灵活

项目准备

  1. 创建一个ASP.NET Core Web应用程序

  • 使用ASP.NET Core 2.2

  • 模板选[空]

  • 不启用HTTPS

  • 不进行身份验证

通过NuGet安装Swashbuckle.AspNetCore程序包,并在Startup类中启用Swagger支持

因为这个示例项目不打算编写前端网页,所以直接使用Swagger来调试,真的很方便。

添加一个空的MVC控制器(HomeController)和一个空的API控制器(AuthController)

HomeController.Index()方法中只写一句简单的跳转代码即可:

return new RedirectResult("~/swagger");

AuthController类中随便写一两个骨架方法,方便看效果。

运行项目,会自动打开浏览器并跳转到Swagger页面。

身份验证

定义基本类型和接口

  1. ClaimTypes 定义一些常用的声明类型常量

  2. IClaimsSession 表示当前会话信息的接口

  3. ClaimsSession 会话信息实现类
    根据声明类型从ClaimsPrincipal.ClaimsIdentity属性中读取用户ID、用户名等信息。

    实际项目中可从此类继承或完全重新实现自己的Session类,以添加更多的会话信息(例如工作部门)

  4. IToken 登录令牌接口
    包含访问令牌、刷新令牌、令牌时效等令牌

  5. IIdentity 身份证明接口
    包含用户基本信息及令牌信息

  6. IAuthenticationService 验证服务接口
    抽象出来的验证服务接口,仅规定了四个身份验证相关的方法,如需扩展可定义由此接口派生的接口。

    Login(userName, password)IIdentity根据用户名及密码验证其身份,成功则返回身份证明
    Logout()void注销本次登录,即使未登录也不报错
    RefreshToken(refreshToken)Token刷新登录令牌,如果当前用户未登录则报错
    ValidateToken(accessToken)IIdentity验证访问令牌,成功则返回身份证明
  7. SimpleToken 登录令牌的简化实现

    这个类提不提供都可以,实际项目中大家生成Token的算法肯定是各不相同的,提供简单实现仅用于演示

编写验证处理器

  1. BearerDefaults 定义了一些与身份验证相关的常量

    如:AuthenticationScheme

  2. BearerOptions 身份验证选项类

    AuthenticationSchemeOptions继承而来

  3. BearerValidatedContext 验证结果上下文

  4. BearerHandler 身份验证处理器 <= 关键类

    覆盖了HandleAuthenticateAsync()方法,实现自定义的身份验证逻辑,简述如下:

    1. 获取访问令牌。从请求头中获取authorization信息,如果没有则从请求的参数中获取

    2. 如果访问令牌为空,则终止验证,但不报错,直接返回AuthenticateResult.NoResult()

    3. 调用从构造函数注入的IAuthenticationService实例的ValidateToken()方法,验证访问令牌是否有效,如果该方法触发异常(例如令牌过期)则捕获后通过AuthenticateResult.Fail()返回错误信息,如果该方法返回值为空(例如访问令牌根本不存在)则返回AuthenticateResult.NoResult(),不报错。

    4. 到这一步说明身份验证已经通过,而且拿到身份证明信息,根据该信息创建Claim数组,然后再创建一个包含这些Claim数据的ClaimsPrincipal实例,并将Thread.CurrentPrincipal设置为该实例。

      重点:其实,HttpContext.User属性的类型正是CurrentPrincipal,而其值应该就是来自于Thread.CurrentPrincipal

    5. 构造BearerValidatedContext实例,并将其Principal属性赋值为上面创建的ClaimsPrincipal实例,然后调用Success()方法,表示验证成功。最后返回该实例的Result属性值。

  5. BearerExtensions 包含一些扩展方法,提供使用便利

    重点在于AddBearer()方法内调用builder.AddScheme<TOptions,THandler>()泛型方法时,分别使用了前面编写的BearerOptionsBearerHandler类作为泛型参数。

    public static AuthenticationBuilder AddBearer(...)
    {
    return builder.AddScheme<BearerOptions, BearerHandler>(...);
    }

    如果想要自己实现BearerHandler类的验证逻辑,可以抛弃此类,重新编写使用新Handler类的扩展方法

实现用户身份验证

说明

  这部分是身份验证的落地,实际项目中应该将上面两步(定义基本类型和接口、编写验证处理器)的代码抽象出来,成为独立可复用的软件包,利用该软件包进行身份验证的实现逻辑可参照此示例代码。

实现步骤

  1. Identity 身份证明实现类

  2. SampleAuthenticationService 验证服务的简单实现

    出于演示方便,固化了三个用户(admin/123456、user/123、tester/123)

  3. AuthController 通过HTTP向前端提供验证服务的控制器类

    提供了用户登录、令牌刷新、令牌验证等方法。

  4. 还需要修改项目中Startup.cs文件,添加依赖注入规则、身份验证,并启用身份验证中间件。
    ConfigureServices方法内添加代码:


    services.AddScoped<IClaimsSession, ClaimsSession>();
    services.AddScoped<IAuthenticationService, SampleAuthenticationService>();

    services.AddAuthentication(options =>
    {
    options.DefaultAuthenticateScheme = BearerDefaults.AuthenticationScheme;
    options.DefaultChallengeScheme = BearerDefaults.AuthenticationScheme;
    }).AddBearer();

    Configure()方法内添加代码:


    app.UseAuthentication();

通过Swagger测试

  • 测试登录功能

    启动项目,自动进入[Swagger UI]界面,点击/api/Auth/Login方法,不修改输入框中的内容直接点击[Execute]按钮,可以见到返回401错误码。

    在输入框中输入{"userName": "admin", "password": "123456"},然后点击[Execute]按钮,系统验证成功并返回身份证明信息。

640?wx_fmt=png

记下访问令牌2ad43df2c11d48a18a88441adbf4994a和刷新令牌9bbaf811ed8b4d29b638777d4f89238e

  • 测试刷新登录令牌

    点击/api/Auth/Refresh方法,在输入框中输入上面获取到的刷新令牌9bbaf811ed8b4d29b638777d4f89238e,然后点击[Execute]按钮,返回401错误码。原因是因为我们并未提供访问令牌。

    点击方法名右侧的[锁]图标,在弹出框中输入之前获取的访问令牌2ad43df2c11d48a18a88441adbf4994a并点击[Authorize]按钮后关闭对话框,重新点击[Execute]按钮,成功获取到新的登录令牌。

640?wx_fmt=png

  • 测试验证访问令牌

    点击/api/Auth/Validate方法,在输入框中输入第一次获取的到访问令牌2ad43df2c11d48a18a88441adbf4994a,然后点击[Execute]按钮,返回400错误码,表明发起的请求参数有误。因为此方法是支持匿名访问的,所以错误码不会是401.

    将输入框内容修改为新的访问令牌f37542e162ed4855921ddf26b05c3f25,然后点击[Execute]按钮,验证成功,返回了对应的用户身份证明信息。

640?wx_fmt=png

权限鉴定

  在ASP.NET Core项目中实现基于角色的授权很容易,在一些权限管理并不复杂的项目中,采取这种方式来实现权限鉴定简单可行。有兴趣可以参考这篇博文ASP.NET Core 认证与授权5:初识授权
  但是,对于稍微复杂一些的项目,权限划分又细又多,如果采用这种方式,要覆盖到各种各样的权限组合,需要在代码中定义相当多的角色,大大增加项目维护工作,并且很不灵活。
  这里借鉴ABP框架中权限鉴定的一些思想,来实现基于功能点的权限访问控制。
  非常感谢ASP.NET Core和ABP等诸多优秀的开源项目,向你们致敬!
  不得不说ABP框架非常优秀,但是我并不喜欢使用它,因为我没有能力和精力搞清楚它的详细设计思路,而且很多功能我根本不需要。

思路

  ASP.NET Core提供了一个IAuthorizationFilter接口,如果在控制器类上添加[授权过滤]特性,相应的AuthorizationFilter类的OnAuthorization()方法会在控制器的Action之前运行,如果在该方法中设置AuthorizationFilterContext.Result为一个错误的response,Action将不会被调用。

基于这个思路,我们设计了以下方案:

  1. 编写一个Attribute(特性)类,包含以下两个属性:

    Permissions:需要检查的权限数组

    RequireAllPermissions:是否需要拥有数组中全部权限,如果为否则拥有任一权限即可

  2. 定义一个IPermissionChecker接口,在接口中定义IsGrantedAsync()方法,用于执行权限鉴定逻辑

  3. 编写一个AuthorizationFilterAttribute特性类(应用目标为class),通过属性注入IPermissionChecker实例。然后在OnAuthorization()方法内调用IPermissionChecker实例的IsGrantedAsync()方法,如果该方法返回值为false,则返回403错误,否则正常放行。

编写过滤器类及相关接口

  1. ApiAuthorizeAttribute类

        [AttributeUsage(AttributeTargets.Method)]
    public class ApiAuthorizeAttribute : Attribute, IFilterMetadata
    {
    public string[] Permissions { get; }

    public bool RequireAllPermissions { get; set; }

    public ApiAuthorizeAttribute(params string[] permissions)
    {
    Permissions = permissions;
    }
    }
  2. IPermissionChecker接口定义

        public interface IPermissionChecker
    {
    Task<bool> IsGrantedAsync(string permissionName);
    }
  3. AuthorizationFilterAttribute类

        [AttributeUsage(AttributeTargets.Class)]
    public class AuthorizationFilterAttribute : Attribute, IAuthorizationFilter
    {
    [Injection]
    public IPermissionChecker PermissionChecker { get; set; } = NullPermissionChecker.Instance;

    public void OnAuthorization(AuthorizationFilterContext context)
    {
    if(存在[AllowAnonymous]特性) return;
    var authorizeAttribute = 从context.Filters中析出ApiAuthorizeAttribute
    foreach (var permission in authorizeAttribute.Permissions)
    {

    var granted = PermissionChecker.IsGrantedAsync(permission).Result;
    }
    if(检查未通过)
    context.Result = new ObjectResult("未授权") { StatusCode = 403 };
    }
    }
  4. 配合属性注入提供NullPermissionChecker类,在IsGrantedAsync()方法内直接返回true。

实现属性注入

  做好上面的准备,我们应该可以开始着手在项目内应用权限鉴定功能了,不过ASP.NET Core内置的DI框架并不支持属性注入,所以还得添加属性注入的功能。

  1. 定义InjectionAttribute类,用于显式声明应用了此特性的属性将使用依赖注入




    [AttributeUsage(AttributeTargets.Property)]
    public class InjectionAttribute : Attribute { }
  2. 添加一个PropertiesAutowiredFilterProvider类,从DefaultFilterProvider类派生

    public class PropertiesAutowiredFilterProvider : DefaultFilterProvider
    {
    private static IDictionary<string, IEnumerable<PropertyInfo>> _publicPropertyCache = new Dictionary<string, IEnumerable<PropertyInfo>>();

    public override void ProvideFilter(FilterProviderContext context, FilterItem filterItem)
    {
    base.ProvideFilter(context, filterItem);
    var filterType = filterItem.Filter.GetType();
    if (!_publicPropertyCache.ContainsKey(filterType.FullName))
    {
    var ps=filterType.GetProperties(BindingFlags.Public|BindingFlags.Instance)
    .Where(c => c.GetCustomAttribute<InjectionAttribute>() != null);
    _publicPropertyCache[filterType.FullName] = ps;
    }

    var injectionProperties = _publicPropertyCache[filterType.FullName];
    if (injectionProperties?.Count() == 0)
    return;

    var serviceProvider = context.ActionContext.HttpContext.RequestServices;
    foreach (var item in injectionProperties)
    {
    var service = serviceProvider.GetService(item.PropertyType);
    if (service == null)
    {
    throw new InvalidOperationException($"Unable to resolve service for type '{item.PropertyType.FullName}' while attempting to activate '{filterType.FullName}'");
    }
    item.SetValue(filterItem.Filter, service);
    }
    }
    }
  3. 还有非常关键的一步,在Startup.ConfigureServices()中添加下面的代码,替换IFilterProvider接口的实现类为上面编写的PropertiesAutowiredFilterProvider

    services.Replace(ServiceDescriptor.Singleton<Microsoft.AspNetCore.Mvc.Filters.IFilterProvider, PropertiesAutowiredFilterProvider>());

实现用户权限鉴定

  终于,我们可以在项目内应用权限鉴定功能了。

编码

  1. 首先,我们定义一些功能点权限常量

    public static class PermissionNames
    {

    public const string TestAdd = "Test.Add";
    public const string TestEdit = "Test.Edit";
    public const string TestDelete = "Test.Delete";
    }
  2. 接着,添加一个新的用于测试的控制器类

        [AuthorizationFilter]
    [Route("api/[controller]")]
    [ApiController]
    public class TestController : ControllerBase
    {
    [Injection]
    public IClaimsSession Session { get; set; }

    [HttpGet]
    [Route("[action]")]
    public IActionResult CurrentUser() => Ok(Session?.UserName);

    [ApiAuthorize]
    [HttpGet("{id}")]
    public IActionResult Get(int id)=> Ok(id);

    [ApiAuthorize(PermissionNames.TestAdd)]
    [HttpPost]
    [Route("[action]")]
    public IActionResult Create()=> Ok();

    [ApiAuthorize(PermissionNames.TestEdit, RequireAllPermissions = false)]
    [HttpPost]
    [Route("[action]")]
    public IActionResult Update()=> Ok();

    [ApiAuthorize(PermissionNames.TestAdd, PermissionNames.TestEdit, RequireAllPermissions = false)]
    [HttpPost]
    [Route("[action]")]
    public IActionResult Patch() => Ok();

    [ApiAuthorize(PermissionNames.TestDelete)]
    [HttpDelete("{id}")]
    public IActionResult Delete(int id) => Ok();
    }

    在控制器类上添加了[AuthorizationFilter]特性,除了CurrentUser()方法以外,都添加了[ApiAuthorize]特性,所需的权限各不相同,为简化测试所有的Action都直接返回OkResult

  3. 实现一个用于演示的权限检查器类

    public class SamplePermissionChecker : IPermissionChecker
    {
    private readonly Dictionary<long, string[]> userPermissions = new Dictionary<long, string[]>
    {

    { 1, new[] { PermissionNames.TestAdd, PermissionNames.TestEdit, PermissionNames.TestDelete } },

    { 2, new[] { PermissionNames.TestEdit, PermissionNames.TestDelete } }
    };

    public IClaimsSession Session { get; }


    public SamplePermissionChecker(IClaimsSession session)
    {
    this.Session = session;
    }

    public Task<bool> IsGrantedAsync(string permissionName)
    {
    if(!userPermissions.Any(p => p.Key == Session.UserId))
    return Task.FromResult(false);
    var up = userPermissions.Where(p => p.Key == Session.UserId).First();
    var granted = up.Value.Any(permission => permission.Equals(permissionName, StringComparison.InvariantCultureIgnoreCase));
    return Task.FromResult(granted);
    }

    }
  4. 最后还需要修改项目中Startup.cs文件,添加依赖注入规则

    services.AddSingleton<IPermissionChecker, SamplePermissionChecker>();

    因为SamplePermissionChecker类中并没有需要进程间隔离的数据,所以使用单例模式注册就可以了。不过这样一来,因为该类通过构造函数注入了IClaimsSession接口实例,在构建Checker类实例时将触发异常。考虑到CliamsSession类中只有方法没有数据 ,改为单例也并无妨,于是将该接口也改为单例模式注册。

通过Swagger测试

  • 测试未登录时仅可访问/api/Test/CurrentUser

  • 测试以用户user登录,可以访问/api/Test/CurrentUser和GET请求/api/Test/{id}

  • 测试以用户admin登录,可以访问除/api/Test/Add以外的接口

测试

编写了命令行程序,用来测试前面实现的Web API服务。

测试不同用户同时访问时Session是否正确

  • 测试方法

    同时运行三个测试程序,都选择[测试身份验证],然后分别输入不同的用户身份序号,快速切换三个程序并按下回车键,三个测试程序会各自发起100次请求,每次请求间隔100毫秒。

    例如同时打开三个命令行终端执行:dotnet .\CustomAuthorization.test.dll

  • 测试结果

    三个测试程序从后台服务所获取到的当前用户信息完成匹配。

640?wx_fmt=png

测试以不同用户身份访问需要权限的接口

  • 测试方法

    预设的权限为:admin=>全部权限,user=>除Test.Add以外权限,tester=>无。

    分别以admin、user、tester三个用户身份请求/api/test下的所有接口,并模拟令牌过期的场景。

  • 测试结果

    可以见到,以过期的令牌发起请求时,后台返回的状态为Unauthorized,当用户未获得足够的授权时后台返回的状态为Forbidden。

    测试通过!

640?wx_fmt=png

最后

源代码托管在gitee.com :https://gitee.com/xant77/CustomAuthorization.WebApi

原文地址:https://www.cnblogs.com/wiseant/p/10515842.html

.NET社区新闻,深度好文,欢迎访问公众号文章汇总 http://www.csharpkit.com
640?wx_fmt=jpeg


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

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

相关文章

ML.NET 发布0.11版本:.NET中的机器学习,为TensorFlow和ONNX添加了新功能

微软发布了其最新版本的机器学习框架&#xff1a;ML.NET 0.11带来了新功能和突破性变化。新版本的机器学习开源框架为TensorFlow和ONNX添加了新功能&#xff0c;但也包括一些重大变化, 这也是发布RC版本之前的最后一个预览版&#xff0c;这个月底将发布0.12版本&#xff0c;也就…

如何使用AWS和Azure的配置存储服务保存读取配置

原文&#xff1a;Want to yank configuration values from your .NET Core apps? 作者&#xff1a;pauljwheeler译文&#xff1a;https://www.cnblogs.com/lwqlun/p/10508748.html译者&#xff1a;Lamond Lu示例源代码&#xff1a;https://github.com/lamondlu/LoadConfigurat…

Meaningless Sequence Gym - 102832D

Meaningless Sequence Gym - 102832D 题意&#xff1a; 给你n和c&#xff0c;an的公式如下图 让你求a0…an的和&#xff0c;mod 1e97 题解&#xff1a; 训练时推了好一阵子才和队友推出 我看网上正解为&#xff1a; 一个数的大小与它的二进制表示中的1的个数有关 ac(二进制…

【.NET Core项目实战-统一认证平台】第十六章 网关篇-Ocelot集成RPC服务

一、什么是RPCRPC是“远程调用&#xff08;Remote Procedure Call&#xff09;”的一个名称的缩写&#xff0c;并不是任何规范化的协议&#xff0c;也不是大众都认知的协议标准&#xff0c;我们更多时候使用时都是创建的自定义化&#xff08;例如Socket&#xff0c;Netty&#…

.net Core2.2 WebApi通过OAuth2.0实现微信登录

前言微信相关配置请参考 微信公众平台 的这篇文章。注意授权回调域名一定要修改正确。微信网页授权是通过OAuth2.0机制实现的&#xff0c;所以我们可以使用 https://github.com/china-live/QQConnect 这个开源项目提供的中间件来实现微信第三方登录的流程。开发流程1、新建一个…

Nginx优化(重点)与防盗链(新版)

Nginx优化(重点)与防盗链 Nginx优化(重点)与防盗链一、隐藏Nginx版本号1、修改配置文件2、修改源代码 二、修改Nginx用户与组1、编译安装时指定用户与组2、修改配置文件指定用户与组 三、配置Nginx网页的缓存时间四、实现Nginx的日志切割1、data的用法2、编写脚本进行日志切割的…

CodeForces730E Award Ceremony(拓扑排序+结论)

CF730E. Award Ceremonyproblemsolutioncodeproblem 题目链接 题目大意&#xff1a; 给出 nnn 个队封榜时的榜单 aia_iai​ 和揭榜时的变化情况 did_idi​。 揭榜时&#xff0c;这个队的名次会变化 tit_iti​。 注意在别的队揭榜时&#xff0c;自己队的排名也是动态变化的…

.Netcore 2.0 Ocelot Api网关教程(番外篇)- Ocelot v13.x升级

由于Ocelot系列博客好久没更新&#xff08;差不多有10个月的时间了&#xff09;&#xff0c;在此先说声抱歉&#xff0c;Ocelot系列会继续更新下去。在写上一篇配置管理的时候发现官方文档已经和以前的不一样&#xff0c;而Ocelot也从5.0版本更新到了13.x版本&#xff0c;进行了…

CF765F Souvenirs(暴力、线段树)

解析 比较神奇的一道题。 考虑一个常规套路&#xff1a;把询问离线&#xff0c;移动右端点&#xff0c;维护左端点答案。 考虑暴力维护&#xff0c;对于当前的 aixa_ixai​x&#xff0c;左侧如图所示的这两条线上的点都可以产生新的可能答案。 容易构造使得单次产生的新点是…

Hard Disk Drive HDU - 4788

Hard Disk Drive HDU - 4788 题意&#xff1a; 通常制造商认为1“kilo”等于1000&#xff0c;但操作系统会认为是1024。 因此&#xff0c;当你购买了一个100MB的硬盘&#xff0c;电脑却只显示大约有95MB&#xff0c;这缺失了大约5MB。 对于硬盘的大小&#xff0c;有多种单位描…

ASP.NET Core 沉思录 - 环境的思考

我的博客换新家啦&#xff0c;新的地址为&#xff1a;https://clrdaily.com :-D今天我们来一起思考一下如何在不同的环境应用不同的配置。这里的配置不仅仅指 IConfiguration 还包含 IWebHostBuilder 的创建过程和 Startup 的初始化过程。0 太长不读环境造成的差异在架构中基本…

深度:从 Office 365 新图标来看微软背后的设计新理念

开始表演请关注我的公众号“寒树Office”来获取一些新鲜而有趣的新闻与知识&#xff0c;最近又有两家俱乐部上线了&#xff08;东莞与长沙&#xff09;&#xff0c;俱乐部的活动告一段落&#xff0c;接下来的日子里我将持续与大家分享 Office 365 的精彩内容&#xff0c;这次很…

NET Core微服务之路:实战SkyWalking+Exceptionless体验生产下追踪系统

前言当一个APM或一个日志中心实际部署在生产环境中时&#xff0c;是有点力不从心的。比如如下场景分析的问题&#xff1a;从APM上说&#xff0c;知道某个节点出现异常&#xff0c;或延迟过过高&#xff0c;却不能及时知道日志反馈情况&#xff0c;总不可能去相应的节点上一个一…

.NET 中创建支持集合初始化器的类型

对象初始化器和集合初始化器只是语法糖&#xff0c;但是能让你的代码看起来更加清晰。至少能让对象初始化的代码和其他业务执行的代码分开&#xff0c;可读性会好一些。本文将编写一个类型&#xff0c;可以使用集合初始化器构造这个类型。不只是添加元素的集合初始化器&#xf…

【无码专区8】三角形二维数点——计数有多少个给定点落在三角形区域内

因为只有std&#xff0c;没有自我实现&#xff0c;所以是无码专区 主要是为了训练思维能力 solution才是dls正解&#xff0c;但是因为只有潦草几句&#xff0c;所以大部分会有我自己基于正解上面的算法实现过程&#xff0c;可能选择的算法跟std中dls的实现不太一样。 std可能…

为什么我的会话状态在ASP.NET Core中不工作了?

原文&#xff1a;Why isnt my session state working in ASP.NET Core? Session state, GDPR, and non-essential cookies作者&#xff1a;Andrew Lock译文&#xff1a;https://www.cnblogs.com/lwqlun/p/10526380.html译者&#xff1a;Lamond Lu在本篇博客中&#xff0c;我将…

现身说法:实际业务出发分析百亿数据量下的多表查询优化

今天给大家带来的讨论主题是通过实战经验来对百亿数据量下的多表数据查询进行优化&#xff0c;俗话说的好&#xff0c;一切脱离业务的架构都是耍流氓&#xff0c;接下来我就整理一下今天早上微信群里石头哥给大家分享的百亿数据量多表查询架构以及优化思路。由于本文内容整理自…

Help Jimmy POJ - 1661

Help Jimmy POJ - 1661 题意&#xff1a; 场景中包括多个长度和高度各不相同的平台。地面是最低的平台&#xff0c;高度为零&#xff0c;长度无限。 Jimmy老鼠在时刻0从高于所有平台的某处开始下落&#xff0c;它的下落速度始终为1米/秒。当Jimmy落到某个平台上时&#xff0c…

ASP.NET Core 沉思录 - ServiceProvider 的二度出生

ASP.NET Core 终于将几乎所有的对象创建工作都和依赖注入框架集成了起来。并对大部分的日常工作进行了抽象。使得整个框架扩展更加方便。各个部分的集成也更加容易。今天我们要思考的部分仍然是从一段每一个工程中都大同小异的代码开始的。IWebHostBuilder CreateWebHostBuilde…

Acwing 216. Rainbow的信号

Acwing 216. Rainbow的信号 题意&#xff1a; 给你n个数&#xff0c;在这n个数中&#xff0c;等概率地选取两个数l&#xff0c;r&#xff0c;如果l>r,则交换l,r 把信号中的第 l 个数到第 r 个数取出来&#xff0c;构成一个数列 P。 A 部分对话的密码是数列 P 的 xor 和的…