用Middleware给ASP.NET Core Web API添加自己的授权验证

Web API,是一个能让前后端分离、解放前后端生产力的好东西。不过大部分公司应该都没能做到完全的前后端分离。API的实现方式有很

多,可以用ASP.NET Core、也可以用ASP.NET Web API、ASP.NET MVC、NancyFx等。说到Web API,不同的人有不同的做法,可能前台、

中台和后台各一个api站点,也有可能一个模块一个api站点,也有可能各个系统共用一个api站点,当然这和业务有必然的联系。

  安全顺其自然的成为Web API关注的重点之一。现在流行的OAuth 2.0是个很不错的东西,不过本文是暂时没有涉及到的,只是按照最最最

原始的思路做的一个授权验证。在之前的MVC中,我们可能是通过过滤器来处理这个身份的验证,在Core中,我自然就是选择Middleware来处

理这个验证。

  下面开始本文的正题:

  先编写一个能正常运行的api,不进行任何的权限过滤。

using Dapper;

using Microsoft.AspNetCore.Mvc;

using System.Data;

using System.Linq;

using System.Threading.Tasks;

using WebApi.CommandText;

using WebApi.Common;

using Common;


namespace WebApi.Controllers

{

    [Route("api/[controller]")]

    public class BookController : Controller

    {


        private DapperHelper _helper;

        public BookController(DapperHelper helper)

        {

            this._helper = helper;

        }


        // GET: api/book

        [HttpGet]

        public async Task<IActionResult> Get()

        {

            var res = await _helper.QueryAsync(BookCommandText.GetBooks);

            CommonResult<Book> json = new CommonResult<Book>

            {

                Code = "000",

                Message = "ok",

                Data = res

            };

            return Ok(json);

        }


        // GET api/book/5

        [HttpGet("{id}")]

        public IActionResult Get(int id)

        {

            DynamicParameters dp = new DynamicParameters();

            dp.Add("@Id", id, DbType.Int32, ParameterDirection.Input);

            var res = _helper.Query<Book>(BookCommandText.GetBookById, dp, null, true, null, CommandType.StoredProcedure).FirstOrDefault();

            CommonResult<Book> json = new CommonResult<Book>

            {

                Code = "000",

                Message = "ok",

                Data = res

            };

            return Ok(json);

        }


        // POST api/book        

        [HttpPost]

        public IActionResult Post([FromForm]PostForm form)

        {

            DynamicParameters dp = new DynamicParameters();

            dp.Add("@Id", form.Id, DbType.Int32, ParameterDirection.Input);

            var res = _helper.Query<Book>(BookCommandText.GetBookById, dp, null, true, null, CommandType.StoredProcedure).FirstOrDefault();

            CommonResult<Book> json = new CommonResult<Book>

            {

                Code = "000",

                Message = "ok",

                Data = res

            };

            return Ok(json);

        }


    }


    public class PostForm

    {

        public string Id { get; set; }

    }


}

  api这边应该没什么好说的,都是一些常规的操作,会MVC的应该都可以懂。主要是根据id获取图书信息的方法(GET和POST)。这是我们后

面进行单元测试的两个主要方法。这样部署得到的一个API站点,是任何一个人都可以访问http://yourapidomain.com/api/book 来得到相关

的数据。现在我们要对这个api进行一定的处理,让只有权限的站点才能访问它。

  下面就是编写自定义的授权验证中间件了。

  Middleware这个东西大家应该都不会陌生了,OWIN出来的时候就有中间件这样的概念了,这里就不展开说明,在ASP.NET Core中是如何

实现这个中间件的可以参考官方文档 Middleware。 

  我们先定义一个我们要用到的option,ApiAuthorizedOptions

namespace WebApi.Middlewares

{

    public class ApiAuthorizedOptions

    {

        //public string Name { get; set; }


        public string EncryptKey { get; set; }

        

        public int ExpiredSecond { get; set; }

    }

}

 option内容比较简单,一个是EncryptKey ,用于对我们的请求参数进行签名,另一个是ExpiredSecond ,用于检验我们的请求是否超时。

与之对应的是在appsettings.json中设置的ApiKey节点

"ApiKey": {

    //"username": "123",

    //"password": "123",

    "EncryptKey": "@*api#%^@",

    "ExpiredSecond": "300"

  }

有了option,下面就可以编写middleware的内容了

  我们的api中就实现了get和post的方法,所以这里也就对get和post做了处理,其他http method,有需要的可以自己补充。

  这里的验证主要是下面的几个方面:

  1.参数是否被篡改

  2.请求是否已经过期

  3.请求的应用是否合法

  主检查方法:Check



 Check方法带了2个参数,一个是当前的httpcontext对象和请求的内容信息,当签名一致,并且时间戳能转化成double时才去校验是否超时

