【问题标题】:Identity Server 4 - saving refresh token in datatabaseIdentity Server 4 - 在数据库中保存刷新令牌
【发布时间】:2018-11-28 18:47:02
【问题描述】:

我在 .Net Core 2.0 中使用 IdentityServer4,并且成功生成访问令牌和刷新令牌。我只需要能够在生成刷新令牌时在服务器端“看到”它,这样我就可以将它保存在数据库中以用于某些特定目的。

如何在服务器上生成刷新令牌时访问它的值?

【问题讨论】:

  • @RuardvanElburg 您能否详细说明在生成特定 refresh_token 时如何读取它?
  • 我的意思是,当它在服务器上生成时,您不必为了保存它而读取 refresh_token。 ID 可以为您做到这一点。如果您需要查找刷新令牌,您可以查询 PersistedGrants(使用商店)并查询 SubjectId。
  • 不知道,但你可能想看看事件:docs.identityserver.io/en/release/topics/events.html
  • 老实说 - 没有找到在 IdentityServer 端读取它的方法。您始终可以在客户端中阅读它,但我认为这对您不起作用。顺便说一句 - 请记住,如果您不覆盖默认的 PersistedGrant 存储,您将在日志中收到此类消息You are using the in-memory version of the persisted grant store. This will store consent decisions, authorization codes, refresh and reference tokens in memory only. If you are using any of those features in production, you want to switch to a different store implementation.
  • 此外 - 您无需根据他们的表格构建表格。在我的情况下,我有同样的情况,我刚刚创建了一个表,它适合持久授权请求,覆盖 PersistedGrants 存储,使用我的服务并在我的表中读/写的存储。我的表和他们的表都不一样,而且我没有使用他们的 EF 包。

标签: c# asp.net-core-2.0 identityserver4 refresh-token


【解决方案1】:

根据 cmets 的说法,我认为这对您和其他与您的情况一样的人来说都是一个有用的解决方案。

我从 IdentityServer 本身开始。由于强烈建议在生产环境中使用您自己的 PersistedGrant 存储,因此我们需要覆盖默认存储。

首先 - 在 Startup.cs 中:

services.AddTransient<IPersistedGrantStore, PersistedGrantStore>();

这将使用我们自己的PersistedGrantStore 类实现他们的IPersistedGrantStore 接口。

类本身:

public class PersistedGrantStore : IPersistedGrantStore
{
    private readonly ILogger logger;
    private readonly IPersistedGrantService persistedGrantService;

    public PersistedGrantStore(IPersistedGrantService persistedGrantService, ILogger<PersistedGrantStore> logger)
    {
        this.logger = logger;
        this.persistedGrantService = persistedGrantService;
    }

    public Task StoreAsync(PersistedGrant token)
    {
        var existing = this.persistedGrantService.Get(token.Key);
        try
        {
            if (existing == null)
            {
                logger.LogDebug("{persistedGrantKey} not found in database", token.Key);

                var persistedGrant = token.ToEntity();
                this.persistedGrantService.Add(persistedGrant);
            }
            else
            {
                logger.LogDebug("{persistedGrantKey} found in database", token.Key);

                token.UpdateEntity(existing);
                this.persistedGrantService.Update(existing);
            }
        }
        catch (DbUpdateConcurrencyException ex)
        {
            logger.LogWarning("exception updating {persistedGrantKey} persisted grant in database: {error}", token.Key, ex.Message);
        }

        return Task.FromResult(0);
    }

    public Task<PersistedGrant> GetAsync(string key)
    {
        var persistedGrant = this.persistedGrantService.Get(key);
        var model = persistedGrant?.ToModel();

        logger.LogDebug("{persistedGrantKey} found in database: {persistedGrantKeyFound}", key, model != null);

        return Task.FromResult(model);
    }

    public Task<IEnumerable<PersistedGrant>> GetAllAsync(string subjectId)
    {
        var persistedGrants = this.persistedGrantService.GetAll(subjectId).ToList();

        var model = persistedGrants.Select(x => x.ToModel());

        logger.LogDebug("{persistedGrantCount} persisted grants found for {subjectId}", persistedGrants.Count, subjectId);

        return Task.FromResult(model);
    }

    public Task RemoveAsync(string key)
    {
        var persistedGrant = this.persistedGrantService.Get(key);

        if (persistedGrant != null)
        {
            logger.LogDebug("removing {persistedGrantKey} persisted grant from database", key);

            try
            {
                this.persistedGrantService.Remove(persistedGrant);
            }
            catch (DbUpdateConcurrencyException ex)
            {
                logger.LogInformation("exception removing {persistedGrantKey} persisted grant from database: {error}", key, ex.Message);
            }
        }
        else
        {
            logger.LogDebug("no {persistedGrantKey} persisted grant found in database", key);
        }

        return Task.FromResult(0);
    }

