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


再来看个单元测试的示例:
[Fact]
public async Task MakeReservationWithUserInfo()
{
using var request = new HttpRequestMessage(HttpMethod.Post, "/api/reservations");
request.Headers.TryAddWithoutValidation("UserId", GuidIdGenerator.Instance.NewId()); // 用户Id
request.Headers.TryAddWithoutValidation("UserName", Environment.UserName); // 用户名
request.Headers.TryAddWithoutValidation("UserRoles", "User,ReservationManager"); //用户角色
request.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.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-authentication
var 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($@"{{""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($@"{{""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
使用请求头认证来测试需要授权的 API 接口的更多相关文章
- asp.net core 自定义认证方式--请求头认证
asp.net core 自定义认证方式--请求头认证 Intro 最近开始真正的实践了一些网关的东西,最近写几篇文章分享一下我的实践以及遇到的问题. 本文主要介绍网关后面的服务如何进行认证. 解决思 ...
- 4、处理方法中获取请求参数、请求头、Cookie及原生的servlet API等
1.请求参数和请求头 使用@RequestParam绑定请求参数,在处理方法的入参处使用该注解可以把请求参数传递给请求方法 —— value :参数名 —— required : 是否必须,默认为tr ...
- 【转】iOS中修改AVPlayer的请求头信息
在开发中, 我们经常需要在网络请求时修改HTTP/HTTPS的请求头信息 1.普通AFN请求 #import "LMHTTPSessionManager.h" #import &l ...
- axios发送自定义请求头的跨域解决
前端发送来的axios请求信息 this.$axios.request({ url:'http://127.0.0.1:8001/pay/shoppingcar/', method:'post', ...
- HTTP响应头和请求头信息对照表
HTTP请求头提供了关于请求,响应或者其他的发送实体的信息.HTTP的头信息包括通用头.请求头.响应头和实体头四个部分.每个头域由一个域名,冒号(:)和域值三部分组成. 通用头标:即可用于请求,也可用 ...
- HTTP协议简介详解 HTTP协议发展 原理 请求方法 响应状态码 请求头 请求首部 java模拟浏览器客户端服务端
协议简介 协议,自然语言里面就是契约,也是双方或者多方经过协商达成的一致意见; 契约也即类似于合同,自然有甲方123...,乙方123...,哪些能做,哪些不能做; 通信协议,也即是双方通过网络通信必 ...
- java 修改HttpServletRequest的参数或请求头
场景:过滤器中获取参数Token并添加到请求头(用户认证兼容老系统) 请求头和请求参数是不能直接修改,也没有提供修改的方法,但是可以在过滤器和拦截器中使用HttpServletRequestWrapp ...
- 浏览器HTTP协议请求头信息
通常HTTP消息包括客户机向服务器的请求消息和服务器向客户机的响应消息.客户端向服务器发送一个请求,请求头包含请求的方法.URI.协议版本.以及包含请求修饰符.客户信息和内容的类似于MIME的消息结构 ...
- HTTP请求头详解
http://blog.csdn.net/kfanning/article/details/6062118 HTTP由两部分组成:请求和响应.当你在Web浏览器中输入一个URL时,浏览 器将根据你的要 ...
随机推荐
- 【C#】CsvHelper 使用手册
目录 简介 模块 读取 读取所有记录 逐条读取 读取单个字段 写入 写入所有记录 逐条写入 逐字段写入 特性 Index Name NameIndex Ignore Optional Default ...
- Java创建线程的方式
Java中线程的创建有四i种方式: 1. 通过继承Thread类,重写Thread的run()方法,将线程运行的逻辑放在其中 2. 通过实现Runnable接口,实例化Thread类 3.应用程序 ...
- 敏捷为什么会失败之「PA-SA-WAKA-DA」理论
在日常生活中,有种有趣的现象:我们更津津乐道于美好的故事,比如提到好莱坞,我们关注的只是大牌明星,却忽略了他们成名其背后的艰辛.对于那些成功的敏捷项目,也是如此.在我们见证成功的同时,却忘记了项目团队 ...
- mysql小白系列_01 原理
1.什么是MVCC?有什么作用? Multi-Version Concurrency Conrol 多版本并发控为解决数据库并发读写可能会出现不一致数据的情况,需要实现数据库的并发访问控制,写时复制产 ...
- MYSQL(1)— 初识MySQL
一.MySQL [官网文档]https://dev.mysql.com/doc/refman/8.0/en/ 1-1.Mysql是一个关系型数据库,里面的表数据是可以有联系的,例如多对一,一对多. 1 ...
- 利用Jackson封装常用JsonUtil工具类
在日常的项目开发中,接口与接口之间.前后端之间的数据传输一般都是使用JSON格式,那必然会封装一些常用的Json数据转化的工具类,本文讲解下如何利用Jackson封装高复用性的Json转换工具类. 转 ...
- css不换行解决
word-wrap: break-word; word-break: break-all; white-space: pre-wrap;
- Django + Celery 实现动态配置定时任务
哈喽,今天给大家分享一篇Django+Celery实现动态配置定时任务,因为最近也是无意间看到一位大佬关于这块的文章,然后自己觉得不错,也想学习写一下,然后最终实现功能是在前端页面统一管理计划任务,大 ...
- dsPIC33EP单片机的PPS(外设引脚选择)
利用dsPIC33EP单片机进行can通信的时候用到引脚复用 引脚复用通过查询数据手册: C1RX的寄存器为RPINR26.C1RXR=(设置为需要用到的引脚) 引脚设置为输入(C1RX),TRIS= ...
- [转]从 Apple TV 看电视的进化
电视被许多人吐槽为 “几十年没变过的东西”,因此苹果也被寄予厚望能改变这件事物.可惜的是,这种期望在空中飘了这么久,苹果也没玩出多少花样,直到这次发布会 Apple TV 才有了一些值得期待的改进. ...