C#/OWIN/ASP.NET:我可以在 API 代码中*手动*生成并获取有效的不记名令牌字符串吗?

C#/OWIN/ASP.NET: can I *manually* generate and get a valid bearer token string in my API code?

我在 OWIN ASP.NET C# web API 中使用 OWIN OAuthAuthorizationServer 库来生成和处理不记名令牌。

现在,我有一个端点(您在 OAuthAuthorizationServerOptions 结构中设置)接受来自前端的 grant_typeusernamepassword 字段.我创建了一个执行验证的提供程序 class,然后相应地调用 context.Validated()context.SetError()。然后中间件处理生成令牌并将其 return 发送给用户,以及 "takes over" 登录端点,在内部完成所有工作。

现在,我正在向我的 API 添加一项新功能,用户可以在其中更改他们的 "role"(例如,管理员可以将自己设置为普通用户以查看他们的工作结果,一个用户可以 select 在多个角色中,等等。)因为我已经通过承载令牌处理了这个(我将用户的角色存储在那里并且我的所有端点都使用承载令牌来确定当前角色),我现在有一个从 API 后端更新不记名令牌内容的原因。

我想要做的是允许前端调用将接受参数的端点(例如 api/set_role)。用户请求某个角色,他们当前的持有者令牌将伴随该请求。然后,服务器将检查是否允许相关用户使用该特定角色,如果允许,将生成一个新令牌并将其 return 发送给响应正文中的用户。然后前端将更新其在本地存储中的令牌。或者,当然,如果不允许用户切换到该角色,后端将 return 一个适当的错误,前端将做出相应的反应。

为此,我基本上希望能够手动生成令牌。类似于我在登录提供程序中使用 identity.AddClaim() 的方式,我希望能够在 API 代码中的任意位置执行此操作。该方法将负责将任何必要的现有信息(例如用户的用户名)转移到新令牌中,因为它已经具有现有信息。

我想要的伪代码:

if (!userCanUseRole(requestedRoleId)) return Request.CreateErrorResponse(...);
// we have a struct containing parsed information for the current token in the variable cToken
bearerToken newToken = new bearerToken();
newToken.AddClaim(new Claim("user", cToken.user));
newToken.AddClaim(new Claim("role", requestedRoleId));
string tokenToReturnToFrontend = newToken.getTokenString(); // string suitable for using in Authorization Bearer header
return Request.CreateResponse(new StringContent(tokenToReturnToFrontend));

我不太熟悉 "refresh" 令牌,但我现在使用它们的唯一方法是延长令牌到期时间。为此,前端明确请求刷新令牌并提供自己的令牌,后端只需将其复制到新令牌并编辑到期时间。这样做的问题是,只有一种方法可以获取刷新令牌,并且由于我现在至少有一个刷新令牌的其他原因(并且可能,未来的发展可能会增加更多理由在不同时间更改令牌内容),然后我必须处理在某个地方存储瞬态数据(例如“当请求刷新令牌时,用户想要做什么?他们要求这样做已经太久了吗?等)它会是如果我可以简单地按需生成不记名令牌,就像 OAuthAuthorizationServer 本身一样。(我知道它使用 MachineKey 来执行此操作,但我不知道不知道它是如何做到的,也不知道我将如何着手做我想做的事情。)

值得注意的是:在另一个项目中,我提供了 internal 对传递给授权服务器实例的 OAuthBearerAuthenticationOptions class 的访问权限,并且能够将其用于 解码 测试中的不记名令牌。我还没有看到任何明显的想法可以让我以这种方式编码不记名令牌。


编辑:我探索了(非常简洁,几乎无用的记录)OWIN 命名空间并找到 AccessTokenFormat class 出现 它应该做什么我想。我写了这段代码:

Microsoft.Owin.Security.AuthenticationTicket at = new Microsoft.Owin.Security.AuthenticationTicket(new ClaimsIdentity
{
    Label="claims"
}
, new Microsoft.Owin.Security.AuthenticationProperties
{
    AllowRefresh=true,
    IsPersistent=true,
    IssuedUtc=DateTime.UtcNow,
    ExpiresUtc=DateTime.UtcNow.AddMinutes(5),
});

at.Identity.AddClaim(new Claim("hello", "world"));
string token = Startup.oabao.AccessTokenFormat.Protect(at);

return Request.CreateResponse(HttpStatusCode.OK, new StringContent(token, System.Text.Encoding.ASCII, "text/plain"));

这看起来应该可行。 (我再次允许访问传递给 OAuthAuthorizationServer 实例的 OAuthBearerAuthenticationOptions class。)但是,此代码抛出 ArgumentNull 异常。堆栈跟踪指示它正在写入 BinaryWriter,但 OWIN 代码将空值传递给 BinaryWriter 上的 Write 方法。

还是没有办法。

我确实找到了实现此功能的代码。有人可能会说我是 "not using OAuth right",但严格来说,这段代码将完成我想要的——在代码中的任意点生成一个标记并获取字符串。

首先,正如我所说,我必须提供对 OAuthBearerAuthenticationOptions class 实例的访问权限。当 OAuth 服务器初始化时,我猜它会使用用于令牌的所有各种对象填充此 class。关键是我们确实可以访问 ProtectUnprotect,它们都可以直接编码和解码不记名令牌。

此代码将生成一个令牌,假设 oabao 是已传递给 OAuthAuthorizationServer 实例的 OAuthBearerAuthenticationOptions class:

Microsoft.Owin.Security.AuthenticationTicket at = new Microsoft.Owin.Security.AuthenticationTicket(new ClaimsIdentity("Bearer", "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name", "http://schemas.microsoft.com/ws/2008/06/identity/claims/role"),
    new Microsoft.Owin.Security.AuthenticationProperties
{
    AllowRefresh = true,
    IsPersistent = true,
    IssuedUtc = DateTime.UtcNow,
    ExpiresUtc = DateTime.UtcNow.AddDays(1) // whenever you want your new token's expiration to happen
});

// add any claims you want here like this:
at.Identity.AddClaim(new Claim("userRole", role));
// and so on

string token = oabao.AccessTokenFormat.Protect(at);

// You now have the token string in the token variable.