当前位置: 首页 > 知识库问答 >
问题:

在C#(web api)中测试已验证方法(使用承载令牌)的正确方法

弘承运
2023-03-14

我有一个Web API,其中包含大量的方法,这些方法都需要存在一个承载令牌才能使用。这些方法都从承载令牌中提取信息。

我想测试API在生成时是否正确填充了承载令牌。我正在使用微软。奥温。编写测试的测试框架。我有一个测试如下:

[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"));

    }
}

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

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

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");

所以我想做的是,在我的测试本身中,提供承载令牌字符串并重新接收用户。标识对象或以其他方式访问令牌包含的声明。这就是我想测试我的方法是否正确地将必要的声明添加到令牌的方式。

“天真”的方法可能是在我的API中编写一个方法,该方法只返回给定的承载令牌中的所有声明。但感觉这应该是没有必要的。ASP。NET在调用我的控制器的方法之前,以某种方式将给定的令牌解码到一个对象。我想在我自己的测试代码中复制相同的操作。

这能做到吗?如果是,如何?

编辑:我的OWIN启动类实例化了一个身份验证令牌提供程序,我编写了该提供程序,用于处理身份验证和令牌生成。在我的创业课上,我有这样一句话:

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);
}

以下是我的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声明。因此,我需要一种方法来评估提供的令牌,以测试它包含哪些声明。

编辑2:我花了一些时间查看了Katana的源代码,并在网上搜索了一些其他帖子,看起来我在IIS上托管这个应用程序很重要,所以我会使用SystemWeb。看起来SystemWeb对令牌使用了机器密钥加密。选项中的AccessTokenFormat参数似乎也与此相关。

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

关于这一点的文档有点稀疏,代码似乎把你扔得到处都是,有很多东西要在我的脑海中保持清醒。

编辑3:我发现了一个项目,其中包含应该是承载令牌反序列化器的内容。我改编了其“API”库中的代码,并一直试图使用它来解密我的API生成的令牌。

我生成了一个<代码>

但是,令牌仍然无法解密。我收到抛出的异常:System. Security. Cryptoology。Cryptograph icException,消息“在加密操作期间发生错误。”以下是错误的堆栈跟踪:

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值在整个项目中设置为相同,我不明白为什么我无法解密令牌。我猜想加密错误是故意含糊的,但我不确定现在从哪里开始解决这个问题。

我只想在单元测试中测试令牌是否包含所需的数据…:-)

共有1个答案

戚云
2023-03-14

我终于能够找到一个解决方案。我在我的Startup类中添加了一个公共变量,该变量公开了传递给UseBearerTokenAuthentication方法的OAuthBearerAuthenticationOptions对象。从该对象中,我可以调用AccessTokenFormat。取消保护并获取解密的令牌。

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

我仍然不明白为什么MachineKey的东西不起作用,为什么我不能直接取消对令牌的保护。似乎只要机器钥匙匹配,我就可以解密令牌,甚至可以手动解密。但至少这似乎是可行的,即使这不是最好的解决方案

这可能可以更干净地完成,例如,Startup类可能会以某种方式检测它是否在测试下启动,并以其他方式将对象传递给测试类,而不是将其挂在那里。但现在这似乎正是我所需要的。

我的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.
    }
}

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

 类似资料:
  • 根据文档,我应该使用这样的承载令牌:https://apigility.org/documentation/auth/authentication-oauth2

  • 我正在尝试使用 NodeJS 构建 RESTful API,但在 api 调用期间无法理解使用令牌对用户进行身份验证的正确方法。在阅读了一些博客和文章后,我想出了这些方法: Access Token(AT)是包含唯一userId作为JWT有效负载的JWT令牌。1天后到期。 刷新令牌(RT)是使用uuid npm包的随机uuid。与用户文档一起存储在数据库中。 过程: 当用户登录/注册服务器发布新A

  • 每次路由转换时验证令牌。为此,我必须在每次要验证时进行rest调用。 只通过一次rest调用验证令牌一次,然后将令牌存储在本地存储中。(令牌本身只有一个布尔值指示它是否通过了身份验证) 我关心的是不要在每一个路由事务中进行rest调用,我不想消耗那么多http流量。但是,如果没有别的办法,我会这么做的。

  • 问题内容: 我从API端点获取了承载令牌,并设置了以下内容: 接下来,我想使用CURL访问安全端点,但是不确定如何或在何处设置Bearer令牌。 我已经尝试过了,但是没有用: 编辑: 根据文档,我应该这样使用承载令牌:https : //apigility.org/documentation/auth/authentication- oauth2 问题答案: 更换: 与: 使其成为有效且有效的Au

  • 问题内容: 我的项目使用Node.js和Express,但问题在于通用方法。 我们的用户全部来自FB,除FB外,我们没有其他任何身份验证。我们需要将某些操作与特定的FB用户相关联,还需要他们的令牌才能与FB通信。 目前, 我们这样做: 用户来到页面 有一些 看不见的 块:一个带有占位符,用于存储用户的化身和姓名(“已登录”),另一个带有用于触发FB登录的按钮(“已退出”) 使用FB JS SDK,

  • 我正在尝试在ASP.NET5中实现OAuth承载令牌身份验证,并且正在努力寻找一个如何实现这一点的示例,因为OWIN的东西在ASP.NET5中发生了变化。 例如IApplicationBuilder.UseOAuthAuthorizationServer()和IApplicationBuilder。UseOAuthBearerAuthentication()要么不再存在,要么缺少引用? 如有任何指