为多个 API 调用重复使用不记名令牌
Reusing a bearer token for multiple API calls
我有一个体系结构,其中我有一个初始 ASP MVC 登录页面,它调用 Web API 服务,该服务又调用另外 2 个它们自己也有的服务。
目前通过 windows 身份验证用户 user/roles.
处理身份验证
我想在到达 aspmvc 方面时获得一个身份服务器令牌(仍然使用 windows 身份验证),然后 return 一个具有合适 claims/scope 的令牌,我可以通过提取并沿线传递,将其重新用于所有后续调用。
这可能吗?这里的首选或最佳实践是什么?或许我会在每次飞跃时使用服务器到服务器的流程。但似乎会得到另一个令牌。我什至会将它们放在内部玩偶中的什么地方??
已更新 - 在与 Matt G 讨论后,我在我的回答中添加了一个更好的解释,以便阐明我的观点。估计是我一开始说的不够清楚
更新 2 - 添加点 5
我认为应该为一个客户端颁发一个令牌,并且只能由该特定客户端使用来访问它请求访问的所有资源。
案例
- Api1 请求令牌并可以访问 Api2, Api3, Api4, Api5.
- Api2 使用 Api1 的令牌并可以访问与 Api1.
相同的资源
评论
表示Api2可以访问Api3、Api4、Api5。但是,如果 Api2 不应被授予 Api5 的访问权限,会发生什么情况?现在你有问题了。一旦出现这种情况,你就得重新设计你的安全机制了。
此外,这意味着发送到 Api2 的令牌包含与其无关的范围,这对我来说听起来有点奇怪。
另一方面,Api1 的作用域对于 Api2 可能意味着不同的东西,这可能会导致误解。不过这要看你的发展了。
如果您使用作用域身份验证和授权,您不应该共享您的令牌,因为Api1 可以执行例如 Api2 不应该执行的代码,这是一个安全问题。
如果 Api1 是向 IdP 请求令牌的那个。 Api2 如果
您想与 Api1 分开使用吗?它无法调用其他 Apis 因为 Api1 没有将令牌传递给它?或者所有 Api 都能够向 IdP 请求令牌,并且所有 Api 都将令牌传递给其他 Api,具体取决于哪个 Api 进行了第一次调用?您是否可能设置了比需要更多的复杂性?
你想要实现的目标是可行的,但对我来说这不是一个好主意。
下面我建议你解决这个问题的替代方案。
听起来您需要一个 TokenCache 和一种每次执行时都注入它的机制 HttpClient.Send。这就是我给你的建议。
你应该创建一个class叫TokenCache,这个class负责在每次过期、无效或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;
}
}
您创建一个 class TokenHttpHandler,如下所示。这个 class 将在您每次执行 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 配置中,您需要添加新的 classes。
如果你想为多个MVC重用上面的代码apps/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 流程,但您可以采用此概念并对其进行修改以使其符合您的要求。
希望对您有所帮助。
亲切的问候
丹尼尔
我认为你是对的,你只是传递令牌。显然,令牌需要它可能命中的所有 api 的范围。 MVC 应用程序拥有令牌并将其作为 Bearer 发送到 api,api 可以简单地将相同的 bearer 令牌重新发送到它使用的任何 api,等等。 ..
这是微服务架构中非常常见的问题,它是通过 API 网关模式处理的。
所有令牌验证都应在 API 网关级别处理。令牌验证后,请求应转发到(微)服务,该服务可以信任该请求。如果您对 update/fix/improve/add 关于令牌安全有任何疑问,都可以在一个地方完成。
穷人的委托 - 只需在随后的 API 调用中转发相同的不记名令牌。正如其他评论中所述,这会在范围内引入差异。
扩展授权 - Identity Server 4 引入了这种授权类型来支持委托。提供承载令牌以换取新令牌以调用第二个 API.
我有一个体系结构,其中我有一个初始 ASP MVC 登录页面,它调用 Web API 服务,该服务又调用另外 2 个它们自己也有的服务。
目前通过 windows 身份验证用户 user/roles.
处理身份验证我想在到达 aspmvc 方面时获得一个身份服务器令牌(仍然使用 windows 身份验证),然后 return 一个具有合适 claims/scope 的令牌,我可以通过提取并沿线传递,将其重新用于所有后续调用。
这可能吗?这里的首选或最佳实践是什么?或许我会在每次飞跃时使用服务器到服务器的流程。但似乎会得到另一个令牌。我什至会将它们放在内部玩偶中的什么地方??
已更新 - 在与 Matt G 讨论后,我在我的回答中添加了一个更好的解释,以便阐明我的观点。估计是我一开始说的不够清楚
更新 2 - 添加点 5
我认为应该为一个客户端颁发一个令牌,并且只能由该特定客户端使用来访问它请求访问的所有资源。
案例
- Api1 请求令牌并可以访问 Api2, Api3, Api4, Api5.
- Api2 使用 Api1 的令牌并可以访问与 Api1. 相同的资源
评论
表示Api2可以访问Api3、Api4、Api5。但是,如果 Api2 不应被授予 Api5 的访问权限,会发生什么情况?现在你有问题了。一旦出现这种情况,你就得重新设计你的安全机制了。
此外,这意味着发送到 Api2 的令牌包含与其无关的范围,这对我来说听起来有点奇怪。
另一方面,Api1 的作用域对于 Api2 可能意味着不同的东西,这可能会导致误解。不过这要看你的发展了。
如果您使用作用域身份验证和授权,您不应该共享您的令牌,因为Api1 可以执行例如 Api2 不应该执行的代码,这是一个安全问题。
如果 Api1 是向 IdP 请求令牌的那个。 Api2 如果 您想与 Api1 分开使用吗?它无法调用其他 Apis 因为 Api1 没有将令牌传递给它?或者所有 Api 都能够向 IdP 请求令牌,并且所有 Api 都将令牌传递给其他 Api,具体取决于哪个 Api 进行了第一次调用?您是否可能设置了比需要更多的复杂性?
你想要实现的目标是可行的,但对我来说这不是一个好主意。
下面我建议你解决这个问题的替代方案。
听起来您需要一个 TokenCache 和一种每次执行时都注入它的机制 HttpClient.Send。这就是我给你的建议。
你应该创建一个class叫TokenCache,这个class负责在每次过期、无效或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;
}
}
您创建一个 class TokenHttpHandler,如下所示。这个 class 将在您每次执行 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 配置中,您需要添加新的 classes。
如果你想为多个MVC重用上面的代码apps/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 流程,但您可以采用此概念并对其进行修改以使其符合您的要求。
希望对您有所帮助。 亲切的问候 丹尼尔
我认为你是对的,你只是传递令牌。显然,令牌需要它可能命中的所有 api 的范围。 MVC 应用程序拥有令牌并将其作为 Bearer 发送到 api,api 可以简单地将相同的 bearer 令牌重新发送到它使用的任何 api,等等。 ..
这是微服务架构中非常常见的问题,它是通过 API 网关模式处理的。 所有令牌验证都应在 API 网关级别处理。令牌验证后,请求应转发到(微)服务,该服务可以信任该请求。如果您对 update/fix/improve/add 关于令牌安全有任何疑问,都可以在一个地方完成。
穷人的委托 - 只需在随后的 API 调用中转发相同的不记名令牌。正如其他评论中所述,这会在范围内引入差异。
扩展授权 - Identity Server 4 引入了这种授权类型来支持委托。提供承载令牌以换取新令牌以调用第二个 API.