和Applicatioin的相关信息。这里的签名用了比较简单的HMACMD5加密,同样是可以换成SHA等加密来进行这一步的处理,加密的参数和规则是

随便定的,要有一个约定的过程,缺少灵活性(就像跟银行对接那样,银行说你就要这样传参数给我,不这样就不行,只好乖乖从命)。

  Check方法还用到了下面的4个处理

  1.子检查方法--超时判断CheckExpiredTime


 这里取了当前时间与1970年1月1日的间隔与请求参数中传过来的时间戳进行比较,是否超过我们在appsettings中设置的那个值,超过就是

超时了,没超过就可以继续下一个步骤。

  2.子检查方法--应用程序判断CheckApplication

  应用程序要验证什么呢?我们会给每个应用程序创建一个ID和一个访问api的密码,所以我们要验证这个应用程序的真实身份,是否是那些有权限的应用程序。

 

先根据请求参数中的应用程序id去找到相应的应用程序,不能找到就说明不是合法的应用程序,能找到再去验证其密码是否正确,最后才确

定其能否取得api中的数据。

  下面两方法是处理没有授权和超时处理的实现:

  没有授权的返回方法ReturnNoAuthorized


  这里做的处理是将响应的状态码设置成401(Unauthorized)。

  超时的返回方法ReturnTimeOut


这里做的处理是将响应的状态码设置成408(Time Out)。

  下面就要处理Http的GET请求和POST请求了。

  HTTP GET请求的处理方法GetInvoke


  处理比较简单,将请求的参数赋值给RequestInfo,然后将当前的httpcontext和这个requestinfo交由我们的主检查方法Check去校验

这个请求的合法性。

  同理,HTTP POST请求的处理方法PostInvoke,也是同样的处理。


最后是Middleware的构造函数和Invoke方法。


 到这里,Middleware是已经编写好了,要在Startup中使用,还要添加一个拓展方法ApiAuthorizedExtensions

using Microsoft.AspNetCore.Builder;

using Microsoft.Extensions.Options;

using System;


  到这里我们已经可以在Startup的Configure和ConfigureServices方法中配置这个中间件了

  这里还有一个不一定非要实现的拓展方法ApiAuthorizedServicesExtensions,但我个人还是倾向于实现这个ServicesExtensions。

using Microsoft.Extensions.DependencyInjection;

using System;


namespace WebApi.Middlewares

{

    public static class ApiAuthorizedServicesExtensions

    {


        /// <summary>

        /// Add response compression services.

        /// </summary>

        /// <param name="services">The <see cref="IServiceCollection"/> for adding services.</param>

        /// <returns></returns>

        public static IServiceCollection AddApiAuthorized(this IServiceCollection services)

        {

            if (services == null)

            {

                throw new ArgumentNullException(nameof(services));

            }


            return services;

        }


        /// <summary>

        /// Add response compression services and configure the related options.

        /// </summary>

        /// <param name="services">The <see cref="IServiceCollection"/> for adding services.</param>

        /// <param name="configureOptions">A delegate to configure the <see cref="ResponseCompressionOptions"/>.</param>

        /// <returns></returns>

        public static IServiceCollection AddApiAuthorized(this IServiceCollection services, Action<ApiAuthorizedOptions> configureOptions)

        {

            if (services == null)

            {

                throw new ArgumentNullException(nameof(services));

            }

            if (configureOptions == null)

            {

                throw new ArgumentNullException(nameof(configureOptions));

            }


            services.Configure(configureOptions);

            return services;

        }

    }

}


ApiAuthorizedServicesExtensions

 为什么要实现这个拓展方法呢?个人认为

  Options、Middleware、Extensions、ServicesExtensions这四个是实现一个中间件的标配(除去简单到不行的那些中间件)

  Options给我们的中间件提供了一些可选的处理,提高了中间件的灵活性;

  Middleware是我们中间件最最重要的实现;

  Extensions是我们要在Startup的Configure去表明我们要使用这个中间件;

  ServicesExtensions是我们要在Startup的ConfigureServices去表明我们把这个中间件添加到容器中。

  下面是完整的Startup

using Microsoft.AspNetCore.Builder;

using Microsoft.AspNetCore.Hosting;

using Microsoft.Extensions.Configuration;

using Microsoft.Extensions.DependencyInjection;

using Microsoft.Extensions.Logging;

using System;

using WebApi.Common;

