Browse Source

Implement Token Exchange Grant Type support

pull/23281/head
maliming 1 year ago
parent
commit
6ced2e8a34
No known key found for this signature in database GPG Key ID: A646B9CB645ECEA4
  1. 35
      modules/openiddict/app/OpenIddict.Demo.Client.Console/Program.cs
  2. 1
      modules/openiddict/app/OpenIddict.Demo.Server/EntityFrameworkCore/ServerDataSeedContributor.cs
  3. 3
      modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/AbpOpenIddictAspNetCoreModule.cs
  4. 2
      modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/Controllers/AuthorizeController.cs
  5. 92
      modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/Controllers/TokenController.TokenExchange.cs
  6. 5
      modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/Controllers/TokenController.cs

35
modules/openiddict/app/OpenIddict.Demo.Client.Console/Program.cs

@ -75,6 +75,41 @@ Console.WriteLine("UserInfo: {0}", JsonSerializer.Serialize(JsonDocument.Parse(u
})); }));
Console.WriteLine(); Console.WriteLine();
var tokenExchangeResponse = await client.RequestTokenExchangeTokenAsync(new TokenExchangeTokenRequest()
{
Address = configuration.TokenEndpoint,
ClientId = clientId,
ClientSecret = clientSecret,
SubjectToken = refreshTokenResponse.AccessToken!,
SubjectTokenType = "urn:ietf:params:oauth:token-type:access_token",
Scope = "AbpAPI profile roles email phone offline_access",
});
if (tokenExchangeResponse.IsError)
{
throw new Exception(tokenExchangeResponse.Error);
}
Console.WriteLine("Token Exchange token: {0}", tokenExchangeResponse.AccessToken);
Console.WriteLine();
Console.WriteLine("Token Exchange token: {0}", tokenExchangeResponse.RefreshToken);
Console.WriteLine();
userinfo = await client.GetUserInfoAsync(new UserInfoRequest()
{
Address = configuration.UserInfoEndpoint,
Token = tokenExchangeResponse.AccessToken
});
if (userinfo.IsError)
{
throw new Exception(userinfo.Error);
}
Console.WriteLine("Token Exchange UserInfo: {0}", JsonSerializer.Serialize(JsonDocument.Parse(userinfo.Raw), new JsonSerializerOptions
{
WriteIndented = true
}));
Console.WriteLine();
var introspectionResponse = await client.IntrospectTokenAsync(new TokenIntrospectionRequest() var introspectionResponse = await client.IntrospectTokenAsync(new TokenIntrospectionRequest()
{ {

1
modules/openiddict/app/OpenIddict.Demo.Server/EntityFrameworkCore/ServerDataSeedContributor.cs

@ -79,6 +79,7 @@ public class ServerDataSeedContributor : IDataSeedContributor, ITransientDepende
OpenIddictConstants.Permissions.GrantTypes.RefreshToken, OpenIddictConstants.Permissions.GrantTypes.RefreshToken,
OpenIddictConstants.Permissions.GrantTypes.DeviceCode, OpenIddictConstants.Permissions.GrantTypes.DeviceCode,
OpenIddictConstants.Permissions.GrantTypes.ClientCredentials, OpenIddictConstants.Permissions.GrantTypes.ClientCredentials,
OpenIddictConstants.Permissions.GrantTypes.TokenExchange,
OpenIddictConstants.Permissions.Prefixes.GrantType + MyTokenExtensionGrant.ExtensionGrantName, OpenIddictConstants.Permissions.Prefixes.GrantType + MyTokenExtensionGrant.ExtensionGrantName,
OpenIddictConstants.Permissions.ResponseTypes.Code, OpenIddictConstants.Permissions.ResponseTypes.Code,

3
modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/AbpOpenIddictAspNetCoreModule.cs

@ -79,7 +79,8 @@ public class AbpOpenIddictAspNetCoreModule : AbpModule
.AllowClientCredentialsFlow() .AllowClientCredentialsFlow()
.AllowRefreshTokenFlow() .AllowRefreshTokenFlow()
.AllowDeviceAuthorizationFlow() .AllowDeviceAuthorizationFlow()
.AllowNoneFlow(); .AllowNoneFlow()
.AllowTokenExchangeFlow();
builder.RegisterScopes(new[] builder.RegisterScopes(new[]
{ {

2
modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/Controllers/AuthorizeController.cs

@ -44,7 +44,7 @@ public class AuthorizeController : AbpOpenIdDictControllerBase
var result = await HttpContext.AuthenticateAsync(IdentityConstants.ApplicationScheme); var result = await HttpContext.AuthenticateAsync(IdentityConstants.ApplicationScheme);
if (result is not { Succeeded: true } || if (result is not { Succeeded: true } ||
((request.HasPromptValue(OpenIddictConstants.PromptValues.Login) || request.MaxAge is 0 || ((request.HasPromptValue(OpenIddictConstants.PromptValues.Login) || request.MaxAge is 0 ||
(request.MaxAge != null && result.Properties?.IssuedUtc != null && (request.MaxAge is not null && result.Properties?.IssuedUtc is not null &&
TimeProvider.System.GetUtcNow() - result.Properties.IssuedUtc > TimeSpan.FromSeconds(request.MaxAge.Value))) && TimeProvider.System.GetUtcNow() - result.Properties.IssuedUtc > TimeSpan.FromSeconds(request.MaxAge.Value))) &&
TempData["IgnoreAuthenticationChallenge"] is null or false)) TempData["IgnoreAuthenticationChallenge"] is null or false))
{ {

92
modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/Controllers/TokenController.TokenExchange.cs

@ -0,0 +1,92 @@
using System;
using System.Collections.Generic;
using System.Security.Claims;
using System.Text.Json.Nodes;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Mvc;
using Microsoft.IdentityModel.Tokens;
using OpenIddict.Abstractions;
using OpenIddict.Server.AspNetCore;
namespace Volo.Abp.OpenIddict.Controllers;
public partial class TokenController
{
protected virtual async Task<IActionResult> HandleTokenExchangeGrantTypeAsync(OpenIddictRequest request)
{
// Retrieve the claims principal stored in the subject token.
//
// Note: the principal may not represent a user (e.g if the token was issued during a client credentials token
// request and represents a client application): developers are strongly encouraged to ensure that the user
// and client identifiers are randomly generated so that a malicious client cannot impersonate a legit user.
//
// See https://datatracker.ietf.org/doc/html/rfc9068#SecurityConsiderations for more information.
var result = await HttpContext.AuthenticateAsync(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
// If available, retrieve the claims principal stored in the actor token.
var actor = result.Properties?.GetParameter<ClaimsPrincipal>(OpenIddictServerAspNetCoreConstants.Properties.ActorTokenPrincipal);
// Retrieve the user profile corresponding to the subject token.
var user = await UserManager.FindByIdAsync(result.Principal!.GetClaim(OpenIddictConstants.Claims.Subject)!);
if (user is null)
{
return Forbid(
authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme,
properties: new AuthenticationProperties(new Dictionary<string, string>
{
[OpenIddictServerAspNetCoreConstants.Properties.Error] = OpenIddictConstants.Errors.InvalidGrant,
[OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = "The token is no longer valid."
}));
}
// Ensure the user is still allowed to sign in.
if (!await SignInManager.CanSignInAsync(user))
{
return Forbid(
authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme,
properties: new AuthenticationProperties(new Dictionary<string, string?>
{
[OpenIddictServerAspNetCoreConstants.Properties.Error] = OpenIddictConstants.Errors.InvalidGrant,
[OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = "The user is no longer allowed to sign in."
}));
}
// Note: whether the identity represents a delegated or impersonated access (or any other
// model) is entirely up to the implementer: to support all scenarios, OpenIddict doesn't
// enforce any specific constraint on the identity used for the sign-in operation and only
// requires that the standard "act" and "may_act" claims be valid JSON objects if present.
// Clear the dynamic claims cache.
await IdentityDynamicClaimsPrincipalContributorCache.ClearAsync(user.Id, user.TenantId);
// Create a new ClaimsPrincipal containing the claims that
// will be used to create an id_token, a token or a code.
var principal = await SignInManager.CreateUserPrincipalAsync(user);
// Note: IdentityModel doesn't support serializing ClaimsIdentity.Actor to the
// standard "act" claim yet, which requires adding the "act" claim manually.
//
// For more information, see
// https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/pull/3219.
if (!string.IsNullOrEmpty(actor?.GetClaim(OpenIddictConstants.Claims.Subject)) &&
!string.Equals(principal.GetClaim(OpenIddictConstants.Claims.Subject), actor.GetClaim(OpenIddictConstants.Claims.Subject), StringComparison.Ordinal))
{
principal.SetClaim(OpenIddictConstants.Claims.Actor, new JsonObject
{
[OpenIddictConstants.Claims.Subject] = actor.GetClaim(OpenIddictConstants.Claims.Subject)
});
}
// 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 GetResourcesAsync(request.GetScopes()));
await OpenIddictClaimsPrincipalManager.HandleAsync(request, principal);
// Returning a SignInResult will ask OpenIddict to issue the appropriate access/identity tokens.
return SignIn(principal, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
}
}

5
modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/Controllers/TokenController.cs

@ -42,6 +42,11 @@ public partial class TokenController : AbpOpenIdDictControllerBase
return await HandleClientCredentialsAsync(request); return await HandleClientCredentialsAsync(request);
} }
if (request.IsTokenExchangeGrantType())
{
return await HandleTokenExchangeGrantTypeAsync(request);
}
var extensionGrantsOptions = HttpContext.RequestServices.GetRequiredService<IOptions<AbpOpenIddictExtensionGrantsOptions>>(); var extensionGrantsOptions = HttpContext.RequestServices.GetRequiredService<IOptions<AbpOpenIddictExtensionGrantsOptions>>();
var extensionTokenGrant = extensionGrantsOptions.Value.Find<ITokenExtensionGrant>(request.GrantType); var extensionTokenGrant = extensionGrantsOptions.Value.Find<ITokenExtensionGrant>(request.GrantType);
if (extensionTokenGrant != null) if (extensionTokenGrant != null)

Loading…
Cancel
Save