目录

OpenIddict授权存储

为了跟踪令牌的逻辑链和用户同意,OpenIddict 支持在数据库中存储授权(在某些 OpenID Connect 实现中也称为“授权”)。

授权类型

授权可以有两种类型:永久授权和临时授权。

永久授权

永久授权是使用 API 创建的开发人员定义的授权,并使用特定于 OpenIddict 的扩展方法IOpenIddictAuthorizationManager.CreateAsync()明确附加到 a 。ClaimsPrincipalprincipal.SetAuthorizationId()

此类授权通常用于记住用户同意并避免为每个授权请求显示同意视图。为此,可以为每个应用程序定义“同意类型”,如以下示例所示:

// Retrieve the application details from the database.
var application = await _applicationManager.FindByClientIdAsync(request.ClientId) ??
    throw new InvalidOperationException("The application cannot be found.");

// Retrieve the permanent authorizations associated with the user and the application.
var authorizations = await _authorizationManager.FindAsync(
    subject: await _userManager.GetUserIdAsync(user),
    client : await _applicationManager.GetIdAsync(application),
    status : Statuses.Valid,
    type   : AuthorizationTypes.Permanent,
    scopes : request.GetScopes()).ToListAsync();

switch (await _applicationManager.GetConsentTypeAsync(application))
{
    // If the consent is external (e.g when authorizations are granted by a sysadmin),
    // immediately return an error if no authorization can be found in the database.
    case ConsentTypes.External when !authorizations.Any():
        return Forbid(
            authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme,
            properties: new AuthenticationProperties(new Dictionary<string, string>
            {
                [OpenIddictServerAspNetCoreConstants.Properties.Error] = Errors.ConsentRequired,
                [OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] =
                    "The logged in user is not allowed to access this client application."
            }));

    // If the consent is implicit or if an authorization was found,
    // return an authorization response without displaying the consent form.
    case ConsentTypes.Implicit:
    case ConsentTypes.External when authorizations.Any():
    case ConsentTypes.Explicit when authorizations.Any() && !request.HasPrompt(Prompts.Consent):
        var principal = await _signInManager.CreateUserPrincipalAsync(user);

        // Note: in this sample, the granted scopes match the requested scope
        // but you may want to allow the user to uncheck specific scopes.
        // For that, simply restrict the list of scopes before calling SetScopes.
        principal.SetScopes(request.GetScopes());
        principal.SetResources(await _scopeManager.ListResourcesAsync(principal.GetScopes()).ToListAsync());

        // Automatically create a permanent authorization to avoid requiring explicit consent
        // for future authorization or token requests containing the same scopes.
        var authorization = authorizations.LastOrDefault();
        if (authorization is null)
        {
            authorization = await _authorizationManager.CreateAsync(
                principal: principal,
                subject  : await _userManager.GetUserIdAsync(user),
                client   : await _applicationManager.GetIdAsync(application),
                type     : AuthorizationTypes.Permanent,
                scopes   : principal.GetScopes());
        }

        principal.SetAuthorizationId(await _authorizationManager.GetIdAsync(authorization));

        foreach (var claim in principal.Claims)
        {
            claim.SetDestinations(GetDestinations(claim, principal));
        }

        return SignIn(principal, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);

    // At this point, no authorization was found in the database and an error must be returned
    // if the client application specified prompt=none in the authorization request.
    case ConsentTypes.Explicit   when request.HasPrompt(Prompts.None):
    case ConsentTypes.Systematic when request.HasPrompt(Prompts.None):
        return Forbid(
            authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme,
            properties: new AuthenticationProperties(new Dictionary<string, string>
            {
                [OpenIddictServerAspNetCoreConstants.Properties.Error] = Errors.ConsentRequired,
                [OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] =
                    "Interactive user consent is required."
            }));

    // In every other case, render the consent form.
    default: return View(new AuthorizeViewModel
    {
        ApplicationName = await _applicationManager.GetLocalizedDisplayNameAsync(application),
        Scope = request.Scope
    });
}

临时授权

当出于安全原因需要跟踪令牌链时,OpenIddict 会自动创建临时授权,但开发人员没有将明确的永久授权附加到ClaimsPrincipal用于登录操作的令牌上。

Such authorizations are typically created in the authorization code flow to link all the tokens associated with the original authorization code, so that they can be automatically revoked if the authorization code was redeemed multiple times (which may indicate a token leakage). In the same vein, ad-hoc authorizations are also created when a refresh token is returned during a resource owner password credentials grant request.

笔记

When using the OpenIddict.Quartz integration, ad-hoc authorizations are automatically removed from the database after a short period of time (14 days by default). Unlike ad-hoc authorizations, permanent authorizations are never removed from the database.

Enabling authorization entry validation at the API level

For performance reasons, OpenIddict 3.0 doesn't check, by default, the status of an authorization entry when receiving an API request: access tokens are considered valid even if the attached authorization was revoked. For scenarios that require immediate authorization revocation, the OpenIddict validation handler can be configured to enforce authorization entry validation for each API request:

笔记

Enabling authorization entry validation requires that the OpenIddict validation handler have a direct access to the server database where authorizations are stored, which makes it better suited for APIs located in the same application as the authorization server. For external applications, consider using introspection instead of local validation.

在这两种情况下,额外的延迟——由额外的数据库请求和用于自省的 HTTP 调用——是预期的。

services.AddOpenIddict()
    .AddValidation(options =>
    {
        options.EnableAuthorizationEntryValidation();
    });

禁用授权存储

虽然强烈反对,但可以在服务器选项中禁用授权存储:

services.AddOpenIddict()
    .AddServer(options =>
    {
        options.DisableAuthorizationStorage();
    });