Skip to content

Commit

Permalink
feat:add stack caller (#502)
Browse files Browse the repository at this point in the history
* feat:add stack caller

* feat:delete stackcaller buildingblock

* feat:delete stackcaller test

* feat:pr

* refactor:tokenprovider

* feat:Func
  • Loading branch information
MayueCif authored Mar 15, 2023
1 parent 1fa6eb9 commit 6a7494d
Show file tree
Hide file tree
Showing 20 changed files with 324 additions and 66 deletions.
1 change: 1 addition & 0 deletions Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
<StackExchangeRedisPackageVersion>2.2.4</StackExchangeRedisPackageVersion>
<NESTPackageVersion>7.17.4</NESTPackageVersion>
<IdentityPackageVersion>6.15.0</IdentityPackageVersion>
<IdentityModelPackageVersion>6.0.0</IdentityModelPackageVersion>

<OpenTelemetryVersion>1.4.0-rc.1</OpenTelemetryVersion>
<OpenTelemetryContribVersion>1.0.0-beta2</OpenTelemetryContribVersion>
Expand Down
137 changes: 74 additions & 63 deletions Masa.Framework.sln

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Copyright (c) MASA Stack All rights reserved.
// Licensed under the MIT License. See LICENSE.txt in the project root for license information.

namespace Masa.Contrib.StackSdks.Caller;

public class AuthenticationService : IAuthenticationService
{
private readonly TokenProvider _tokenProvider;
private readonly JwtTokenValidator _jwtTokenValidator;

public AuthenticationService(TokenProvider tokenProvider,
JwtTokenValidator jwtTokenValidator)
{
_tokenProvider = tokenProvider;
_jwtTokenValidator = jwtTokenValidator;
}

public async Task ExecuteAsync(HttpRequestMessage requestMessage)
{
await _jwtTokenValidator.ValidateTokenAsync(_tokenProvider);

if (!_tokenProvider.AccessToken.IsNullOrWhiteSpace())
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _tokenProvider.AccessToken);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Copyright (c) MASA Stack All rights reserved.
// Licensed under the MIT License. See LICENSE.txt in the project root for license information.

namespace Masa.Contrib.StackSdks.Caller;

public class ClientRefreshTokenOptions
{
public string ClientId { get; set; } = string.Empty;

public string ClientSecret { get; set; } = string.Empty;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
// Copyright (c) MASA Stack All rights reserved.
// Licensed under the MIT License. See LICENSE.txt in the project root for license information.

namespace Masa.Contrib.StackSdks.Caller;

public class JwtTokenValidator
{
readonly JwtTokenValidatorOptions _jwtTokenValidatorOptions;
readonly ClientRefreshTokenOptions _clientRefreshTokenOptions;
readonly HttpClient _httpClient;
readonly ILogger<JwtTokenValidator>? _logger;

public JwtTokenValidator(
IOptions<JwtTokenValidatorOptions> jwtTokenValidatorOptions,
HttpClient httpClient,
IOptions<ClientRefreshTokenOptions> clientRefreshTokenOptions,
ILogger<JwtTokenValidator>? logger)
{
_jwtTokenValidatorOptions = jwtTokenValidatorOptions.Value;
_httpClient = httpClient;
_clientRefreshTokenOptions = clientRefreshTokenOptions.Value;
_logger = logger;
}

public async Task ValidateTokenAsync(TokenProvider tokenProvider)
{
var discoveryDocument = await _httpClient.GetDiscoveryDocumentAsync(_jwtTokenValidatorOptions.AuthorityEndpoint);
var validationParameters = new TokenValidationParameters
{
ValidateLifetime = _jwtTokenValidatorOptions.ValidateLifetime,
ValidateAudience = _jwtTokenValidatorOptions.ValidateAudience,
ValidateIssuer = _jwtTokenValidatorOptions.ValidateIssuer,
ValidIssuer = discoveryDocument.Issuer,
ValidAudiences = _jwtTokenValidatorOptions.ValidAudiences,
ValidateIssuerSigningKey = true,
IssuerSigningKeys = GetIssuerSigningKeys(discoveryDocument)
};
JwtSecurityTokenHandler handler = new();
handler.InboundClaimTypeMap.Clear();
try
{
handler.ValidateToken(tokenProvider.AccessToken, validationParameters, out SecurityToken _);
}
catch (SecurityTokenExpiredException)
{
if (!string.IsNullOrEmpty(tokenProvider.RefreshToken))
{
var tokenClient = new TokenClient(_httpClient, new TokenClientOptions
{
Address = discoveryDocument.TokenEndpoint,
ClientId = _clientRefreshTokenOptions.ClientId,
ClientSecret = _clientRefreshTokenOptions.ClientSecret
});
var tokenResult = await tokenClient.RequestRefreshTokenAsync(tokenProvider.RefreshToken).ConfigureAwait(false);
if (tokenResult.IsError)
{
_logger?.LogError("JwtTokenValidator failed, RefreshToken failed, Error Message: {Message}", tokenResult.Error);
throw new UserFriendlyException($"JwtTokenValidator failed, RefreshToken failed, Error Message: {tokenResult.Error}");
}
else
{
tokenProvider.AccessToken = tokenResult.AccessToken;
handler.ValidateToken(tokenProvider.AccessToken, validationParameters, out SecurityToken _);
}
}
else
{
_logger?.LogWarning(
"RefreshToken is null,please AllowOfflineAccess value(true) and RequestedScopes should contains offline_access");
throw new UserFriendlyException("JwtTokenValidator failed, RefreshToken is null");
}
}
catch (Exception e)
{
_logger?.LogError(e, "JwtTokenValidator failed");
throw new UserFriendlyException("JwtTokenValidator failed");
}
}

private static List<SecurityKey> GetIssuerSigningKeys(DiscoveryDocumentResponse discoveryDocument)
{
var keys = new List<SecurityKey>();
foreach (var webKey in discoveryDocument.KeySet.Keys)
{
var e = Base64Url.Decode(webKey.E);
var n = Base64Url.Decode(webKey.N);
var key = new RsaSecurityKey(new RSAParameters { Exponent = e, Modulus = n })
{
KeyId = webKey.Kid
};
keys.Add(key);
}
return keys;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Copyright (c) MASA Stack All rights reserved.
// Licensed under the MIT License. See LICENSE.txt in the project root for license information.

namespace Masa.Contrib.StackSdks.Caller;

public class JwtTokenValidatorOptions
{
public string AuthorityEndpoint { get; set; } = string.Empty;

public bool ValidateLifetime { get; set; } = true;

public bool ValidateAudience { get; set; }

public bool ValidateIssuer { get; set; } = true;

public IEnumerable<string> ValidAudiences { get; set; } = Enumerable.Empty<string>();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="IdentityModel" Version="$(IdentityModelPackageVersion)" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="$(MicrosoftPackageVersion)" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="$(MicrosoftPackageVersion)" />
<PackageReference Include="Microsoft.IdentityModel.Protocols.OpenIdConnect" Version="$(IdentityPackageVersion)" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\..\BuildingBlocks\Service\Masa.BuildingBlocks.Service.Caller\Masa.BuildingBlocks.Service.Caller.csproj" />
<ProjectReference Include="..\..\Service\Caller\Masa.Contrib.Service.Caller.HttpClient\Masa.Contrib.Service.Caller.HttpClient.csproj" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Copyright (c) MASA Stack All rights reserved.
// Licensed under the MIT License. See LICENSE.txt in the project root for license information.

namespace Masa.Contrib.StackSdks.Caller;

public static class ServiceCollectionExtensions
{
public static IServiceCollection AddStackCaller(
this IServiceCollection services,
Assembly assembly,
Func<IServiceProvider, TokenProvider> tokenProvider,
Action<JwtTokenValidatorOptions> jwtTokenValidatorOptions,
Action<ClientRefreshTokenOptions>? clientRefreshTokenOptions = null)
{
MasaArgumentException.ThrowIfNull(jwtTokenValidatorOptions);

services.Configure(jwtTokenValidatorOptions);
services.Configure(clientRefreshTokenOptions);
services.AddScoped((serviceProvider) => { return tokenProvider.Invoke(serviceProvider); });
services.AddSingleton<JwtTokenValidator>();
services.AddAutoRegistrationCaller(assembly);
return services;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Copyright (c) MASA Stack All rights reserved.
// Licensed under the MIT License. See LICENSE.txt in the project root for license information.

namespace Masa.Contrib.StackSdks.Caller;

public abstract class StackHttpClientCaller : HttpClientCallerBase
{
protected override void UseHttpClientPost(MasaHttpClientBuilder masaHttpClientBuilder)
{
masaHttpClientBuilder.UseAuthentication(serviceProvider => new AuthenticationService(
serviceProvider.GetRequiredService<TokenProvider>(),
serviceProvider.GetRequiredService<JwtTokenValidator>()
));
base.UseHttpClientPost(masaHttpClientBuilder);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Copyright (c) MASA Stack All rights reserved.
// Licensed under the MIT License. See LICENSE.txt in the project root for license information.

namespace Masa.Contrib.StackSdks.Caller;

public class TokenProvider
{
public string? AccessToken { get; set; }

public string? RefreshToken { get; set; }

public string? IdToken { get; set; }
}
15 changes: 15 additions & 0 deletions src/Contrib/StackSdks/Masa.Contrib.StackSdks.Caller/_Imports.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Copyright (c) MASA Stack All rights reserved.
// Licensed under the MIT License. See LICENSE.txt in the project root for license information.

global using IdentityModel;
global using IdentityModel.Client;
global using Masa.BuildingBlocks.Service.Caller;
global using Masa.Contrib.Service.Caller.HttpClient;
global using Microsoft.Extensions.DependencyInjection;
global using Microsoft.Extensions.Logging;
global using Microsoft.Extensions.Options;
global using Microsoft.IdentityModel.Tokens;
global using System.IdentityModel.Tokens.Jwt;
global using System.Net.Http.Headers;
global using System.Reflection;
global using System.Security.Cryptography;
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
<Nullable>enable</Nullable>

<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
<TargetFramework>net6.0</TargetFramework>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
<Nullable>enable</Nullable>

<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>

<ItemGroup>
Expand All @@ -26,14 +27,14 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\..\Authentication\Identity\Masa.Contrib.Authentication.Identity\Masa.Contrib.Authentication.Identity.csproj" />
<ProjectReference Include="..\..\..\Dispatcher\Masa.Contrib.Dispatcher.Events\Masa.Contrib.Dispatcher.Events.csproj" />
<ProjectReference Include="..\..\Masa.Contrib.StackSdks.Config\Masa.Contrib.StackSdks.Config.csproj" />
<ProjectReference Include="..\..\Masa.Contrib.StackSdks.Middleware\Masa.Contrib.StackSdks.Middleware.csproj" />
</ItemGroup>

<ItemGroup>
<None Update="appsettings.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
<TargetFramework>net6.0</TargetFramework>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
<TargetFramework>net6.0</TargetFramework>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@
<TargetFramework>net6.0</TargetFramework>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="$(MicrosoftTeskSdkPackageVersion)" />
<PackageReference Include="Moq" Version="$(MoqPackageVersion)" />
<PackageReference Include="MSTest.TestAdapter" Version="$(MSTestPackageVersion)" />
<PackageReference Include="MSTest.TestFramework" Version="$(MSTestPackageVersion)" />
<PackageReference Include="MSTest.TestFramework" Version="$(MSTestPackageVersion)" />
<PackageReference Include="coverlet.collector" Version="$(CoverletPackageVersion)">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
Expand Down

0 comments on commit 6a7494d

Please sign in to comment.