我遇到了一个非常奇怪的问题,在托管我的Wep Api项目的Azure应用服务上几个小时后,刷新令牌“消失”了。我已经为我的密码流程实现了OAuth。我们的AccessToken会在1小时后过期,而我们的RefreshToken会在一周后过期。
对于某些背景。这是在Azure应用服务上发生的,我托管了我的Web Api,并且移动前端正在对其进行调用(有多个用户/移动设备对此应用服务进行了调用)。
这是使用/token
call的示例初始呼叫的样子:
我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
:
更新
最终,我能够通过反复试验在本地重现该问题,并等待访问令牌到期(不确定)。但是无论如何,我都能产生这个错误:
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)
Inside CreateAsync I make an await
call CreateOrUpdateRefreshTokenManager
which either does an Update if entry exists or a Create. And ultimately make a SaveChanges()
. This SaveChanges()
is what causes the error If I don't call a SaveChanges()
no entry is updated or created in that table. This is odd because in other parts of my code i dont call SaveChanges()
at all at the end of the web request lifecycle yet an update/create/delete is made. Im assuming that EF/Windsor handles the saving for me.
My thoughts is that because this flow is different from all my other endpoints and that its handling two Async calls that somewhere in between I am disposing the DbContext and that is maybe why im seeing it failing on the second (CreateAsync
) call. Not sure, just my thought here.
Anyway, sorry for the long winded post here. I wanted to post as much info as possible and am hoping that this may also help someone else facing this or similar issue.
Thanks!
UPDATE 2
I've noticed that after getting this error on /token
call, any other (AllowAnonymous) calls i make work - even those that involve the DB. But the /token
call in particular no longer works. My only way around this is to restart the server.
UPDATE 3 I was able to reproduce this issu ONLY on mobile testing (linked to Azure server) but cannot reproduce locally. Steps I used to reproduce:
Alright ya'll I was able to figure out this issue and i'll do my best to describe what was happening here.
For those of you who have followed a tutorial such as this one or any other similar one, you'll see that you basically have some repository structure set up along with maybe your own context which you inherit the base context, right?
In my case, I was handling the Dispose of the context at the end of the request by overriding the Dispose(bool disposing) method found in the ApiController
class. If you're building a WebApi, you'll know what im talking about because any controllers you write inherit this. And if you've got some IoC set up with Lifetimes set to the end of a request, it'll dispose there :)
However, in this case of the /token
call I noticed that we were never hitting any controllers...or at least none that utilized ApiController so i couldn't even hit that Dispose method. That meant that the context stayed "active". And in my second, third, fourth, etc calls to /token
endpoint, I noticed in the watcher that the context calls were building up (meaning it was saving prior edits to the context i made from previous /token
calls - they were never getting disposed! Thus making the context stale).
Unfortunately, the way around this for my situation was to wrap any context calls made within the /token
request in a using
statement, that way i knew after the using
finished up it would dispose properly. And this seemed to work :)
因此,如果您有一个与我共享的类似的项目或与我共享的教程类似,则可能会遇到此问题。
本文收集自互联网,转载请注明来源。
如有侵权,请联系 [email protected] 删除。
我来说两句