如何使用外部登录提供程序创建刷新令牌?
Posted
技术标签:
【中文标题】如何使用外部登录提供程序创建刷新令牌?【英文标题】:How to create Refresh Token with External Login Provider? 【发布时间】:2014-11-02 14:02:09 【问题描述】:我在网上搜索过,但找不到解决问题的方法。我正在我的应用程序中实现 OAuth。我正在使用 ASP .NET Web API 2 和 Owin。场景是这样的,一旦用户请求令牌端点,他或她将收到一个访问令牌以及一个刷新令牌以生成新的访问令牌。我有一个类可以帮助我生成刷新令牌。就是这样:
public class SimpleRefreshTokenProvider : IAuthenticationTokenProvider
private static ConcurrentDictionary<string, AuthenticationTicket> _refreshTokens = new ConcurrentDictionary<string, AuthenticationTicket>();
public async Task CreateAsync(AuthenticationTokenCreateContext context)
var refreshTokenId = Guid.NewGuid().ToString("n");
using (AuthRepository _repo = new AuthRepository())
var refreshTokenLifeTime = context.OwinContext.Get<string> ("as:clientRefreshTokenLifeTime");
var token = new RefreshToken()
Id = Helper.GetHash(refreshTokenId),
ClientId = clientid,
Subject = context.Ticket.Identity.Name,
IssuedUtc = DateTime.UtcNow,
ExpiresUtc = DateTime.UtcNow.AddMinutes(15)
;
context.Ticket.Properties.IssuedUtc = token.IssuedUtc;
context.Ticket.Properties.ExpiresUtc = token.ExpiresUtc;
token.ProtectedTicket = context.SerializeTicket();
var result = await _repo.AddRefreshToken(token);
if (result)
context.SetToken(refreshTokenId);
// this method will be used to generate Access Token using the Refresh Token
public async Task ReceiveAsync(AuthenticationTokenReceiveContext context)
string hashedTokenId = Helper.GetHash(context.Token);
using (AuthRepository _repo = new AuthRepository())
var refreshToken = await _repo.FindRefreshToken(hashedTokenId);
if (refreshToken != null )
//Get protectedTicket from refreshToken class
context.DeserializeTicket(refreshToken.ProtectedTicket);
// one refresh token per user and client
var result = await _repo.RemoveRefreshToken(hashedTokenId);
public void Create(AuthenticationTokenCreateContext context)
throw new NotImplementedException();
public void Receive(AuthenticationTokenReceiveContext context)
throw new NotImplementedException();
现在我允许我的用户通过 facebook 注册。一旦用户在 facebook 上注册,我会生成一个访问令牌并将其提供给他。我也应该生成一个刷新令牌吗?我想到了一件事,就是像一天一样生成一个长访问令牌,然后这个用户必须再次使用 facebook 登录。但是如果我不想这样做,我可以给客户端一个刷新令牌,他可以使用它来刷新生成的访问令牌并获得一个新的。当有人使用 facebook 或外部注册或登录时,如何创建刷新令牌并将其附加到响应中?
这是我的外部注册 API
public class AccountController : ApiController
[AllowAnonymous]
[Route("RegisterExternal")]
public async Task<IHttpActionResult> RegisterExternal(RegisterExternalBindingModel model)
if (!ModelState.IsValid)
return BadRequest(ModelState);
var accessTokenResponse = GenerateLocalAccessTokenResponse(model.UserName);
return Ok(accessTokenResponse);
// 生成访问令牌的私有方法
private JObject GenerateLocalAccessTokenResponse(string userName)
var tokenExpiration = TimeSpan.FromDays(1);
ClaimsIdentity identity = new ClaimsIdentity(OAuthDefaults.AuthenticationType);
identity.AddClaim(new Claim(ClaimTypes.Name, userName));
identity.AddClaim(new Claim("role", "user"));
var props = new AuthenticationProperties()
IssuedUtc = DateTime.UtcNow,
ExpiresUtc = DateTime.UtcNow.Add(tokenExpiration),
;
var ticket = new AuthenticationTicket(identity, props);
var accessToken = Startup.OAuthBearerOptions.AccessTokenFormat.Protect(ticket);
JObject tokenResponse = new JObject(
new JProperty("userName", userName),
new JProperty("access_token", accessToken),
// Here is what I need
new JProperty("resfresh_token", GetRefreshToken()),
new JProperty("token_type", "bearer"),
new JProperty("refresh_token",refreshToken),
new JProperty("expires_in", tokenExpiration.TotalSeconds.ToString()),
new JProperty(".issued", ticket.Properties.IssuedUtc.ToString()),
new JProperty(".expires", ticket.Properties.ExpiresUtc.ToString())
);
return tokenResponse;
【问题讨论】:
关于该主题的任何更新?我有完全相同的问题。 【参考方案1】:我花了很多时间来寻找这个问题的答案。所以,我很乐意为您提供帮助。
1) 更改您的 ExternalLogin 方法。 它通常看起来像:
if (hasRegistered)
Authentication.SignOut(DefaultAuthenticationTypes.ExternalCookie);
ClaimsIdentity oAuthIdentity = await user.GenerateUserIdentityAsync(UserManager,
OAuthDefaults.AuthenticationType);
ClaimsIdentity cookieIdentity = await user.GenerateUserIdentityAsync(UserManager,
CookieAuthenticationDefaults.AuthenticationType);
AuthenticationProperties properties = ApplicationOAuthProvider.CreateProperties(user.UserName);
Authentication.SignIn(properties, oAuthIdentity, cookieIdentity);
现在,实际上,需要添加 refresh_token。 方法如下所示:
if (hasRegistered)
Authentication.SignOut(DefaultAuthenticationTypes.ExternalCookie);
ClaimsIdentity oAuthIdentity = await user.GenerateUserIdentityAsync(UserManager,
OAuthDefaults.AuthenticationType);
ClaimsIdentity cookieIdentity = await user.GenerateUserIdentityAsync(UserManager,
CookieAuthenticationDefaults.AuthenticationType);
AuthenticationProperties properties = ApplicationOAuthProvider.CreateProperties(user.UserName);
// ADD THIS PART
var ticket = new AuthenticationTicket(oAuthIdentity, properties);
var accessToken = Startup.OAuthOptions.AccessTokenFormat.Protect(ticket);
Microsoft.Owin.Security.Infrastructure.AuthenticationTokenCreateContext context =
new Microsoft.Owin.Security.Infrastructure.AuthenticationTokenCreateContext(
Request.GetOwinContext(),
Startup.OAuthOptions.AccessTokenFormat, ticket);
await Startup.OAuthOptions.RefreshTokenProvider.CreateAsync(context);
properties.Dictionary.Add("refresh_token", context.Token);
Authentication.SignIn(properties, oAuthIdentity, cookieIdentity);
现在将生成刷新令牌。
2) 在 SimpleRefreshTokenProvider CreateAsync 方法中使用基本 context.SerializeTicket 存在问题。 来自Bit Of Technology的消息
好像在ReceiveAsync方法中,context.DeserializeTicket不是 在外部登录案例中完全返回身份验证票。 当我在调用之后查看 context.Ticket 属性时,它是空的。 与本地登录流程相比,DeserializeTicket 方法 将 context.Ticket 属性设置为 AuthenticationTicket。所以 现在的谜团是 DeserializeTicket 的行为如何不同 两个流动。创建数据库中受保护的票证字符串 在同一个 CreateAsync 方法中,不同之处仅在于我称之为 在 GenerateLocalAccessTokenResponse 中手动方法,与 Owin middlware 正常调用它...... SerializeTicket 或 DeserializeTicket 抛出错误……
因此,您需要使用 Microsoft.Owin.Security.DataHandler.Serializer.TicketSerializer 对票证进行序列化和反序列化。 它看起来像这样:
Microsoft.Owin.Security.DataHandler.Serializer.TicketSerializer serializer
= new Microsoft.Owin.Security.DataHandler.Serializer.TicketSerializer();
token.ProtectedTicket = System.Text.Encoding.Default.GetString(serializer.Serialize(context.Ticket));
代替:
token.ProtectedTicket = context.SerializeTicket();
对于 ReceiveAsync 方法:
Microsoft.Owin.Security.DataHandler.Serializer.TicketSerializer serializer = new Microsoft.Owin.Security.DataHandler.Serializer.TicketSerializer();
context.SetTicket(serializer.Deserialize(System.Text.Encoding.Default.GetBytes(refreshToken.ProtectedTicket)));
代替:
context.DeserializeTicket(refreshToken.ProtectedTicket);
3) 现在您需要将 refresh_token 添加到 ExternalLogin 方法响应中。 覆盖 OAuthAuthorizationServerProvider 中的 AuthorizationEndpointResponse。像这样的:
public override Task AuthorizationEndpointResponse(OAuthAuthorizationEndpointResponseContext context)
var refreshToken = context.OwinContext.Authentication.AuthenticationResponseGrant.Properties.Dictionary["refresh_token"];
if (!string.IsNullOrEmpty(refreshToken))
context.AdditionalResponseParameters.Add("refresh_token", refreshToken);
return base.AuthorizationEndpointResponse(context);
所以.. 就是这样!现在,在调用 ExternalLogin 方法后,您将获得 url: https://localhost:44301/Account/ExternalLoginCallback?access_token=ACCESS_TOKEN&token_type=bearer&expires_in=300&state=STATE&refresh_token=TICKET&returnUrl=URL
希望对你有帮助)
【讨论】:
感谢您的努力。实际上,我正在将此代码用于我停止处理的项目。但是,我会尝试这段代码,一旦我再次开始处理它,我会让你知道我的反馈。但再次感谢您的努力,非常感谢您的帮助。 很高兴为您提供帮助)随时提问。 首先感谢您的解决方案。但由于某种原因,它对我不起作用。一旦我使用外部登录提供程序,我确实得到了 refresh_token,但是当我尝试使用它来获取新的访问令牌时,我得到一个 400 Bad Request 错误,正文:“error”:“invalid_grant”服务器上没有异常,我只是不知道如何调试它。 :-| (尝试了您的解决方案和@Wouter Crooy 提出的解决方案)您能否创建一个示例解决方案,或者给我任何提示? Alexander,你确定你收到的 refresh_token 是有效的吗?如果令牌无效/过期/错误序列化,则可以获得消息 "error":"invalid_grant"。尝试调试 RefreshTokenProvider 中的 ReceiveAsync/Receive 方法,确保令牌存储在数据库中并正确反序列化。 谢谢,正常登录成功实现了这个。这样做时不要忘记验证您的 client_id!【参考方案2】:@giraffe 和其他人
几点说明。无需使用 custom 代码序列化器。
下面一行:
Microsoft.Owin.Security.Infrastructure.AuthenticationTokenCreateContext context =
new Microsoft.Owin.Security.Infrastructure.AuthenticationTokenCreateContext(
Request.GetOwinContext(),
Startup.OAuthOptions.AccessTokenFormat, ticket);
使用令牌格式:Startup.OAuthOptions.AccessTokenFormat
。由于我们要提供一个刷新令牌,因此需要将其更改为:Startup.OAuthOptions.RefreshTokenFormat
否则,如果您想获取新的访问令牌并刷新刷新令牌(grant_type=refresh_token&refresh_token=......),反序列化器/取消保护器将失败。因为它在解密阶段使用了错误的目的关键字。
【讨论】:
感谢您的评论!也许您是对的,但就我而言,该过程运行正常。下次我将尝试实现您的变体!【参考方案3】:终于找到了解决我问题的方法。 首先,如果您在使用 OWIN 时遇到任何问题并且无法弄清楚出了什么问题,我建议您简单地启用符号调试并对其进行调试。可以在这里找到一个很好的解释: http://www.symbolsource.org/Public/Home/VisualStudio
我的错误只是,我在使用外部登录提供程序时计算了错误的 ExiresUtc。所以我的refreshtoken基本上总是马上过期....
如果您要实现刷新令牌,请查看这篇博客文章: http://bitoftech.net/2014/07/16/enable-oauth-refresh-tokens-angularjs-app-using-asp-net-web-api-2-owin/
要使其与外部提供商的刷新令牌一起使用,您必须在上下文中设置两个必需的参数(“as:clientAllowedOrigin”和“as:clientRefreshTokenLifeTime”) 所以而不是
var ticket = new AuthenticationTicket(oAuthIdentity, properties); var context = new Microsoft.Owin.Security.Infrastructure.AuthenticationTokenCreateContext( Request.GetOwinContext(), Startup.OAuthOptions.AccessTokenFormat,票证); 等待 Startup.OAuthOptions.RefreshTokenProvider.CreateAsync(context); properties.Dictionary.Add("refresh_token", context.Token);需要先获取客户端并设置上下文参数
// 从数据库中检索客户端 var client = authRepository.FindClient(client_id); // 仅当客户端注册时才生成刷新令牌 如果(客户端!= null) var ticket = new AuthenticationTicket(oAuthIdentity, properties); var context = new AuthenticationTokenCreateContext(Request.GetOwinContext(), AuthConfig.OAuthOptions.RefreshTokenFormat, ticket); // 设置这两个上下文参数,否则不起作用!! context.OwinContext.Set("as:clientAllowedOrigin", client.AllowedOrigin); context.OwinContext.Set("as:clientRefreshTokenLifeTime", client.RefreshTokenLifeTime.ToString()); 等待 AuthConfig.OAuthOptions.RefreshTokenProvider.CreateAsync(context); properties.Dictionary.Add("refresh_token", context.Token);【讨论】:
以上是关于如何使用外部登录提供程序创建刷新令牌?的主要内容,如果未能解决你的问题,请参考以下文章
如何从外部应用程序安全地使用json web令牌,与wordpress rest api对话