using WebApi.Middlewares;


namespace WebApi

{

    public class Startup

    {

        public Startup(IHostingEnvironment env)

        {

            var builder = new ConfigurationBuilder()

                .SetBasePath(env.ContentRootPath)

                .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)

                .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true);


            if (env.IsEnvironment("Development"))

            {

                // This will push telemetry data through Application Insights pipeline faster, allowing you to view results immediately.

                builder.AddApplicationInsightsSettings(developerMode: true);

            }


            builder.AddEnvironmentVariables();

            Configuration = builder.Build();

        }


        public IConfigurationRoot Configuration { get; }


        // This method gets called by the runtime. Use this method to add services to the container

        public void ConfigureServices(IServiceCollection services)

        {

            // Add framework services.

            services.AddApplicationInsightsTelemetry(Configuration);

            services.Configure<IISOptions>(options =>

            {


            });


            services.Configure<DapperOptions>(options =>

            {

                options.ConnectionString = Configuration.GetConnectionString("DapperConnection");

            });


            //api authorized middleware

            services.AddApiAuthorized(options =>

            {

                options.EncryptKey = Configuration.GetSection("ApiKey")["EncryptKey"];

                options.ExpiredSecond = Convert.ToInt32(Configuration.GetSection("ApiKey")["ExpiredSecond"]);

            });



            services.AddMvc();


            services.AddSingleton<DapperHelper>();

        }


        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline

        public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)

        {


            loggerFactory.AddConsole(Configuration.GetSection("Logging"));

            loggerFactory.AddDebug();


            app.UseDapper();


            //api authorized middleware

            app.UseApiAuthorized();


            app.UseApplicationInsightsRequestTelemetry();


            app.UseApplicationInsightsExceptionTelemetry();


            app.UseMvc();

        }

    }

}

万事具备,只欠测试!!

  建个类库项目,写个单元测试看看。

using Common;

using Newtonsoft.Json;

using System;

using System.Collections.Generic;

using System.Net.Http;

using System.Threading.Tasks;

using Xunit;


namespace WebApiTest

{

    public class BookApiTest

    {

        private HttpClient _client;

        private string applicationId = "1";

        private string applicationPassword = "123";

        private string timestamp = (DateTime.UtcNow - new DateTime(1970, 1, 1)).TotalSeconds.ToString();

        private string nonce = new Random().Next(1000, 9999).ToString();

        private string signature = string.Empty;


        public BookApiTest()

        {

            _client = new HttpClient();

            _client.BaseAddress = new Uri("http://localhost:8091/");

            _client.DefaultRequestHeaders.Clear();

            signature = HMACMD5Helper.GetEncryptResult($"{applicationId}-{timestamp}-{nonce}", "@*api#%^@");

        }


        [Fact]

        public async Task book_api_get_by_id_should_success()

        {

            string queryString = $"applicationId={applicationId}&timestamp={timestamp}&nonce={nonce}&signature={signature}&applicationPassword={applicationPassword}";

            

            HttpResponseMessage message = await _client.GetAsync($"api/book/4939?{queryString}");

            var result = JsonConvert.DeserializeObject<CommonResult<Book>>(message.Content.ReadAsStringAsync().Result);


            Assert.Equal("000", result.Code);

            Assert.Equal(4939, result.Data.Id);

            Assert.True(message.IsSuccessStatusCode);

        }


        [Fact]

        public async Task book_api_get_by_id_should_failure()

        {

            string inValidSignature = Guid.NewGuid().ToString();

            string queryString = $"applicationId={applicationId}&timestamp={timestamp}&nonce={nonce}&signature={inValidSignature}&applicationPassword={applicationPassword}";


            HttpResponseMessage message = await _client.GetAsync($"api/book/4939?{queryString}");

            var result = JsonConvert.DeserializeObject<CommonResult<Book>>(message.Content.ReadAsStringAsync().Result);


            Assert.Equal("401", result.Code);

            Assert.Equal(System.Net.HttpStatusCode.Unauthorized, message.StatusCode);            

        }


        [Fact]

        public async Task book_api_post_by_id_should_success()

        {              

            var data = new Dictionary<string, string>();

            data.Add("applicationId", applicationId);

            data.Add("applicationPassword", applicationPassword);

            data.Add("timestamp", timestamp);

            data.Add("nonce", nonce);

            data.Add("signature", signature);

            data.Add("Id", "4939");

            HttpContent ct = new FormUrlEncodedContent(data);


            HttpResponseMessage message = await _client.PostAsync("api/book", ct);

            var result = JsonConvert.DeserializeObject<CommonResult<Book>>(message.Content.ReadAsStringAsync().Result);


            Assert.Equal("000", result.Code);

            Assert.Equal(4939, result.Data.Id);

            Assert.True(message.IsSuccessStatusCode);


        }


