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

时间:2017-09-19 19:38:24

标签: c# asp.net-mvc asp.net-web-api jwt claims-based-identity

我有一个架构,我有一个初始的ASP MVC登陆页面,它调用一个Web API服务,将调用转换为另外两个本身也有的。“/ p>

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

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

这可能吗?这里的首选或最佳做法是什么?也许我会使用服务器来为每个跳跃服务器流..但似乎跟着获得另一个令牌..我甚至会把它们放在内部玩偶中?

4 个答案:

答案 0 :(得分:5)

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

更新2 - 添加第5点

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

<强>案例

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

<强>评论

  1. 这意味着Api2可以访问Api3,Api4,Api5。但是如果Api2不能被授予Api5的访问权限会怎样?现在你有问题了。一旦出现这种情况,您就必须重新设计安全机制。

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

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

  4. 如果您使用范围执行身份验证授权,则您不应该共享您的令牌,因为Api1可以执行代码,例如Api2不应该&#39 ; t执行,这是一个安全问题。

  5. 如果Api1是向IdP请求令牌的人。 Api2会发生什么事 你想与Api1分开使用吗?它不能打电话给别人Apis,因为Api1没有通过令牌?或者所有的Apis都有能力向IdP请求令牌,并且所有人都将令牌传递给其他人Apis,这取决于Api第一次打电话的时间?你可能比需要更复杂吗?

  6. 你想要达到的目标是可行的,但对我而言,这不是一个好主意。

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

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

    你应该创建一个名为TokenCache的类,这个类负责每次到期时获取令牌,无效或为空。

    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流程,但您可以采用此概念并对其进行修改以使其符合您的要求。

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

答案 1 :(得分:1)

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

答案 2 :(得分:1)

穷人的代表团 - 在随后的API调用中简单地转发相同的承载令牌。正如其他评论所述,这引入了范围的差异。

扩展授权 - Identity Server 4引入了此授权类型以支持委派。提供持有者令牌以换取新令牌以调用第二个API。

答案 3 :(得分:0)

我认为你是对的,你只需传递令牌。显然,令牌需要它可能遇到的所有api的范围。 MVC应用程序拥有令牌并将其作为承载发送给api,api可以简单地将相同的承载令牌重新发送到它消耗的任何api,等等......