创建自己的IdentityServer4存储库
多年来,我对默认的IdentityServer4存储库有很多见解。但是,无论您对实体框架,聚簇索引和varchar长度有何看法,如果您担心默认值,那么我的建议始终是相同的:如果您拥有内部数据库专业知识,请使用它并创建自己的存储层。
创建自己的IdentityServer4持久性存储非常简单。只有少数几个接口可以实现,每个接口只有几个读取和写入方法。它们不是完整的存储库层,也不指示数据库类型或结构。
因此,让我们看一下实现自己的IdentityServer4存储库所涉及的内容。
IdentityServer4实体框架库
IdentityServer4实体框架库旨在跨多个不同的数据库提供程序工作。它依赖于Entity Framework关系库,该库可能会限制它可以支持的数据库提供程序,并已针对SQL Server,MySQL,SQLite和PostgreSQL进行了测试。
结果,它没有针对任何一个数据库提供程序进行优化,因此可能会遭受损失。尽管如此,Rock Solid Knowledge的客户仍在生产中使用此库,其中一位客户拥有超过2000万用户。因此,除非您像疯子一样锤击自省端点,否则尽管您有DBA的坚持,但该库很可能会为您提供良好的服务。
IdentityServer4存储接口
从IdentityServer4 v2.3开始,现在可以在IdentityServer4.Storage库中找到IdentityServer4的存储接口和实体。否则,可以在IdentityServer4核心库中找到它们。
让我们看一下IdentityServer4存储接口,该接口处理客户端,资源,范围和临时数据。
客户商店(IClientStore)
可能最难应付的商店是IClientStore。这是由于客户端实体及其大量集合的规模很大。但是,一旦您确定了一个模式,客户端存储本身就非常简单,只有一种方法可以实现:FindClientByIdAsync。
public interface IClientStore {
Task<Client> FindClientByIdAsync(string clientId);
}
该FindClientByIdAsync方法应返回完整的客户端实体和所有关联的集合。
客户还具有允许范围的列表。是否要独立于或链接到我们不久将看到的身份和API范围取决于您,这取决于您。
CORS(ICorsPolicyService)
当实现自己的时IClientStore,您还需要实现自己的ICorsPolicyService。该界面必须能够使用您选择的客户端存储并加载所有客户端存储,AllowedCorsOrigins以方便进行CORS来源检查。
public interface ICorsPolicyService {
Task<bool> IsOriginAllowedAsync(string origin);
}
资源库(IResourceStore)
为了存储身份资源和API资源,我们有资源存储。此接口具有比其他任何商店更多的方法:
public interface IResourceStore {
Task<IEnumerable<IdentityResource>> FindIdentityResourcesByScopeAsync(IEnumerable<string> scopeNames);
Task<IEnumerable<ApiResource>> FindApiResourcesByScopeAsync(IEnumerable<string> scopeNames);
Task<ApiResource> FindApiResourceAsync(string name);
Task<Resources> GetAllResourcesAsync();
}
此接口处理从授权和令牌请求接收到的范围到IdentityServer中各自资源模型的转换。因此,请不要忘记,这意味着标识资源的资源名称,但是各个API的作用域就属于API资源。毕竟,API资源会为API本身建模,而API本身又可以具有许多范围,每个范围代表对该API的可委派权限。
持续补助金
随着持续资助我们有两个选择:落实IPersistedGrantStore和处理的授权码,刷新令牌,令牌参考存储,并同意一次性,或单独使用实施每一项IAuthorizationCodeStore,IRefreshTokenStore,IReferenceTokenStore,和IUserConsentStore。
IPersistedGrantStore
默认的实现IAuthorizationCodeStore,IRefreshTokenStore,IReferenceTokenStore,和IUserConsentStore所有利用IPersistedGrantStore。这个大小适合所有商店接受的序列化数据,以后可以通过密钥检索。该密钥既可以是客户端应用程序已知的密钥(例如授权码),也可以是可以始终评估传入客户端应用程序的密钥(例如同意)的密钥。
IdentityServer可以为持久授权授予到期,并且由您来清理到期的授权,以免数据库开始因压力而吟。
由于密钥可能是敏感的内容,例如刷新令牌值,因此应以散列格式存储密钥。
public class PersistedGrant {
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; }
}
public interface IPersistedGrantStore {
Task StoreAsync(PersistedGrant grant);
Task<PersistedGrant> GetAsync(string key);
Task<IEnumerable<PersistedGrant>> GetAllAsync(string subjectId);
Task RemoveAsync(string key);
Task RemoveAllAsync(string subjectId, string clientId);
Task RemoveAllAsync(string subjectId, string clientId, string type);
}
序列化
默认情况下,使用IPersistentGrantSerializer接口将持久授权授予序列化为JSON 。如果这不符合您的喜好,那么可以再次将其覆盖,然后由默认的IdentityServer存储自动使用。
个人商店
否则,如果您发现自己正在处理数百万个参考令牌,而您当前的存储正成为瓶颈,则可以一次实现一个这些存储,以不同的方式存储每个存储,也许有些使用IPersistedGrantStore,而有些则没有。
public interface IAuthorizationCodeStore {
Task<string> StoreAuthorizationCodeAsync(AuthorizationCode code);
Task<AuthorizationCode> GetAuthorizationCodeAsync(string code);
Task RemoveAuthorizationCodeAsync(string code);
}
public interface IRefreshTokenStore {
Task<string> StoreRefreshTokenAsync(RefreshToken refreshToken);
Task UpdateRefreshTokenAsync(string handle, RefreshToken refreshToken);
Task<RefreshToken> GetRefreshTokenAsync(string refreshTokenHandle);
Task RemoveRefreshTokenAsync(string refreshTokenHandle);
Task RemoveRefreshTokensAsync(string subjectId, string clientId);
}
public interface IReferenceTokenStore {
Task<string> StoreReferenceTokenAsync(Token token);
Task<Token> GetReferenceTokenAsync(string handle);
Task RemoveReferenceTokenAsync(string handle);
Task RemoveReferenceTokensAsync(string subjectId, string clientId);
}
public interface IUserConsentStore {
Task StoreUserConsentAsync(Consent consent);
Task<Consent> GetUserConsentAsync(string subjectId, string clientId);
Task RemoveUserConsentAsync(string subjectId, string clientId);
}
如果您走使用单个商店的路线,那么您还需要创建一个IPersistedGrantService了解它们的实现,因为默认实现仅使用IPersistedGrantStore。但是,此服务仅由DefaultIdentityServerInteractionService的方法使用GetAllUserConsentsAsync,即使该方法也仅在QuickStart UI的授权页面中使用。
设备流(IDeviceFlowStore)
设备流请求的存储再次相对简单,但是与其他临时数据存储不同,它必须可以通过两个不同的项进行搜索:设备代码和用户代码。
public interface IDeviceFlowStore {
Task StoreDeviceAuthorizationAsync(string deviceCode, string userCode, DeviceCode data);
Task<DeviceCode> FindByUserCodeAsync(string userCode);
Task<DeviceCode> FindByDeviceCodeAsync(string deviceCode);
Task UpdateByUserCodeAsync(string userCode, DeviceCode data);
Task RemoveByDeviceCodeAsync(string deviceCode);
}
该存储可以再次利用IPersistentGrantSerializer来简化存储。
注册您的自定义实现
要注册我们的商店,IIdentityServerBuilder我们可以使用一些扩展功能。否则,我们必须自己注册。默认情况下,这些存储库已在瞬态生存期中注册。
services.AddIdentityServer()
// existing registrations
.AddClientStore<MyCustomClientStore>()
.AddCorsPolicyService<MyCustomCorsPolicyService>()
.AddResourceStore<MyCustomResourcesStore>()
.AddPersistedGrantStore<MyCustomPersistedGrantStore>()
.AddDeviceFlowStore<MyCustomDeviceFlowStore>();
// For manual temp data stores
services.AddTransient<IAuthorizationCodeStore, MyCustomAuthorizationCodeStore>();
services.AddTransient<IRefreshTokenStore, MyCustomRefreshTokenStore>();
services.AddTransient<IReferenceTokenStore, MyCustomReferenceTokenStore>();
services.AddTransient<IUserConsentStore, MyCustomUserConsentStore>();
奖励:密钥和消息存储
较少见,但还是在范围上的剩余门店ISigningCredentialStore,IValidationKeysStore,IMessageStore,和IConsentMessageStore。
ISigningCredentialStore,并IValidationKeys分别处理用于签名令牌的私钥和用于验证令牌的公钥的加载。默认情况下,密钥是从x509证书或证书存储中加载的,然后存储在内存中。
IMessageStore,并IConsentMessage存储处理IdentityServer协议端点和UI之间的数据持久性。这些的使用由IdentityServer交互服务处理,从而允许通过ID加载错误,并将同意响应信息返回给IdentityServer。就个人而言,我从未在野外看到这两个的自定义实现。

我帮助开发人员学习OAuth和网络安全。
我是Rock Solid Knowledge的身份和访问控制负责人;专注于身份验证,FIDO2,OAuth和OpenID Connect的软件开发人员。
浙公网安备 33010602011771号