当前位置:网站首页>使用请求头认证来测试需要授权的 API 接口
使用请求头认证来测试需要授权的 API 接口
2022-07-26 19:18:00 【biyusr】
使用请求头认证来测试需要授权的 API 接口
Intro
有一些需要认证授权的接口在写测试用例的时候一般会先获取一个 token,然后再去调用接口,其实这样做的话很不灵活,一方面是存在着一定的安全性问题,获取 token 可能会有一些用户名密码之类的测试数据,还有就是获取 token 的话如果全局使用同一个 token 会很不灵活,如果我要测试没有用户信息的话还比较简单,我可以不传递 token,如果token里有两个角色,我要测试另外一个角色的时候,只能给这个测试用户新增一个角色然后再获取token,这样就很不灵活,于是我就尝试把之前写的自定义请求头认证的代码,整理了一下,集成到了一个 nuget 包里以方便其他项目使用,nuget 包是 WeihanLi.Web.Extensions,源代码在这里 https://github.com/WeihanLi/WeihanLi.Web.Extensions 有想自己改的可以直接拿去用,目前提供了基于请求头的认证和基于 QueryString 的认证两种认证方式。
实现效果
基于请求头动态配置用户的信息,需要什么样的信息就在请求头中添加什么信息,示例如下:


再来看个单元测试的示例:
]public async Task MakeReservationWithUserInfo(){using var request = new HttpRequestMessage(HttpMethod.Post, "/api/reservations");request.Headers.TryAddWithoutValidation("UserId", GuidIdGenerator.Instance.NewId()); // 用户Idrequest.Headers.TryAddWithoutValidation("UserName", Environment.UserName); // 用户名request.Headers.TryAddWithoutValidation("UserRoles", "User,ReservationManager"); //用户角色request.Content = new StringContent([email protected]"{ {""reservationUnit"":""nnnnn"",""reservationActivityContent"":""13211112222"",""reservationPersonName"":""谢谢谢"",""reservationPersonPhone"":""13211112222"",""reservationPlaceId"":""f9833d13-a57f-4bc0-9197-232113667ece"",""reservationPlaceName"":""第一多功能厅"",""reservationForDate"":""2020-06-13"",""reservationForTime"":""10:00~12:00"",""reservationForTimeIds"":""1""}}", Encoding.UTF8, "application/json");using var response = await Client.SendAsync(request);Assert.Equal(HttpStatusCode.OK, response.StatusCode);}
实现原理解析
实现原理其实挺简单的,就是实现了一种基于 header 的自定义认证模式,从 header 中获取用户信息并进行认证,核心代码如下:
protected override async Task<AuthenticateResult> HandleAuthenticateAsync(){if (await Options.AuthenticationValidator(Context)){var claims = new List<Claim>();if (Request.Headers.TryGetValue(Options.UserIdHeaderName, out var userIdValues)){claims.Add(new Claim(ClaimTypes.NameIdentifier, userIdValues.ToString()));}if (Request.Headers.TryGetValue(Options.UserNameHeaderName, out var userNameValues)){claims.Add(new Claim(ClaimTypes.Name, userNameValues.ToString()));}if (Request.Headers.TryGetValue(Options.UserRolesHeaderName, out var userRolesValues)){var userRoles = userRolesValues.ToString().Split(new[] { Options.Delimiter }, StringSplitOptions.RemoveEmptyEntries);claims.AddRange(userRoles.Select(r => new Claim(ClaimTypes.Role, r)));}if (Options.AdditionalHeaderToClaims.Count > 0){foreach (var headerToClaim in Options.AdditionalHeaderToClaims){if (Request.Headers.TryGetValue(headerToClaim.Key, out var headerValues)){foreach (var val in headerValues.ToString().Split(new[] { Options.Delimiter }, StringSplitOptions.RemoveEmptyEntries)){claims.Add(new Claim(headerToClaim.Value, val));}}}}// claims identity 's authentication type can not be null https://stackoverflow.com/questions/45261732/user-identity-isauthenticated-always-false-in-net-core-custom-authenticationvar principal = new ClaimsPrincipal(new ClaimsIdentity(claims, Scheme.Name));var ticket = new AuthenticationTicket(principal,Scheme.Name);return AuthenticateResult.Success(ticket);}return AuthenticateResult.NoResult();}
其实就是将请求头的信息读取到 Claims,然后返回一个 ClaimsPrincipal 和 AuthenticationTicket,在读取 header 之前有一个 AuthenticationValidator 是用来验证请求是不是满足使用 Header 认证,是一个基于 HttpContext 的断言委托(Func<HttpContext, Task<bool>>),默认实现是验证是否有 UserId 对应的 Header,如果要修改可以通过 Startup 来配置
使用示例
Startup 配置,和其它的认证方式一样,Header 认证和 Query 认证也提供了基于 AuthenticationBuilder 的扩展,只需要在 services.AddAuthentication() 后增加 Header 认证的模式即可,示例如下:
services.AddAuthentication(HeaderAuthenticationDefaults.AuthenticationSchema).AddQuery(options =>{options.UserIdQueryKey = "uid";}).AddHeader(options =>{options.UserIdHeaderName = "X-UserId";options.UserNameHeaderName = "X-UserName";options.UserRolesHeaderName = "X-UserRoles";});
默认的 Header 是 UserId/UserName/UserRoles,你也可以自定义为符合自己需要的配置,如果只是想新增一个转换可以配置 AdditionalHeaderToClaims 增加自己需要的请求头 => Claims 转换,AuthenticationValidator 也可以自定义,就是上面提到的会首先会验证是不是需要读取 Header,验证通过之后才会读取 Header 信息并认证
测试示例
有一个接口我需要登录之后才能访问,需要用户信息,类似下面这样
[HttpPost][Authorize]public async Task<IActionResult> MakeReservation([FromBody] ReservationViewModel model){// ...}
在测试代码里我配置使用了 Header 认证,在请求的时候直接通过 Header 来控制用户的信息
Startup 配置:
services.AddAuthentication(HeaderAuthenticationDefaults.AuthenticationSchema).AddHeader()// 使用 Query 认证//.AddAuthentication(QueryAuthenticationDefaults.AuthenticationSchema)//.AddQuery();
测试代码:
[Fact]public async Task MakeReservationWithUserInfo(){using var request = new HttpRequestMessage(HttpMethod.Post, "/api/reservations");request.Headers.TryAddWithoutValidation("UserId", GuidIdGenerator.Instance.NewId());request.Headers.TryAddWithoutValidation("UserName", Environment.UserName);request.Headers.TryAddWithoutValidation("UserRoles", "User,ReservationManager");request.Content = new StringContent([email protected]"{ {""reservationUnit"":""nnnnn"",""reservationActivityContent"":""13211112222"",""reservationPersonName"":""谢谢谢"",""reservationPersonPhone"":""13211112222"",""reservationPlaceId"":""f9833d13-a57f-4bc0-9197-232113667ece"",""reservationPlaceName"":""第一多功能厅"",""reservationForDate"":""2020-06-13"",""reservationForTime"":""10:00~12:00"",""reservationForTimeIds"":""1""}}", Encoding.UTF8, "application/json");using var response = await Client.SendAsync(request);Assert.Equal(HttpStatusCode.OK, response.StatusCode);}[Fact]public async Task MakeReservationWithInvalidUserInfo(){using var request = new HttpRequestMessage(HttpMethod.Post, "/api/reservations");request.Headers.TryAddWithoutValidation("UserName", Environment.UserName);request.Content = new StringContent([email protected]"{ {""reservationUnit"":""nnnnn"",""reservationActivityContent"":""13211112222"",""reservationPersonName"":""谢谢谢"",""reservationPersonPhone"":""13211112222"",""reservationPlaceId"":""f9833d13-a57f-4bc0-9197-232113667ece"",""reservationPlaceName"":""第一多功能厅"",""reservationForDate"":""2020-06-13"",""reservationForTime"":""10:00~12:00"",""reservationForTimeIds"":""1""}}", Encoding.UTF8, "application/json");using var response = await Client.SendAsync(request);Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);}[Fact]public async Task MakeReservationWithoutUserInfo(){using var request = new HttpRequestMessage(HttpMethod.Post, "/api/reservations"){Content = new StringContent(@"{""reservationUnit"":""nnnnn"",""reservationActivityContent"":""13211112222"",""reservationPersonName"":""谢谢谢"",""reservationPersonPhone"":""13211112222"",""reservationPlaceId"":""f9833d13-a57f-4bc0-9197-232113667ece"",""reservationPlaceName"":""第一多功能厅"",""reservationForDate"":""2020-06-13"",""reservationForTime"":""10:00~12:00"",""reservationForTimeIds"":""1""}",Encoding.UTF8, "application/json")};using var response = await Client.SendAsync(request);Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);}
More
QueryString 认证和请求头认证是类似的,这里就不再赘述,只是把请求头上的参数转移到 QueryString 上了,觉得不够好用的可以直接 Github 上找源码修改, 也欢迎 PR,源码地址:https://github.com/WeihanLi/WeihanLi.Web.Extensions
Reference
https://github.com/WeihanLi/WeihanLi.Web.Extensions
https://www.nuget.org/packages/WeihanLi.Web.Extensions
https://github.com/OpenReservation/ReservationServer/blob/dev/ActivityReservation.API.Test/TestStartup.cs
https://github.com/OpenReservation/ReservationServer/blob/dev/ActivityReservation.API.Test/Controllers/ReservationControllerTest.cs
https://www.cnblogs.com/weihanli/p/cutom-authentication-in-aspnetcore.html
边栏推荐
- How to adjust the abnormal win11 USB drive to normal?
- KVM virtualization
- Excel-VBA 快速上手(十、提示框、可输入的弹出框)
- openstack 虚拟机网卡被重名为cirename0
- 2022/07/26 learning notes (day16) linked list and stack
- 金仓数据库 KingbaseES SQL 语言参考手册 (19. SQL语句: DROP TABLE 到 LOAD)
- 中天钢铁在 GPS、 AIS 调度中使用 TDengine
- 金仓数据库 KingbaseES SQL 语言参考手册 (13. SQL语句:ALTER SYNONYM 到 COMMENT)
- tf.GraphKeys
- Linux regularly backs up the database and deletes the data n days ago
猜你喜欢

2022/07/26 learning notes (day16) linked list and stack

【MySQL】 - 索引原理与使用

Digital transformation of enterprises has become a general trend, and it is important to choose the right online collaboration tools

企业数字化转型成大趋势,选对在线协作工具很重要

DOM case: 10 second countdown - write jump page related knowledge

Where can I find the files downloaded from iPad

Intensive reading of the paper: yolov2 - yolo9000: better, faster, stronger

客户案例|生学教育依托观测云打造可观测智慧教育新生态

Software testing - what are the automated testing frameworks?

win11 edge怎么卸载?win11 edge浏览器彻底卸载的方法教程
随机推荐
three.js 制作地球标注的两种方法
金仓数据库 KingbaseES SQL 语言参考手册 (17. SQL语句: DISCARD 到 DROP LANGUAGE)
[PHP] common header definitions
Win11 U盘驱动异常怎么调整为正常?
【JVM 系列】JVM 调优
猎聘问卷星,成为微信「寄生虫」
Excel-VBA 快速上手(十一、字符串常用操作)
2000字助你精通防抖与节流
一年卖7亿,德州扒鸡赶考IPO
金仓数据库 KingbaseES SQL 语言参考手册 (14. SQL语句:COMMIT 到 CREATE LANGUAGE)
【PHP】常用的header头部定义
开源 | AREX-携程无代码侵入的流量回放实践
【shell】转载:批量替换 find awk sed xargs
Is qiniu a channel for securities companies? Is it safe to open an account?
负载均衡的使用
Three paradigms of database design
LeetCode_回溯_中等_40.组合总和 II
MySQL 子查询使用方式
Thinking personally
福建争抢VC/PE