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

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

概述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,不进行任何的权限过滤。1 using Dapper;2 using Microsoft.AspNetCore.Mvc;3 using System.Data;4 using System.Linq;5 using System.Threading.Tasks;6 using WebApi.CommandText;7 using WebApi.Common;8 using Common;910 namespace WebApi.Controllers11 {12 [Route("api/[controller]")]13 public class BookController : Controller14 {1516 private DapperHelper _helper;17 public BookController(DapperHelper helper)18 {19 this._helper = helper;20 }2122 // GET: api/book23 [HttpGet]24 public async Task<IActionResult> Get()25 {26 var res = await _helper.QueryAsync(BookCommandText.GetBooks);27 CommonResult<Book> json = new CommonResult<Book>28 {29 Code = "000",30 Message = "ok",31 Data = res32 };33 return Ok(json);34 }3536 // GET api/book/537 [HttpGet("{id}")]38 public IActionResult Get(int id)39 {40 DynamicParameters dp = new DynamicParameters();41 dp.Add("@Id", id, DbType.Int32, ParameterDirection.Input);42 var res = _helper.Query<Book>(BookCommandText.GetBookById, dp, null, true, null, CommandType.StoredProcedure).FirstOrDefault();43 CommonResult<Book> json = new CommonResult<Book>44 {45 Code = "000",46 Message = "ok",47 Data = res48 };49 return Ok(json);50 }5152 // POST api/book53 [HttpPost]54 public IActionResult Post([FromForm]PostForm form)55 {56 DynamicParameters dp = new DynamicParameters();57 dp.Add("@Id", form.Id, DbType.Int32, ParameterDirection.Input);58 var res = _helper.Query<Book>(BookCommandText.GetBookById, dp, null, true, null, CommandType.StoredProcedure).FirstOrDefault();59 CommonResult<Book> json = new CommonResult<Book>60 {61 Code = "000",62 Message = "ok",63 Data = res64 };65 return Ok(json);66 }6768 }6970 public class PostForm71 {72 public string Id { get; set; }73 }7475 }api这边应该没什么好说的,都是一些常规的 *** 作,会MVC的应该都可以懂。主要是根据id获取图书信息的方法(GET和POST)。这是我们后面进行单元测试的两个主要方法。这样部署得到的一个API站点,是任何一个人都可以访问http://yourapidomain.com/api/book 来得到相关的数据。现在我们要对这个api进行一定的处理,让只有权限的站点才能访问它。下面就是编写自定义的授权验证中间件了。Middleware这个东西大家应该都不会陌生了,OWIN出来的时候就有中间件这样的概念了,这里就不展开说明,在ASP.NET Core中是如何实现这个中间件的可以参考官方文档 Middleware。 我们先定义一个我们要用到的option,ApiAuthorizedOptions1 namespace WebApi.Middlewares2 {3 public class ApiAuthorizedOptions4 {5 //public string Name { get; set; }67 public string EncryptKey { get; set; }89 public int ExpiredSecond { get; set; }10 }11 }option内容比较简单,一个是EncryptKey ,用于对我们的请求参数进行签名,另一个是ExpiredSecond ,用于检验我们的请求是否超时。与之对应的是在appsettings.json中设置的ApiKey节点1 "ApiKey": {2 //"username": "123",3 //"password": "123",4 "EncryptKey": "@*api#%^@",5 "ExpiredSecond": "300"6 }有了option,下面就可以编写middleware的内容了我们的api中就实现了get和post的方法,所以这里也就对get和post做了处理,其他http method,有需要的可以自己补充。这里的验证主要是下面的几个方面:1.参数是否被篡改2.请求是否已经过期3.请求的应用是否合法主检查方法:Check1 /// <summary>2 /// the main check method3 /// </summary>4 /// <param name="context"></param>5 /// <param name="requestInfo"></param>6 /// <returns></returns>7 private async Task Check(HttpContext context, RequestInfo requestInfo)8 {9 string computeSinature = HMACMD5Helper.GetEncryptResult($"{requestInfo.ApplicationId}-{requestInfo.Timestamp}-{requestInfo.Nonce}", _options.EncryptKey);10 double tmpTimestamp;11 if (computeSinature.Equals(requestInfo.Sinature) &&12 double.TryParse(requestInfo.Timestamp, out tmpTimestamp))13 {14 if (CheckExpiredTime(tmpTimestamp, _options.ExpiredSecond))15 {16 await ReturnTimeOut(context);17 }18 else19 {20 await CheckApplication(context, requestInfo.ApplicationId, requestInfo.ApplicationPassword);21 }22 }23 else24 {25 await ReturnNoAuthorized(context);26 }27 }Check方法带了2个参数,一个是当前的httpcontext对象和请求的内容信息,当签名一致,并且时间戳能转化成double时才去校验是否超时和Appli

  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,不进行任何的权限过滤。

[Route( ._helper = Task@H_419_81@ res = CommonResult Json = CommonResult Code = Message = Data = [httpGet( IActionResult Get( DynamicParameters dp = dp.Add( res = _helper.query(BookCommandText.GetBookByID,dp,,, CommonResult Json = CommonResult Code = Message = Data = DynamicParameters dp = dp.Add( res = _helper.query(BookCommandText.GetBookByID,CommandType.StoredProcedure).FirstOrDefault(); CommonResult Json = CommonResult Code = Message = Data = ID { ; }  API这边应该没什么好说的,都是一些常规的 *** 作,会MVC的应该都可以懂。主要是根据ID获取图书信息的方法(GET和POST)。这是我们后

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

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

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

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

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

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

EncryptKey { ; ExpiredSecond { ; }

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

与之对应的是在appsettings.Json中设置的APIKey节点

: : }

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

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

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

  1.参数是否被篡改

  2.请求是否已经过期

  3.请求的应用是否合法

  主检查方法:Check computeSinature = HMACMD5Helper.GetEncryptResult($ (computeSinature.Equals(requestInfo.Sinature) && .TryParse(requestInfo.Timestamp, }

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

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

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

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

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

CheckExpiredTime( timestamp, Now_timestamp = (DateTime.UtcNow - DateTime(,, (Now_timestamp - timestamp) > }

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

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

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

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

有权限的应用程序。

Task CheckApplication(httpContext context, applicationID, application = GetAllApplications().Where(x => x.ApplicationID == (application != (application.ApplicationPassword != }

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

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

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

  没有授权的返回方法ReturnNoAuthorized

BaseResponseResult response = Code = Message = context.Response.StatusCode = }

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

  超时的返回方法ReturnTimeOut

BaseResponseResult response = Code = Message = context.Response.StatusCode = }

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

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

  http GET请求的处理方法GetInvoke

queryStrings = RequestInfo requestInfo = ApplicationID = queryStrings[ ApplicationPassword = queryStrings[ Timestamp = queryStrings[ Nonce = queryStrings[ Sinature = queryStrings[ }

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

这个请求的合法性。

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

formCollection = RequestInfo requestInfo = ApplicationID = formCollection[ ApplicationPassword = formCollection[ Timestamp = formCollection[ Nonce = formCollection[ Sinature = formCollection[ }

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

   APIAuthorizedMIDdleware(RequestDelegate next,IOptions ._next = ._options = }

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

IApplicationBuilder UseApiAuthorized( (builder == builder.UseMiddleware IApplicationBuilder UseApiAuthorized( (builder == (options == builder.UseMiddleware }

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

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

IServiceCollection AddApiAuthorized( (services == IServiceCollection AddApiAuthorized( IServiceCollection services,Action (services == (configureOptions == }

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

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

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

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

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

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

  下面是完整的Startup

builder = .AddJsonFile(,optional: ,reloadOnChange: .AddJsonFile($,optional: (env.IsEnvironment( builder.AddApplicationInsightsSettings(developerMode: Configuration = IConfigurationRoot Configuration { services.Configure(options => services.Configure(options => options.ConnectionString = Configuration.GetConnectionString( services.AddApiAuthorized(options => options.EncryptKey = Configuration.GetSection()[ options.ExpiredSecond = Convert.ToInt32(Configuration.GetSection()[ services.AddSingleton loggerFactory.AddConsole(Configuration.GetSection( }

  万事具备,只欠测试!!

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

applicationId = applicationPassword = timestamp = (DateTime.UtcNow - DateTime(, nonce = Random().Next(, signature = _client = _client.BaseAddress = Uri( signature = HMACMD5Helper.GetEncryptResult($, queryString = $ HttpResponseMessage message = _client.GetAsync($ result = JsonConvert.DeserializeObject> Assert.Equal( Assert.Equal( inValidSignature = queryString = $ HttpResponseMessage message = _client.GetAsync($ result = JsonConvert.DeserializeObject> Assert.Equal( data = Dictionary<,> data.Add( data.Add( data.Add( data.Add( data.Add( data.Add(, HttpContent ct = HttpResponseMessage message = _client.PostAsync( result = JsonConvert.DeserializeObject> Assert.Equal( Assert.Equal( inValidSignature = data = Dictionary<,> data.Add( data.Add( data.Add( data.Add( data.Add( data.Add(, HttpContent ct = HttpResponseMessage message = _client.PostAsync( result = JsonConvert.DeserializeObject> Assert.Equal( }

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

  下面来看看测试的效果。

 

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

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

  本文的Demo已经上传到Github:

  

  Thanks for your reading!

总结

以上是内存溢出为你收集整理的用Middleware给ASP.NET Core Web API添加自己的授权验证全部内容,希望文章能够帮你解决用Middleware给ASP.NET Core Web API添加自己的授权验证所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/1256001.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-06-07
下一篇 2022-06-07

发表评论

登录后才能评论

评论列表(0条)

保存