        [Fact]

        public async Task book_api_post_by_id_should_failure()

        {

            string inValidSignature = Guid.NewGuid().ToString();

            var data = new Dictionary<string, string>();

            data.Add("applicationId", applicationId);

            data.Add("applicationPassword", applicationPassword);

            data.Add("timestamp", timestamp);

            data.Add("nonce", nonce);

            data.Add("signature", inValidSignature);

            data.Add("Id", "4939");

            HttpContent ct = new FormUrlEncodedContent(data);


            HttpResponseMessage message = await _client.PostAsync("api/book", ct);

            var result = JsonConvert.DeserializeObject<CommonResult<Book>>(message.Content.ReadAsStringAsync().Result);


            Assert.Equal("401", result.Code);

            Assert.Equal(System.Net.HttpStatusCode.Unauthorized, message.StatusCode);

        }

    }   

}

 测试用的是XUnit。这里写了get和post的测试用例。

  下面来看看测试的效果。

 

   测试通过。这里是直接用VS自带的测试窗口来运行测试,比较直观。

  当然也可以通过我们的dotnet test命令来运行测试。

  本文的Demo已经上传到Github:

  https://github.com/hwqdt/Demos/tree/master/src/ASPNETCoreAPIAuthorizedDemo

原文地址:http://www.cnblogs.com/catcher1994/p/6021046.html


.NET社区新闻,深度好文,微信中搜索dotNET跨平台或扫描二维码关注

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

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

相关文章

公司电脑重装经验 ThinkPad E480 win7重装 电脑重装

***************************下面是20200813更新的****************************** 1.电脑的机械盘老是掉线 要重启才能找到 不用了 还是用自己的移动硬盘代替公司的D盘吧 拷贝也很慢 2.公司的c盘菜120G太小 就只安装必须使用的软件 外接移动硬盘代替电脑的D盘 可以用…

Java 内存泄露以及避免方法

转载自 Java 内存泄露以及避免方法 内存泄露: 是指在程序运行过程中会不断的分配内存空间&#xff0c;那些不再使用的内存空间应该即时回收它们&#xff0c;从而保证可以保证系统可以再次使用这些内存。如果存在无用的内存没有被收回来&#xff0c;那就是内存泄露。 说明…

Tomacat乱码解决

解决方法 解决后

体验Rabbitmq强大的【优先级队列】之轻松面对现实业务场景

说到队列的话&#xff0c;大家一定不会陌生&#xff0c;但是扯到优先级队列的话&#xff0c;还是有一部分同学是不清楚的&#xff0c;可能是不知道怎么去实现吧&#xff0c;其实呢&#xff0c;&#xff0c;&#xff0c;这东西已经烂大街了。。。很简单&#xff0c;用“堆”去实…

jstack(查看线程)、jmap(查看内存)和jstat(性能分析)命令

转载自 jstack(查看线程)、jmap(查看内存)和jstat(性能分析)命令 1&#xff0e;Jstack 1.1 jstack能得到运行java程序的java stack和native stack的信息。可以轻松得知当前线程的运行情况。如下图所示 注&#xff1a;这个和thread dump是同样的结果。但是thread dump是用ki…

RabbitMQ消息队列应用

消息通信组件Net分布式系统的核心中间件之一&#xff0c;应用与系统高并发&#xff0c;各个组件之间解耦的依赖的场景。本框架采用消息队列中间件主要应用于两方面&#xff1a;一是解决部分高并发的业务处理&#xff1b;二是通过消息队列传输系统日志。目前业界使用较多的消息队…

vue插槽面试题_VUE面试题解析,半年出一篇,建议收藏!

回答范例&#xff1a;vuex是vue专用的状态管理库。它以全局方式集中管理应用的状态&#xff0c;并且可以保证状态变更的可预测性。vuex主要解决的问题是多组件之间状态共享的问题&#xff0c;利用各种组件通信方式&#xff0c;我们虽然能够做到状态共享。但是往往需要在多个组件…

常用数据库连接串与驱动总结

1、sql server驱动com.microsoft.sqlserver.jdbc.SQLServerDriver//sql验证jdbc:sqlserver://127.0.0.1:1433;databasenamemydb;Usersa;Password1712682、oracle驱动oracle.jdbc.driver.OracleDriverjdbc:oracle:thin:127.0.0.1:1521:orcl3、mysql驱动com.mysql.jdbc.Driverjdb…

