Подтвердить что ты не робот

Как создать токен обновления с помощью внешнего провайдера?

Я искал через Интернет и не смог найти решение моей проблемы. Я использую 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;
        }
4b9b3361

Ответ 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);
}

Теперь токен refrehs будет сгенерирован.

2) Существует проблема использования базового контекста. СериализоватьТикет в SimpleRefreshTokenProvider CreateAsync. Сообщение из Бит технологии

Кажется, в методе ReceiveAsync контекст .DeserializeTicket не возвращая аутентификационный билет вообще во внешнем случае входа. Когда я смотрю на свойство context.Ticket, после этого вызываем его значение null. Сравнивая это с локальным логином, метод 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. Переопределите AuthorizationEndpointResponse в OAuthAuthorizationServerProvider. Что-то вроде этого:

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

Я надеюсь, что это поможет)

Ответ 2

@giraffe и другие offcourse

Несколько замечаний. Там нет необходимости использовать пользовательский tickerserializer.

Следующая строка:

Microsoft.Owin.Security.Infrastructure.AuthenticationTokenCreateContext context = 
                new Microsoft.Owin.Security.Infrastructure.AuthenticationTokenCreateContext(
                    Request.GetOwinContext(), 
                    Startup.OAuthOptions.AccessTokenFormat, ticket);

Как используется токенформат: Startup.OAuthOptions.AccessTokenFormat. Поскольку мы хотим предоставить refeshtoken, это необходимо изменить следующим образом: Startup.OAuthOptions.RefreshTokenFormat

В противном случае, если вы хотите получить новый accesstoken и обновить refreshtoken (grant_type = refresh_token & refresh_token =......), десериализатор /unprotector завершится с ошибкой. Так как он использует неверные целые ключевые слова на этапе дешифрования.

Ответ 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/

Чтобы заставить его работать с токенами обновления для внешних поставщиков, вам необходимо установить два параметра ( "как: 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);
    }