Вручную сгенерировать ссылочный токен IdentityServer4 и сохранить в таблице PersistedGrants

#c# #asp.net-identity #identityserver4 #asp.net-core-2.1

#c# #asp.net-идентификатор #identityserver4 #asp.net-core-2.1

Вопрос:

Я изучал IdentityServer4 в течение недели и успешно внедрил простой поток аутентификации с ResourceOwnedPassword помощью flow.

Теперь я внедряю аутентификацию Google с помощью IdentityServer4, следуя этому руководству

Это то, что я делаю:

Startup.cs

 public void ConfigureServices(IServiceCollection services)
{
    //...

    const string connectionString = @"Data Source=.SQLEXPRESS;database=IdentityServer4.Quickstart.EntityFramework-2.0.0;trusted_connection=yes;";
    var migrationsAssembly = typeof(Startup).GetTypeInfo().Assembly.GetName().Name;

    services.AddIdentityServer()
        .AddDeveloperSigningCredential()
        .AddProfileService<IdentityServerProfileService>()
        .AddResourceOwnerValidator<IdentityResourceOwnerPasswordValidator>()
        // this adds the config data from DB (clients, resources)
        .AddConfigurationStore(options =>
        {
            options.ConfigureDbContext = builder =>
            {
                builder.UseSqlServer(connectionString,
                    sql => sql.MigrationsAssembly(migrationsAssembly));
            };

        })
        // this adds the operational data from DB (codes, tokens, consents)
        .AddOperationalStore(options =>
        {
            options.ConfigureDbContext = builder =>
                builder.UseSqlServer(connectionString,
                    sql => sql.MigrationsAssembly(migrationsAssembly));
            // this enables automatic token cleanup. this is optional.
            options.EnableTokenCleanup = true;
            options.TokenCleanupInterval = 30;
        });

    // Add jwt validation.
    services.AddAuthentication(options =>
        {
            options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
            options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
        })
        .AddIdentityServerAuthentication(options =>
        {
            // base-address of your identityserver
            options.Authority = "https://localhost:44386";

            options.ClaimsIssuer = "https://localhost:44386";

            // name of the API resource
            options.ApiName = "api1";
            options.ApiSecret = "secret";

            options.RequireHttpsMetadata = false;

            options.SupportedTokens = SupportedTokens.Reference;

        });

    //...
}
  