Mysql8.0可以使用解压版 这个比较快 好像现在都是解压版了

https://blog.csdn.net/Charonmomo/article/details/98440968 MySQL-mysql 8.0.17安装 2019年08月04日 09:59:12 Charonmomo 阅读数 1654 标签&#xff1a; MySQL 更多 个人分类&#xff1a; MySQL 版权声明&#xff1a;本文为博主原创文章&#xff0c;遵循 CC 4.0 by-sa 版…

公式冒号是什么意思_三角学中,这么一堆公式其实就说了2个事而已

[遇见数学创作小组] 作者: 心如止水(Java程序员。善于把复杂的数学知识&#xff0c;简洁易懂地表达出来)在学三角这部分的时候&#xff0c;有些书习惯列出诱导公式(induction formula)&#xff0c;都列出来的话可以写小半张纸。那什么是“诱导公式”呢&#xff1f;“诱导公式”…

.NET Core开发:项目实践

初始化项目 本来想详细讲一讲dotnet core的&#xff0c;但我对于dotnet core的研究还不到一星期&#xff0c;半吊子&#xff0c;脑子又笨&#xff0c;就不写那些理论出来误人子弟了&#xff0c;还是直接来一篇实践给大家做个参考。废话不多说&#xff0c;直接上项目&#xff0c…

如何使用jstack分析线程状态

转载自 如何使用jstack分析线程状态背景 记得前段时间&#xff0c;同事说他们测试环境的服务器cpu使用率一直处于100%&#xff0c;本地又没有什么接口调用&#xff0c;为什么会这样&#xff1f;cpu使用率居高不下&#xff0c;自然是有某些线程一直占用着cpu资源&#xff0c;那…

JAVA网络编程实战(笔记)

计算机网络简介 网络编程的目的&#xff1a; 无限电台。。。。传播交流信息&#xff0c;数据交换。通信 想要达到这个效果需要什么&#xff1a; 1.如何准确的定位网络上的一台主机 ip地址192.168.16.124&#xff1a; 端口&#xff0c;定位到这个计算机上的某个资源 2.找到…

让 Java 应用运行更快:性能调优工具及实践

转载自 让 Java 应用运行更快&#xff1a;性能调优工具及实践Java 应用性能优化是一个老生常谈的话题&#xff0c;笔者根据个人经验&#xff0c;将 Java 性能优化分为 4 个层级&#xff1a;应用层、数据库层、框架层、JVM 层。通过介绍 Java 性能诊断工具和思路&#xff0c;给…

github 公钥 私钥_github快速使用

##第一次使用github推荐方式Git第一步&#xff1a;下载git工具&#xff0c;这里是链接&#xff0c;选择适合自己的版本进行安装。第二步&#xff1a;安装完成后&#xff0c;鼠标右键找到Git bash&#xff0c;双击打开。第三步&#xff1a;设置SSH key众所周知ssh是加密传输。加…

ASP.NET Core 之 Identity 入门(一)

前言 在 ASP.NET Core 中&#xff0c;仍然沿用了 ASP.NET里面的 Identity 组件库&#xff0c;负责对用户的身份进行认证&#xff0c;总体来说的话&#xff0c;没有MVC 5 里面那么复杂&#xff0c;因为在MVC 5里面引入了OWIN的东西&#xff0c;所以很多初学者在学习来很费劲&…

蓝桥杯JAVA省赛2013-----B------2(马虎算式)

二、马虎的算式 【解析】&#xff1a;暴力穷举即可 枚举每个位上的数字、组合判断、暴力穷举 【答案】&#xff1a;142 方法一&#xff1a;暴力穷举 package com.kuang.Test; public class Test{public static void main(String[] args) {int ans 0;for(int a 1; a < …

简单解决“无法打开内核设备:\\Global\\vmx86”错误

简单解决“无法打开内核设备:\\Global\\vmx86”错误 本文链接&#xff1a;https://blog.csdn.net/tristan_tian/article/details/79434715 我是win10系统&#xff0c;在小娜搜索“服务”后右击选择使用管理员打开。然后在一大串服务中找到vm开头的服务项&#xff0c;全部都启…

按钮右对齐_Python Tkinter Button按钮

简介BUTTON小工具时使用的按钮添加到各种类型的Python应用&#xff0c;Python允许用户配置按钮的按我们的要求&#xff0c;各种选项可以被设置或重置的要求。BUTTON 小工具时使用的按钮添加到各种类型的Python应用。Python允许用户配置按钮的按我们的要求。各种选项可以被设置或…