Asp.net 如何使用外部登录提供程序创建刷新令牌?

Asp.net 如何使用外部登录提供程序创建刷新令牌?,asp.net,oauth,Asp.net,Oauth,我在网上搜索过,找不到解决问题的方法。我正在我的应用程序中实现OAuth。我正在使用ASP.NETWebAPI2和Owin。场景是这样的,一旦用户请求令牌端点,他或她将收到一个访问令牌和一个刷新令牌,以生成一个新的访问令牌。我有一个类帮助我生成刷新令牌。这是: public class SimpleRefreshTokenProvider : IAuthenticationTokenProvider { private static ConcurrentDicti

我在网上搜索过,找不到解决问题的方法。我正在我的应用程序中实现OAuth。我正在使用ASP.NETWebAPI2和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();
        }
    }

我花了很多时间来寻找这个问题的答案。所以,我很乐意帮助你

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);
}
现在,实际上,需要添加刷新令牌。 方法将如下所示:

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);
}
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));
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);
}
现在将生成refrehs令牌

2) 在SimpleRefreshTokenProvider CreateAync方法中使用basic context.SerializeTicket时出现问题。 来自

似乎在ReceiveAsync方法中,context.DeserializeTicket不是 在外部登录案例中返回身份验证票证。 当我在调用后查看context.Ticket属性时,它是null。 与本地登录流相比,DeserializeTicket方法 将context.Ticket属性设置为AuthenticationTicket。所以 现在的谜团是,为什么反序列化客户端在 这两种流动。将创建数据库中受保护的票证字符串 在相同的CreateAsync方法中,不同之处在于我调用 方法在GenerateLocalAccessTokenResponse中手动执行,而不是在Owin中执行 我们都在正常地叫它…而且都没有 DeserializeTicket引发错误

因此,您需要使用Microsoft.Owin.Security.DataHandler.Serializer.TicketSerializer对票证进行搜索和反序列化。 它将是这样的:

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);
}
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));
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);
}
而不是:

token.ProtectedTicket = context.SerializeTicket();
context.DeserializeTicket(refreshToken.ProtectedTicket);
对于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)));
而不是:

token.ProtectedTicket = context.SerializeTicket();
context.DeserializeTicket(refreshToken.ProtectedTicket);
3) 现在您需要将refresh\u令牌添加到ExternalLogin方法响应中。 覆盖OAuthAuthorizationServerProvider中的AuthorizationEndpointResponse。大概是这样的:

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


我希望这能有所帮助)

@当然还有长颈鹿和其他动物

几句话。无需使用自定义tickerserializer

以下一行:

Microsoft.Owin.Security.Infrastructure.AuthenticationTokenCreateContext上下文=
新的Microsoft.Owin.Security.Infrastructure.AuthenticationTokenCreateContext(
Request.GetOwinContext(),
Startup.OAuthOptions.AccessTokenFormat,票证);
作为令牌格式:
Startup.OAuthOptions.AccessTokenFormat
。由于我们希望提供refeshtoken,因此需要将其更改为:
Startup.OAuthOptions.refreshttokenformat


否则,如果您想要获取新的accesstoken并刷新refreshtoken(grant\U type=refresh\U token&refresh\U token=…),反序列化程序/反保护程序将失败。因为它在解密阶段使用了错误的关键字。

终于找到了解决我问题的方法。 首先,如果您在OWIN中遇到任何问题,并且无法找出问题所在,我建议您启用符号调试并调试它。这里可以找到一个很好的解释:

我的错误只是,在使用外部登录提供程序时,我计算了一个错误的exireutc。所以我的refreshtoken基本上总是马上过期

如果您正在实现刷新令牌,请参阅这篇gread博客文章:

要使其与外部提供者的刷新令牌一起工作,必须在上下文上设置两个所需参数(“as:clientAllowedOrigin”和“as:clientRefreshTokenLifeTime”) 所以不是

var ticket = new AuthenticationTicket(oAuthIdentity, properties); var 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); var票证=新的身份验证票证(oAuthIdentity,属性); var context=new Microsoft.Owin.Security.Infrastructure.AuthenticationTokenCreateContext( Request.GetOwinContext(), Startup.OAuthOptions.AccessTokenFormat,票证); wait Startup.OAuthOptions.RefreshTokenProvider.CreateAsync(上下文); properties.Dictionary.Add(“刷新令牌”,context.token); 您需要首先获取客户端并设置上下文参数

// retrieve client from database var client = authRepository.FindClient(client_id); // only generate refresh token if client is registered if (client != null) { var ticket = new AuthenticationTicket(oAuthIdentity, properties); var context = new AuthenticationTokenCreateContext(Request.GetOwinContext(), AuthConfig.OAuthOptions.RefreshTokenFormat, ticket); // Set this two context parameters or it won't work!! context.OwinContext.Set("as:clientAllowedOrigin", client.AllowedOrigin); context.OwinContext.Set("as:clientRefreshTokenLifeTime", client.RefreshTokenLifeTime.ToString()); await AuthConfig.OAuthOptions.RefreshTokenProvider.CreateAsync(context); properties.Dictionary.Add("refresh_token", context.Token); } //从数据库检索客户端 var client=authRepository.FindClient(客户端id); //仅当客户端已注册时生成刷新令牌 如果(客户端!=null) { var票证=新的身份验证票证(oAuthIdentity,属性); var context=newauthenticationtokencreatecontext(Request.GetOwinContext(),AuthConfig.OAuthOptions.refreshttokenformat,ticket); //设置这两个上下文参数,否则它将不起作用!! context.OwinContext.Set(“as:clientAllowedOrigin”,client.AllowedOrigin); context.OwinContext.Set(“as:clientRefreshTokenLifeTime”,client.RefreshTokenLifeTime.ToString()); 等待AuthConfig.OAuthOptions.RefreshTokenProvider.CreateAsync(上下文); properties.Dictionary.Add(“刷新令牌”,context.token); }
有关于这个话题的最新消息吗?我也有同样的问题。谢谢你的努力。实际上,我在一个项目中使用了这段代码,我停止了它的工作。然而,我会尝试这个代码,一旦我再次开始工作,我会让你知道我的反馈。再次感谢你的努力,我真的很感谢你的帮助。很高兴帮助)请随意提问。首先感谢你的解决方案。但出于某种原因,它对我不起作用。当我使用外部登录提供程序时,我确实获得了刷新令牌,但是当我尝试使用它来获取新的访问令牌时,我得到了一个400错误的请求错误,错误体:{“error”:“invalid_grant”}服务器上没有异常,我只是不知道如何调试它。:-|(尝试了您的解决方案和@Wouter Crooy提出的解决方案)您可以创建一个示例解决方案,或者给我一些提示吗?Alexander,您确定收到的刷新令牌有效吗?消息{“错误”:“无效”