** Контроллер Google (который предназначен для обработки возвращаемого токена из Google **

 public class GLoginController : Controller
    {
        #region Properties

        private readonly IPersistedGrantStore _persistedGrantStore;

        private readonly IUserFactory _userFactory;

        private readonly IBaseTimeService _baseTimeService;

        private readonly ITokenCreationService _tokenCreationService;

        private readonly IReferenceTokenStore _referenceTokenStore;

        private readonly IBaseEncryptionService _baseEncryptionService;

        #endregion

        #region Constructor

        public GLoginController(IPersistedGrantStore persistedGrantStore,
            IBaseTimeService basetimeService,
            ITokenCreationService tokenCreationService,
            IReferenceTokenStore referenceTokenStore,
            IBaseEncryptionService baseEncryptionService,
            IUserFactory userFactory)
        {
            _persistedGrantStore = persistedGrantStore;
            _baseTimeService = basetimeService;
            _userFactory = userFactory;
            _tokenCreationService = tokenCreationService;
            _referenceTokenStore = referenceTokenStore;
            _baseEncryptionService = baseEncryptionService;
        }

        #endregion

        #region Methods

        [HttpGet("login")]
        [AllowAnonymous]
        public IActionResult Login()
        {
            var authenticationProperties = new AuthenticationProperties
            {
                RedirectUri = "/api/google/handle-external-login"
            };

            return Challenge(authenticationProperties, "Google");
        }

        [HttpGet("handle-external-login")]
        //[Authorize("ExternalCookie")]
        [AllowAnonymous]
        public async Task<IActionResult> HandleExternalLogin()
        {
            //Here we can retrieve the claims
            var authenticationResult = await HttpContext.AuthenticateAsync(IdentityServerConstants.ExternalCookieAuthenticationScheme);
            var principal = authenticationResult.Principal;

            var emailAddress = principal.FindFirst(ClaimTypes.Email)?.Value;
            if (string.IsNullOrEmpty(emailAddress))
                return NotFound(new ApiMessageViewModel("Email is not found"));

            // Find user by using username.
            var loadUserConditions = new LoadUserModel();
            loadUserConditions.Usernames = new HashSet<string> { emailAddress };
            loadUserConditions.Pagination = new PaginationValueObject(1, 1);

            // Find users asynchronously.
            var loadUsersResult = await _userFactory.FindUsersAsync(loadUserConditions);
            var user = loadUsersResult.FirstOrDefault();

            // User is not defined.
            if (user == null)
            {
                user = new User(Guid.NewGuid(), emailAddress);
                user.Email = emailAddress;
                user.HashedPassword = _baseEncryptionService.Md5Hash("abcde12345-");
                user.JoinedTime = _baseTimeService.DateTimeUtcToUnix(DateTime.UtcNow);
                user.Kind = UserKinds.Google;
                user.Status = UserStatuses.Active;

                //await _userFactory.AddUserAsync(user);
            }
            else
            {
                // User is not google account.
                if (user.Kind != UserKinds.Google)
                    return Forbid("User is not allowed to access system.");
            }

            var token = new Token(IdentityServerConstants.TokenTypes.IdentityToken);
            var userCredential = new UserCredential(user);

            token.Claims = userCredential.GetClaims();
            token.AccessTokenType = AccessTokenType.Reference;
            token.ClientId = "ro.client";
            token.CreationTime = DateTime.UtcNow;
            token.Audiences = new[] {"api1"};
            token.Lifetime = 3600;


            return Ok();
        }

        #endregion
    }

  

Все в порядке, я могу получать запросы, возвращаемые из Google OAuth2, находить пользователей в базе данных, используя адрес электронной почты Google, и регистрировать их, если у них нет учетных записей.

Мой вопрос: как я могу использовать утверждения Google OAuth2, которые я получаю в HandleExternalLogin методе, для генерации ссылочного токена, сохранения его в таблице PersistedGrants и возврата клиенту.

Это означает, что при доступе пользователя https://localhost:44386/api/google/login , после перенаправления на экран согласия Google, они могут получить access_token , refresh_token который был сгенерирован IdentityServer4 .

Спасибо,

Ответ №1:

  • В IdentityServer тип (jwt ссылки) токена настраивается для каждого клиента (приложения), запросившего токен.
  • AccessTokenType.Reference допустимо для TokenTypes.AccessToken не TokenTypes.IdentityToken как в вашем фрагменте.

В общем, было бы проще следовать оригинальному quickstart, а затем расширить общий код в соответствии с вашими потребностями. То, что я вижу сейчас во фрагменте выше, — это только ваши конкретные данные, а не часть по умолчанию, отвечающая за создание сеанса IdSrv и перенаправление обратно клиенту.

Если вы все еще хотите создать токен вручную:

  • внедрите ITokenService в ваш контроллер.
  • исправьте ошибку, о которой я упоминал выше: TokenTypes.AccessToken вместо TokenTypes.IdentityToken
  • вызов var tokenHandle = await TokenService.CreateAccessTokenAsync(token);

tokenHandle является ли ключ в PersistedGrantStore

Комментарии:

1. Я не понимаю вашей идеи. Я пытался реализовать пользовательский интерфейс IdentityServer4 , но они используют in-memory хранилище пользователей. Очень неприятно снова и снова читать код и находить способ его настройки из-за отсутствия подробного руководства. Теперь я просто хочу знать, могу ли я сгенерировать reference token вручную и сохранить в persistedgrantstore или нет.

2. Ну, на самом деле вы спросили немного о другом. Вы спросили, как объединить внешнюю аутентификацию с интерактивным потоком с некоторой неинтерактивной логикой, и мой совет — избегать этого. Смотрите: вы не получаете никакой выгоды от концепции RO, поскольку все равно перенаправляете своего пользователя в Google. Таким образом, вам было бы проще установить для своего приложения тот же тип гранта, который вы уже определили для Google. Затем вы просто устанавливаете AccessTokenType.Reference в своем определении клиента, и все, дальнейшее кодирование не требуется.

3. И что касается in-memory пользовательского хранилища: вы можете свободно переключить его в DI на то, которое основано на Identity, или на ваше собственное. Без перезаписи общей логики.

4. Добавлена строка кода, которую вы, скорее всего, ищете. CreateAccessTokenAsync(token); ведет себя в зависимости от token.Type и token.AccessTokenType . В случае, если они являются OidcConstants.TokenTypes.AccessToken и AccessTokenType.Reference , он сохраняет токен.

5. Извините за мой поздний ответ. Позвольте мне попробовать