为多个 API 调用重用不记名令牌

Posted

技术标签:

【中文标题】为多个 API 调用重用不记名令牌【英文标题】:Reusing a bearer token for multiple API calls 【发布时间】:2018-02-28 17:12:21 【问题描述】:

我有一个架构,其中我有一个初始的 ASP MVC 登录页面,该页面调用一个 Web API 服务,该服务又调用另外两个自己也有的。

目前身份验证是通过 windows 身份验证用户用户/角色处理的。

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

这可能吗?这里的首选或最佳实践是什么?也许我会在每次飞跃时使用服务器到服务器的流程.. 但似乎跟随获得另一个令牌.. 我什至会将它们放在内部玩偶中的什么地方??

【问题讨论】:

【参考方案1】:

已更新 - 在与 Matt G 讨论后,我为我的答案添加了更好的解释,以便明确我的观点。我想我一开始还不够清楚。

更新 2 - 添加点 5

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

案例

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

评论

    表示Api2可以访问Api3、Api4、Api5。但是,如果不应该授予 Api2 访问 Api5 的权限,会发生什么?现在你有问题。一旦出现这种情况,您就必须重新设计您的安全机制。

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

    另一方面,Api1 的范围可能对 Api2 意味着不同的东西,这可能会导致误解。但这将取决于您的发展。

    如果您使用 Scopes 进行 authenticationauthorization,则不应共享您的令牌,因为 Api1 可以执行例如 Api2 不应执行的代码并且这是一个安全问题。

    如果 Api1 是向 IdP 请求令牌的那个。如果 Api2 发生了什么 您想将它与 Api1 分开使用吗?它不能调用其他 API,因为 Api1 没有将令牌传递给它?还是所有 API 都能够向 IdP 请求令牌,并且所有 API 都根据第一次调用的 API 将令牌传递给其他 API?您是否可能比需要的复杂程度更高?

您想要实现的目标是可行的,但对我来说这不是一个好主意。

下面我建议你解决这个问题的替代方案。

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

你应该创建一个叫TokenCache的类,这个类负责在每次过期、无效或为空时获取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;
    

您创建一个 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);
    

然后你在 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"));
    

最后,在您的 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();

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

希望对您有所帮助。 亲切的问候 丹尼尔

【讨论】:

非常感谢,但我不明白这对重用有何帮助?这如何提取/公开用于希望 1a->1b 的令牌,它可以用于 1b->2a/2b/c,然后再次用于 2x->y。 对不起,我忘了在回答中说应该为一个客户颁发令牌并仅供该客户使用。我实际上向您展示了一种发行令牌并缓存它们的方法,因此您不必担心它们。我要编辑我的答案。 @DanielBoteroCorrea 我尝试使用 Autofac 作为 IoC 容器来实现它,不幸的是每次代码命中: return await base.SendAsync(request, cancelToken).ConfigureAwait(false);我要么任务被取消,要么对象被处置......:/【参考方案2】:

穷人的委托 - 只需在后续 API 调用中转发相同的不记名令牌。正如其他 cmets 所述,这会导致范围上的差异。

扩展授权 - Identity Server 4 引入了这种授权类型来支持委派。提供不记名令牌以换取新令牌以调用第二个 API。

【讨论】:

【参考方案3】:

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

【讨论】:

我对此进行了思考和思考……我很快得出结论,这是正确的方法。这是在没有我所希望的严格辩论或最佳实践链接的情况下发生的。对我来说,我只需要请求访问服务 ONE 的权限似乎是有道理的。我的凭据不应影响该服务访问后续调用以执行其功能的能力。 我想主要的收获——这就是引发争论的原因是,其他服务使用的服务在它们之间使用令牌是不正确的——只有在公开暴露的情况下令牌认证是否合适。【参考方案4】:

我认为您是对的,您只需传递令牌即可。显然,令牌需要它可能命中的所有 api 的范围。 MVC 应用拥有令牌并将其作为 Bearer 发送到 api,该 api 可以简单地将相同的承载令牌重新发送到它使用的任何 api,等等......

【讨论】:

在我看来这是个坏主意。 “可能导致安全问题” - 您能详细说明一下吗? “所有 API 的范围” - 是的,但无论如何它都会有第一级 api 的范围,那么为什么不是第二级呢?令牌仅包含身份验证信息。令牌不能提供授权信息,因为这很可能是客户端/api 特定的。 例如授予对 Api1 中的范围的访问权限,这在 Api2 中可能意味着不同的东西,因此您可以实际执行不允许执行的代码。 我想这就是我所说的授权。我认为令牌仅对您进行身份验证,而不是授权。允许令牌持有者做什么应该由每个 api 决定。令牌仅标识持有者,而不是他们可以做什么。 我同意你的看法。如果授权信息在令牌中,那么不要在api之间共享,如果不是那么应该可以共享。目前我认为很多人在令牌中包含授权。这次谈话内容丰富:youtube.com/watch?v=EJeZ3YNnqz8 谢谢,愉快的聊天:)

以上是关于为多个 API 调用重用不记名令牌的主要内容,如果未能解决你的问题,请参考以下文章

重用令牌 asp.net web api c#

使用需要不记名令牌的 API 在 Python 中进行 API 调用

Power Query API 调用 POST 方法 - 不记名令牌

如何传递不记名令牌以使用 URLSessoin 进行 Yelp API 调用

代表客户端验证密钥斗篷不记名令牌

JWT不记名令牌授权不起作用asp net core web api