gpt4 book ai didi

c# - 在 C# (web api) 中测试经过身份验证的方法(使用不记名 token )的正确方法

转载 作者:太空狗 更新时间:2023-10-29 20:58:14 26 4
gpt4 key购买 nike

我有一个包含大量方法的 Web API,所有这些方法都需要持有者 token 才能使用。这些方法都是从不记名 token 中提取信息。

我想测试 API 是否在其生成时正确填充不记名 token 。我正在使用 Microsoft.Owin.Testing 框架来编写我的测试。我有一个看起来像这样的测试:

[TestMethod]
public async Task test_Login()
{
using (var server = TestServer.Create<Startup>())
{
var req = server.CreateRequest("/authtoken");
req.AddHeader("Content-Type", "application/x-www-form-urlencoded");
req.And(x => x.Content = new StringContent("grant_type=password&username=test&password=1234", System.Text.Encoding.ASCII));
var response = await req.GetAsync();

// Did the request produce a 200 OK response?
Assert.AreEqual(response.StatusCode, System.Net.HttpStatusCode.OK);

// Retrieve the content of the response
string responseBody = await response.Content.ReadAsStringAsync();
// this uses a custom method for deserializing JSON to a dictionary of objects using JSON.NET
Dictionary<string, object> responseData = deserializeToDictionary(responseBody);

// Did the response come with an access token?
Assert.IsTrue(responseData.ContainsKey("access_token"));

}
}

所以我能够检索表示 token 的字符串。但现在我想实际访问该 token 的内容,并确保提供了某些声明。

我将在实际经过身份验证的方法中用于检查声明的代码如下所示:

var identity = (ClaimsIdentity)User.Identity;
IEnumerable<Claim> claims = identity.Claims;

var claimTypes = from x in claims select x.Type;

if (!claimTypes.Contains("customData"))
throw new InvalidOperationException("Not authorized");

因此,我希望能够做的是,在我的测试本身中,提供不记名 token 字符串并接收 User.Identity 对象或以其他方式获得对 token 包含的声明的访问权限。这就是我想要测试我的方法是否正确地将必要的声明添加到 token 的方式。

“天真”方法可能是在我的 API 中编写一个方法,该方法仅返回给定的不记名 token 中的所有声明。但感觉这应该是不必要的。在调用我的 Controller 的方法之前,ASP.NET 以某种方式将给定的标记解码为对象。我想在我的测试代码中自己复制相同的操作。

这能做到吗?如果是,怎么办?


编辑:我的 OWIN 启动类实例化了一个我编写的身份验证 token 提供程序,它处理身份验证和 token 生成。在我的启动类中,我有这个:

public void Configuration(IAppBuilder app)
{
// Setup configuration object
HttpConfiguration config = new HttpConfiguration();

// Web API configuration and services
// Configure Web API to use only bearer token authentication.
config.SuppressDefaultHostAuthentication();
config.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType));

// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);

// configure the OAUTH server
OAuthAuthorizationServerOptions OAuthServerOptions = new OAuthAuthorizationServerOptions()
{
//AllowInsecureHttp = false,
AllowInsecureHttp = true, // THIS HAS TO BE CHANGED BEFORE PUBLISHING!

TokenEndpointPath = new PathString("/authtoken"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
Provider = new API.Middleware.MyOAuthProvider()
};

// Now we setup the actual OWIN pipeline.

// setup CORS support
// in production we will only allow from the correct URLs.
app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);

// Token Generation
app.UseOAuthAuthorizationServer(OAuthServerOptions);
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());

// insert actual web API and we're off!
app.UseWebApi(config);
}

这是来 self 的 OAuth 提供商的相关代码:

public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{

// Will be used near end of function
bool isValidUser = false;

// Simple sanity check: all usernames must begin with a lowercase character
Match testCheck = Regex.Match(context.UserName, "^[a-z]{1}.+$");
if (testCheck.Success==false)
{
context.SetError("invalid_grant", "Invalid credentials.");
return;
}

string userExtraInfo;
// Here we check the database for a valid user.
// If the user is valid, isValidUser will be set to True.
// Invalid authentications will return null from the method below.
userExtraInfo = DBAccess.getUserInfo(context.UserName, context.Password);
if (userExtraInfo != null) isValidUser = true;

if (!isValidUser)
{
context.SetError("invalid_grant", "Invalid credentials.");
return;
}

// The database validated the user. We will include the username in the token.
string userName = context.UserName;

// generate a claims object
var identity = new ClaimsIdentity(context.Options.AuthenticationType);

// add the username to the token
identity.AddClaim(new Claim(ClaimTypes.Sid, userName));

// add the custom data on the user to the token.
identity.AddClaim(new Claim("customData", userExtraInfo));

// store token expiry so the consumer can determine expiration time
DateTime expiresAt = DateTime.Now.Add(context.Options.AccessTokenExpireTimeSpan);
identity.AddClaim(new Claim("expiry", expiresAt.ToString()));

// Validate the request and generate a token.
context.Validated(identity);

}