    public Task RemoveAllAsync(string subjectId, string clientId)
    {
        var persistedGrants = this.persistedGrantService.GetAll(subjectId, clientId);

        logger.LogDebug("removing {persistedGrantCount} persisted grants from database for subject {subjectId}, clientId {clientId}", persistedGrants.Count(), subjectId, clientId);

        try
        {
            this.persistedGrantService.RemoveAll(persistedGrants);
        }
        catch (DbUpdateConcurrencyException ex)
        {
            logger.LogInformation("removing {persistedGrantCount} persisted grants from database for subject {subjectId}, clientId {clientId}: {error}", persistedGrants.Count(), subjectId, clientId, ex.Message);
        }

        return Task.FromResult(0);
    }

    public Task RemoveAllAsync(string subjectId, string clientId, string type)
    {
        var persistedGrants = this.persistedGrantService.GetAll(subjectId, clientId, type);

        logger.LogDebug("removing {persistedGrantCount} persisted grants from database for subject {subjectId}, clientId {clientId}, grantType {persistedGrantType}", persistedGrants.Count(), subjectId, clientId, type);

        try
        {
            this.persistedGrantService.RemoveAll(persistedGrants);
        }
        catch (DbUpdateConcurrencyException ex)
        {
            logger.LogInformation("exception removing {persistedGrantCount} persisted grants from database for subject {subjectId}, clientId {clientId}, grantType {persistedGrantType}: {error}", persistedGrants.Count(), subjectId, clientId, type, ex.Message);
        }

        return Task.FromResult(0);
    }
}

正如您在其中看到的,我有一个界面和记录器。

IPersistedGrantService 接口:

public interface IPersistedGrantService
{
    void Add(PersistedGrantInfo persistedGrant);

    void Update(PersistedGrantInfo existing);

    PersistedGrantInfo Get(string key);

    IEnumerable<PersistedGrantInfo> GetAll(string subjectId);

    IEnumerable<PersistedGrantInfo> GetAll(string subjectId, string clientId);

    IEnumerable<PersistedGrantInfo> GetAll(string subjectId, string clientId, string type);

    void Remove(PersistedGrantInfo persistedGrant);

    void RemoveAll(IEnumerable<PersistedGrantInfo> persistedGrants);
}

如您所见,有一个名为PersistedGrantInfo 的对象。这是我的 DTO,用于在 db 实体和 IDS4 实体之间进行映射(您不必强制使用它,但我这样做是为了更好的抽象)。

这个 Info 对象使用 AutoMapper 映射到 IDS4 实体:

public static class PersistedGrantMappers
{
    internal static IMapper Mapper { get; }

    static PersistedGrantMappers()
    {
        Mapper = new MapperConfiguration(cfg => cfg.AddProfile<PersistedGrantMapperProfile>())
            .CreateMapper();
    }

    /// <summary>
    /// Maps an entity to a model.
    /// </summary>
    /// <param name="entity">The entity.</param>
    /// <returns></returns>
    public static PersistedGrant ToModel(this PersistedGrantInfo entity)
    {
        return entity == null ? null : Mapper.Map<PersistedGrant>(entity);
    }

    /// <summary>
    /// Maps a model to an entity.
    /// </summary>
    /// <param name="model">The model.</param>
    /// <returns></returns>
    public static PersistedGrantInfo ToEntity(this PersistedGrant model)
    {
        return model == null ? null : Mapper.Map<PersistedGrantInfo>(model);
    }

    /// <summary>
    /// Updates an entity from a model.
    /// </summary>
    /// <param name="model">The model.</param>
    /// <param name="entity">The entity.</param>
    public static void UpdateEntity(this PersistedGrant model, PersistedGrantInfo entity)
    {
        Mapper.Map(model, entity);
    }
}

以及映射器配置文件:

public class PersistedGrantMapperProfile:Profile
{
    /// <summary>
    /// <see cref="PersistedGrantMapperProfile">
    /// </see>
    /// </summary>
    public PersistedGrantMapperProfile()
    {
        CreateMap<PersistedGrantInfo, IdentityServer4.Models.PersistedGrant>(MemberList.Destination)
            .ReverseMap();
    }
}

回到IPersistedGrantService - 实施取决于您。目前作为数据库实体,我拥有 IDS4 实体的精确副本:

 public class PersistedGrant
{
    [Key]
    public string Key { get; set; }

    public string Type { get; set; }

    public string SubjectId { get; set; }

    public string ClientId { get; set; }

    public DateTime CreationTime { get; set; }

    public DateTime? Expiration { get; set; }

    public string Data { get; set; }
}

但是根据您的需要,您可以做一些不同的事情(将此数据存储在不同的表中,使用不同的列名等)。然后在我的服务实现中,我只是使用来自“IPersistedGrantStore”实现的数据,并且我在我的数据库上下文中对实体进行 CRUD。

作为结论 - 这里的主要内容是覆盖\实现他们的IPersistedGrantStore 接口根据您的需要。希望这会有所帮助。

【讨论】:

    猜你喜欢
    • 2020-01-08
    • 2020-11-12
    • 2019-07-30
    • 2020-08-28
    • 2021-07-28
    • 2017-10-30
    • 1970-01-01
    • 1970-01-01
    • 2017-06-29
    相关资源
    最近更新 更多