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

use*_*456 20 asp.net oauth

我在网上搜索过,无法找到解决问题的方法.我正在我的应用中实施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();
        }
    }
Run Code Online (Sandbox Code Playgroud)

现在我允许我的用户通过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);
      }


    }
Run Code Online (Sandbox Code Playgroud)

//生成访问令牌的私有方法

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;
        }
Run Code Online (Sandbox Code Playgroud)

小智 26

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

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);
}
Run Code Online (Sandbox Code Playgroud)

现在,实际上,有必要添加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);
}
Run Code Online (Sandbox Code Playgroud)

现在将生成refrehs标记.

2)在SimpleRefreshTokenProvider CreateAsync方法中使用基本context.SerializeTicket存在问题.来自技术的消息

在ReceiveAsync方法中,context.DeserializeTicket在外部登录案例中根本没有返回身份验证票证.当我查看context.Ticket属性之后调用它为null.将其与本地登录流进行比较,DeserializeTicket方法将context.Ticket属性设置为AuthenticationTicket.所以现在的谜团是DeserializeTicket在两个流程中的表现如何不同.数据库中受保护的票证字符串是在相同的CreateAsync方法中创建的,区别仅在于我在GenerateLocalAccessTokenResponse中手动调用该方法,而Owin middlware正常调用它...而且SerializeTicket或DeserializeTicket都没有抛出错误...

因此,您需要使用Microsoft.Owin.Security.DataHandler.Serializer.TicketSerializer对票证进行searizize和反序列化.它看起来像这样:

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));
Run Code Online (Sandbox Code Playgroud)

代替:

token.ProtectedTicket = context.SerializeTicket();
Run Code Online (Sandbox Code Playgroud)

对于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)));
Run Code Online (Sandbox Code Playgroud)

代替:

context.DeserializeTicket(refreshToken.ProtectedTicket);
Run Code Online (Sandbox Code Playgroud)

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);
}
Run Code Online (Sandbox Code Playgroud)

所以..这就是全部!现在,在调用ExternalLogin方法之后,你得到url: https:// localhost:44301/Account/ExternalLoginCallback?access_token = ACCESS_TOKEN&token_type = bearer&expires_in = 300&state = STATE&refresh_token = TICKET&returnUrl = URL

我希望这有帮助)


Wou*_*ooy 6

@giraffe和其他人在外面

一些评论.没有必要使用自定义 tickerserializer.

以下行:

Microsoft.Owin.Security.Infrastructure.AuthenticationTokenCreateContext context = 
                new Microsoft.Owin.Security.Infrastructure.AuthenticationTokenCreateContext(
                    Request.GetOwinContext(), 
                    Startup.OAuthOptions.AccessTokenFormat, ticket);
Run Code Online (Sandbox Code Playgroud)

Startup.OAuthOptions.AccessTokenFormat使用tokenformat:.由于我们要提供refeshtoken,因此需要将其更改为: Startup.OAuthOptions.RefreshTokenFormat

否则,如果您想获取新的accesstoken并刷新refreshtoken(grant_type = refresh_token&refresh_token = ......),则反序列化器/ unprotector将失败.因为它在解密阶段使用了错误的目的关键字.


Ale*_*rek 6

终于找到了我的问题的解决方案.首先,如果您遇到OWIN的任何问题并且无法弄清楚出了什么问题,我建议您只需启用符号调试并对其进行调试.可以在这里找到一个很好的解释:http: //www.symbolsource.org/Public/Home/VisualStudio

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

如果您正在实施刷新令牌,请查看这篇gread博客文章: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, ticket);

 await Startup.OAuthOptions.RefreshTokenProvider.CreateAsync(context);
 properties.Dictionary.Add("refresh_token", 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);
    }