重用承载令牌用于多个API调用

Jon*_*n H 4 c# asp.net-mvc claims-based-identity jwt asp.net-web-api

我有一个体系结构,在该体系结构中,我有一个初始的ASP MVC登陆页面,该页面调用Web API服务,而Web API服务又调用另外2个自身也具有的功能。

当前,身份验证是通过Windows身份验证用户user / roles处理的。

我想在到达aspmvc方面时获得身份服务器令牌(仍然使用Windows身份验证),然后返回带有适当声明/范围的令牌,可以通过提取并沿行将其用于所有后续调用。

这可能吗?什么是首选或最佳做法?也许我会使用服务器到服务器流来实现每一次飞跃..但是似乎又要获得另一个令牌..我什至会将它们放在内部玩偶中?

Dan*_*rea 5

更新 - 与Matt G讨论后,我在回答中添加了更好的解释,以使我的观点更加清楚。我认为一开始我还不够清楚。

更新2 - 加点5

我认为应该为一个客户端颁发令牌,并且只有该特定客户端才能使用令牌来访问它要求访问的所有资源。

案件

  • Api1要求令牌,并且可以访问Api2,Api3,Api4,Api5。
  • Api2使用Api1的令牌,并有权访问与Api1相同的资源。

评论

  1. 这意味着Api2可以访问Api3,Api4,Api5。但是,如果不应授予Api2访问权限,会发生什么?现在您遇到了问题。一旦出现这种情况,您就必须重新设计安全机制。

  2. 另外,这意味着发送到Api2的令牌包含与其无关的范围,这对我来说听起来有点奇怪。

  3. 另一方面,Api1的范围可能意味着与Api2不同,这可能导致误解。但这将取决于您的开发。

  4. 如果使用Scopes 进行身份验证授权,则不应共享令牌,因为Api1可以执行例如Api2不应执行的代码,这是一个安全问题。

  5. 如果Api1是向IdP请求令牌的人。如果要与Api1分开使用,Api2会怎样?因为Api1没有将令牌传递给它,所以无法对其他Apis进行呼叫?还是所有的Apis都有能力向IdP请求令牌,并且所有的Apis都将令牌传递给其他Apis,具体取决于首次呼叫的是哪个Api?您是否可能提出了比需要更多的复杂性?

您试图实现的目标是可行的,但对我而言,这不是一个好主意。

下面我为您提出了解决此问题的替代解决方案。

听起来您每次需要执行HttpClient.Send时都需要TokenCache和一种将其注入的机制。这就是我建议你的。

您应该创建一个名为TokenCache的类,该类负责在每次过期,无效或为null时获取Token。

public class TokenCache : ITokenCache
{
    public TokenClient TokenClient { get; set; }
    private readonly string _scope;
    private DateTime _tokenCreation;
    private TokenResponse _tokenResponse;

    public TokenCache(string scope)
    {
        _scope = scope;
    }

    private bool IsTokenValid()
    {
        return _tokenResponse != null && 
                !_tokenResponse.IsError &&
                !string.IsNullOrWhiteSpace(_tokenResponse.AccessToken) &&
                (_tokenCreation.AddSeconds(_tokenResponse.ExpiresIn) > DateTime.UtcNow);
    }

    private async Task RequestToken()
    {
        _tokenResponse = await TokenClient.RequestClientCredentialsAsync(_scope).ConfigureAwait(false);
        _tokenCreation = DateTime.UtcNow;
    }

    public async Task<string> GetAccessToken(bool forceRefresh = false)
    {
        if (!forceRefresh && IsTokenValid()) return _tokenResponse.AccessToken;

        await RequestToken().ConfigureAwait(false);

        if (!IsTokenValid())
        {
            throw new InvalidOperationException("An unexpected token validation error has occured during a token request.");
        }

        return _tokenResponse.AccessToken;
    }
}
Run Code Online (Sandbox Code Playgroud)

您将创建一个类TokenHttpHandler,如下所示。每次执行HttpClient.Send时,此类将设置Bearer令牌。请注意,我们正在使用TokenCache(_tokenCache.GetAccessToken)在SetAuthHeaderAndSendAsync方法中获取令牌。这样,您可以确定每次从api / mvc应用程序调用另一个api时都会发送令牌。

public class TokenHttpHandler : DelegatingHandler
{
    private readonly ITokenCache _tokenCache;

    public TokenHttpHandler(ITokenCache tokenCache)
    {
        InnerHandler = new HttpClientHandler();
        _tokenCache = tokenCache;
    }

    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        var response = await SetAuthHeaderAndSendAsync(request, cancellationToken, false).ConfigureAwait(false);

        //check for 401 and retry
        if (response.StatusCode == HttpStatusCode.Unauthorized)
        {
            response = await SetAuthHeaderAndSendAsync(request, cancellationToken, true);
        }

        return response;
    }

    private async Task<HttpResponseMessage> SetAuthHeaderAndSendAsync(HttpRequestMessage request, CancellationToken cancellationToken, bool forceTokenRefresh)
    {
        request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", await _tokenCache.GetAccessToken(forceTokenRefresh).ConfigureAwait(false));

        return await base.SendAsync(request, cancellationToken).ConfigureAwait(false);
    }
Run Code Online (Sandbox Code Playgroud)

然后,在ExtendedHttpClient中使用它,如下所示。注意,我们正在将TokenHttpHandler注入构造函数。

public class ExtendedHttpClient : HttpClient
{
    public ExtendedHttpClient(TokenHttpHandler messageHandler) : base(messageHandler)
    {
        DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
        DefaultRequestHeaders.AcceptEncoding.Add(new StringWithQualityHeaderValue("gzip"));
    }
}
Run Code Online (Sandbox Code Playgroud)

最后,在IoC配置中,您需要添加新的类。

如果要对多个MVC应用程序/ Api重用以上代码,则应将其放在共享库(例如基础结构)中,然后仅为每个IdentityServer客户端配置IoC。

builder.RegisterType<TokenHttpHandler>().AsSelf();
            builder.RegisterType<ExtendedHttpClient>().As<HttpClient>();

builder.RegisterType<TokenCache>()
                .As<ITokenCache>()
                .WithParameter("scope", "YOUR_SCOPES")
                .OnActivating(e => e.Instance.TokenClient = e.Context.Resolve<TokenClient>())
                .SingleInstance();

builder.Register(context =>
                {
                    var address = "YOUR_AUTHORITY";

                    return new TokenClient(address, "ClientID", "Secret");
                })
                .AsSelf();
Run Code Online (Sandbox Code Playgroud)

注意,此示例使用ClientCredentials流,但是您可以采用此概念并对其进行修改以使其符合您的要求。

希望能帮助到你。亲切的问候丹尼尔


Man*_*ngh 3

这是微服务架构中非常常见的问题,通过API网关模式来处理。所有令牌验证都应在 API 网关级别处理。令牌验证后,请求应转发到(微)服务,该服务可以信任该请求。如果您需要更新/修复/改进/添加有关令牌安全性的任何内容,都可以在一个地方完成。