Identity Server 4 - 在数据库中保存刷新令牌

Beh*_*ooz 1 c# identityserver4 refresh-token asp.net-core-2.0

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

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

m3n*_*ak3 5

根据评论,我认为这对您和其他有您案例的人来说都是一个有用的解决方案。

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

首先 - 在 Startup.cs 中:

services.AddTransient<IPersistedGrantStore, PersistedGrantStore>();
Run Code Online (Sandbox Code Playgroud)

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

班级本身:

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

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

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

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

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

和映射器配置文件:

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

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

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

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