我遇到了一个非常奇怪的问题,在托管我的Wep Api项目的Azure应用服务上几个小时后,刷新令牌“消失”了。我已经为我的密码流程实现了OAuth。我们的AccessToken在1小时后过期,我们的RefreshToken在一周后过期。
对于某些背景。这是在我托管Web Api的Azure应用服务上发生的,移动前端正在对其进行调用(有多个用户/移动设备对此应用服务进行了调用)。
这是使用/token
呼叫的示例初始呼叫的样子:
我的grant_type
是密码。通常我会返回refresh_token
字段以及access_token
,token_type
和expires_in
。
在我推送到应用程序服务后的头几个小时工作正常,然后refresh_token消失了。这个问题让我很困惑。
这是我的CreateAsync
代码:
public async Task CreateAsync(AuthenticationTokenCreateContext context)
{
var clientid = context.Ticket.Properties.Dictionary["as:client_id"];
if (string.IsNullOrEmpty(clientid))
{
return;
}
string refreshTokenId = await CreateRefreshTokenId(clientid, context);
if (refreshTokenId != null)
{
context.SetToken(refreshTokenId);
}
else
{
throw new Exception("refresh token could not be created");
}
}
private async Task<string> CreateRefreshTokenId(string clientId, AuthenticationTokenCreateContext context)
{
var ticket = context.Ticket;
var refreshTokenId = Guid.NewGuid().ToString("n");
var refreshTokenLifeTime = ConfigurationManager.AppSettings["as:clientRefreshTokenLifeTime"];
var token = new CreateRefreshTokenDTO
{
RefreshTokenId = refreshTokenId,
ClientId = clientId,
Subject = ticket.Identity.Name,
IssuedUtc = DateTime.UtcNow,
ExpiresUtc = DateTime.UtcNow.AddMinutes(Convert.ToDouble(refreshTokenLifeTime))
};
ticket.Properties.IssuedUtc = token.IssuedUtc;
ticket.Properties.ExpiresUtc = token.ExpiresUtc;
token.ProtectedTicket = context.SerializeTicket();
var result = await createRefreshTokenManager.ManagerRequest(new CreateRefreshTokenRequest
{
RefreshToken = token
});
return result.IsError ? null : refreshTokenId;
}
我已在else语句中添加了异常,以查看是否会抛出异常,而实际上确实会抛出异常,这使我相信refreshTokenId为null。我还向日志表中添加了日志记录,但无论出于何种原因,抛出此错误时,都应将其保存到DB表(我已经在本地进行了测试并可以正常工作),但在App服务器上却没有保存到表中。非常令人困惑... 更新:请参阅下面的更新,为什么没有保存日志
然后,在此之后应该发生的事情是,既然前端(在这种情况下为移动设备)具有访问令牌和刷新令牌,则当访问令牌到期时,将再次调用/token
,但使用Grant_type = refresh_token
:
UPDATE
最终,我能够通过反复试验在本地重现该问题,并等待访问令牌到期(不确定)。但无论如何,我都能产生此错误:
Store update, insert, or delete statement affected an unexpected number of rows (0). Entities may have been modified or deleted since entities were loaded. See http://go.microsoft.com/fwlink/?LinkId=472540 for information on understanding and handling optimistic concurrency exceptions.
此错误是我无法将任何日志保存到数据库的原因。
我使用温莎城堡作为我的IoC和EF6。我的呼叫按此顺序:
1]尝试验证上下文。在这里,我对await
进行了另一个LoginUserManager
调用,基本上可以获取并验证用户信息
// This is used for validating the context
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
2] CreateAsync用于从上下文创建访问和刷新令牌
public async Task CreateAsync(AuthenticationTokenCreateContext context)
在CreateAsync内部,我进行了await
调用CreateOrUpdateRefreshTokenManager
,如果存在条目或创建,则会进行更新。并最终做出SaveChanges()
。 此SaveChanges()
是引起错误的原因如果我不调用SaveChanges()
,则该表中不会更新或创建任何条目。这很奇怪,因为在我的代码的其他部分中,我在Web请求生命周期结束时根本不调用SaveChanges()
,但进行了更新/创建/删除。我假设EF / Windsor为我处理了储蓄。
[我的想法是,因为此流与我的所有其他终结点不同,并且它处理两个异步调用,而这些异步调用在我之间放置DbContext,所以也许我在第二次(CreateAsync
)调用中看到它失败。不确定,只是我的想法在这里。
无论如何,对于这里冗长的帖子深表歉意。我想发布尽可能多的信息,希望这也可以帮助面临此问题或类似问题的其他人。
谢谢!
UPDATE 2
[我已经注意到,在/token
调用中收到此错误之后,所有其他(AllowAnonymous)调用都会使我工作-甚至那些涉及数据库的调用。但是/token
调用不再有效。解决此问题的唯一方法是重新启动服务器。
UPDATE 3我只能在移动测试(链接到Azure服务器)上重现此问题,但无法在本地重现。我以前复制的步骤:
好的,我能够弄清楚这个问题,我会尽力描述这里发生的事情。
对于那些按照教程学习this或任何其他类似的教程的人,您会发现基本上已经设置了一些存储库结构以及可能继承自基本上下文的自己的上下文,对?
就我而言,我通过覆盖ApiController
类中的Dispose(bool dispose)方法在请求的末尾处理上下文的Dispose。如果要构建WebApi,您将知道我在说什么,因为您编写的任何控制器都将继承此内容。如果您在生命周期中设置了一些IoC,并将生命周期设置为请求的末尾,它将在此进行处理:)
但是,在这种情况下,在/token
调用中,我注意到我们从未遇到过任何控制器...或者至少没有一个控制器使用过ApiController,因此我什至无法使用该Dispose方法。这意味着上下文保持“活动”状态。在对/token
端点的第二,第三,第四等调用中,我在观察程序中注意到上下文调用正在建立(这意味着它将先前的编辑保存到我从先前的/token
调用所做的上下文中,永不处置!因此使上下文陈旧)。
[不幸的是,针对我的情况,解决此问题的方法是将/token
请求中进行的所有上下文调用都包装在using
语句中,以这种方式,我知道using
完成后可以正确处理。这似乎可行:)
因此,如果您要布置一个类似的项目或与我分享的该教程相似,则可能会遇到此问题。