单元测试要确保 customData声明实际上存在于身份验证 token 中。因此,我需要一种方法来评估提供的 token 以测试它包含哪些声明。


编辑 2:我花了一些时间查看 Katana 源代码并在线搜索了一些其他帖子,看起来我在 IIS 上托管此应用程序很重要,因此我将使用 SystemWeb。看起来 SystemWeb 对 token 使用机器 key 加密。它也看起来像 AccessTokenFormat选项中的参数与此处相关。

所以现在我想知道我是否可以根据这些知识实例化我自己的“解码器”。假设我只会在 IIS 上托管,我可以实例化一个解码器,然后解码 token 并将其转换为 Claims 对象吗?

这方面的文档有点稀疏,代码似乎把你扔得乱七八糟,很多东西都试图让我的头脑保持清醒。


编辑 3:我找到了一个包含不记名 token 反序列化器的项目。我在它的“API”库中修改了代码,并一直在尝试用它来解密我的 API 生成的 token 。

我生成了一个 <machineKey...>使用 Microsoft 的 PowerShell 脚本获取值,并将其放置在 API 本身的 Web.config 文件和测试项目中的 App.confg 文件中。

然而, token 仍然无法解密。我收到抛出的异常:System.Security.Cryptography.CryptographicException带有消息 "Error occurred during a cryptographic operation."以下是错误的堆栈跟踪:

at System.Web.Security.Cryptography.HomogenizingCryptoServiceWrapper.HomogenizeErrors(Func`2 func, Byte[] input)
at System.Web.Security.Cryptography.HomogenizingCryptoServiceWrapper.Unprotect(Byte[] protectedData)
at System.Web.Security.MachineKey.Unprotect(ICryptoServiceProvider cryptoServiceProvider, Byte[] protectedData, String[] purposes)
at System.Web.Security.MachineKey.Unprotect(Byte[] protectedData, String[] purposes)
at MyAPI.Tests.BearerTokenAPI.MachineKeyDataProtector.Unprotect(Byte[] protectedData) in D:\Source\MyAPI\MyAPI.WebAPI.Tests\BearerTokenAPI.cs:line 251
at MyAPI.Tests.BearerTokenAPI.SecureDataFormat`1.Unprotect(String protectedText) in D:\Source\MyAPI\MyAPI.WebAPI.Tests\BearerTokenAPI.cs:line 287

此时我被难住了。在整个项目中将 MachineKey 值设置为相同的情况下,我不明白为什么我无法解密 token 。我猜密码错误是故意含糊不清的,但我不确定现在从哪里开始解决这个问题。

我只想在单元测试中测试 token 是否包含所需数据....:-)

最佳答案

我终于找到了解决办法。我向我的 Startup 类添加了一个公共(public)变量,该类公开传递到 UseBearerTokenAuthentication 方法中的 OAuthBearerAuthenticationOptions 对象。从该对象,我可以调用 AccessTokenFormat.Unprotect 并获得解密的 token 。

我还重写了我的测试以单独实例化 Startup 类,这样我就可以从测试中访问该值。

我仍然不明白为什么 MachineKey 不起作用,为什么我不能直接解除对 token 的保护。似乎只要 MachineKey 匹配,我就应该能够解密 token ,甚至是手动解密。但至少这似乎可行,即使它不是最佳解决方案。

这可能会做得更干净,例如,也许 Startup 类可以以某种方式检测它是否正在测试中启动,并以某种其他方式将对象传递给测试类,而不是让它随风飘荡。但就目前而言,这似乎完全符合我的需要。

我的启动类以这种方式公开变量:

public partial class Startup
{
public OAuthBearerAuthenticationOptions oabao;

public void Configuration(IAppBuilder app)
{

// repeated code omitted

// Token Generation
app.UseOAuthAuthorizationServer(OAuthServerOptions);
oabao = new OAuthBearerAuthenticationOptions();
app.UseOAuthBearerAuthentication(oabao);

// insert actual web API and we're off!
app.UseWebApi(config);

}
}

我的测试现在看起来像这样:

[TestMethod]
public async Task Test_SignIn()
{
Startup owinStartup = new Startup();
Action<IAppBuilder> owinStartupAction = new Action<IAppBuilder>(owinStartup.Configuration);

using (var server = TestServer.Create(owinStartupAction))
{
var req = server.CreateRequest("/authtoken");
req.AddHeader("Content-Type", "application/x-www-form-urlencoded");

// repeated code omitted

// Is the access token of an appropriate length?
string access_token = responseData["access_token"].ToString();
Assert.IsTrue(access_token.Length > 32);

AuthenticationTicket token = owinStartup.oabao.AccessTokenFormat.Unprotect(access_token);

// now I can check whatever I want on the token.
}
}

希望我所有的努力能帮助其他尝试做类似事情的人。

关于c# - 在 C# (web api) 中测试经过身份验证的方法(使用不记名 token )的正确方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43267763/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com