如何使用依赖注入调用使用令牌 OAuth 的第 3 方 API 以允许通过 Web API 进行调用
How to Call 3rd Party API that uses a Token OAuth To Allow Calls Through Web API Using Dependency Injection
我希望开始着手将我拥有的项目从一堆 classes 迁移到 DI 模式,我可以在其中注入对象并使我的项目可测试。
我需要调用第三方 api 来处理他们的一些身份验证(我使用多个第 3 方 API),我所做的其中一件事是处理不记名令牌 (OAuth),我想了解如何或什么是处理具有到期日期的 OAuth 令牌或不记名令牌的最佳方式。
最初我使用带有静态成员和静态函数的 class 来存储令牌(24 小时过期),如果它没有过期,则无需获取它,只需使用变量中的不记名令牌即可。
通过 DI 遵守此类令牌请求和响应的最佳方式是什么?我想在所有服务器端执行此操作,这将是 angular 或 jquery 将与之交互的 Web api。 .NET 框架标准。
我想补充一下,我目前正在使用 Unity 进行 DI。
你不能只用一个class来管理通过 DI 注册的单例吗?然后实际上与您的旧静态内容相同。
(我假设这是为了您的服务器与其他服务器之间的通信,并不直接涉及您的 api 客户端)
如果你不喜欢让某种臃肿的单例一直浮动的想法,你可以简单地抽象出令牌的存储,使用一些东西像这样:
public interface ITokenStore
{
string GetCurrentToken();
void SetToken(string token);
}
public class TokenStore : ITokenStore
{
private DateTime _tokenRefreshedAt;
private string _currentToken;
public string GetCurrentToken()
{
//if we last got the token more than 23 hours ago,
//just reset token
if (lastTokenRefreshed.AddHours(23) < DateTime.Now)
{
_currentToken = null;
}
return _currentToken;
}
public void SetCurrentToken(string token)
{
_currentToken = token;
}
}
然后将其注册为单例(不熟悉Unity,因此调整语法以适应):
container.RegisterSingleton<ITokenStore, TokenStore>();
然后您需要令牌的服务可以按请求或瞬态生命周期注册,只需执行以下操作:
class SomeService
{
private ITokenStore _tokenStore;
public SomeService(ITokenStore tokenStore)
{
_tokenStore = tokenStore;
}
public string DoThings(params..)
{
var currentToken = _tokenStore.GetCurrentToken();
if (currentToken == null)
{
currentToken = GetNewTokenSomehow();
_tokenStore.SetCurrentToken(currentToken);
}
.... Do other things....
}
}
你可以让 tokenstore class 自己获取一个新的 token,但是如果它的生命周期是单例的,那么你注入它的任何服务都必须是单例的,所以我可能会有一个per-request-lifetime TokenManager 处理所有这些,但它本身使用单例令牌存储或其他东西....
作为 DI 框架的 Unity 不再由 Microsoft 维护,现在由社区负责,请参阅此处 link:Unity Future
现在如果你想将项目迁移到新的 webapi,开始寻找 Aspnet 核心:ASPNet Core
现在就令牌而言,您可以开始寻找与 Identity Server it is an implementation of OAuth and OpenId and it has an integration with AspNet Core AspNet Core Security Video 的集成。在与身份提供者(可以是 Google、Facebook 等)通信时,您不需要随时存储令牌,如果您想刷新令牌,您可以自己处理。
请参阅下面的示例:
public interface IApplicationHttpClient
{
Task<HttpClient> GetClient();
}
public class ApplicationHttpClient : IApplicationHttpClient
{
private readonly IHttpContextAccessor _httpContextAccessor;
private HttpClient _httpClient = new HttpClient();
public ApplicationHttpClient(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
public async Task<HttpClient> GetClient()
{
string accessToken = string.Empty;
// get the current HttpContext to access the tokens
var currentContext = _httpContextAccessor.HttpContext;
// get access token
//accessToken = await currentContext.GetTokenAsync(OpenIdConnectParameterNames.AccessToken);
//should we renew access & refresh tokens?
//get expires_at value
var expires_at = await currentContext.GetTokenAsync("expires_at");
//compare -make sure to use the exact date formats for comparison(UTC, in this case)
if (string.IsNullOrWhiteSpace(expires_at) ||
((DateTime.Parse(expires_at).AddSeconds(-60)).ToUniversalTime() < DateTime.UtcNow))
{
accessToken = await RenewTokens();
}
else
{
//get access token
accessToken = await currentContext.GetTokenAsync(OpenIdConnectParameterNames.AccessToken);
}
if (!string.IsNullOrWhiteSpace(accessToken))
{
// set as Bearer token
_httpClient.SetBearerToken(accessToken);
}
//api url
_httpClient.BaseAddress = new Uri("https://localhost:44310/");
_httpClient.DefaultRequestHeaders.Accept.Clear();
_httpClient.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json"));
return _httpClient;
}
public async Task<string> RenewTokens()
{
//get the current HttpContext to access the tokens
var currentContext = _httpContextAccessor.HttpContext;
//get the metadata from the IDP
var discoveryClient = new DiscoveryClient("https://localhost:44329/");
var metaDataResponse = await discoveryClient.GetAsync();
//create a new token client to get new tokens
var tokenClient = new TokenClient(metaDataResponse.TokenEndpoint, "mywebapp", "secret");
//get the saved refresh token
var currentRefreshToken = await currentContext.GetTokenAsync(OpenIdConnectParameterNames.RefreshToken);
//refresh the tokens
var tokenResult = await tokenClient.RequestRefreshTokenAsync(currentRefreshToken);
if (!tokenResult.IsError)
{
var updatedTokens = new List<AuthenticationToken>();
updatedTokens.Add(new AuthenticationToken
{
Name = OpenIdConnectParameterNames.IdToken,
Value = tokenResult.IdentityToken
});
updatedTokens.Add(new AuthenticationToken
{
Name = OpenIdConnectParameterNames.AccessToken,
Value = tokenResult.AccessToken
});
updatedTokens.Add(new AuthenticationToken
{
Name = OpenIdConnectParameterNames.RefreshToken,
Value = tokenResult.RefreshToken
});
var expiresAt = DateTime.UtcNow + TimeSpan.FromSeconds(tokenResult.ExpiresIn);
updatedTokens.Add(new AuthenticationToken
{
Name = "expires-at",
Value = expiresAt.ToString("o", CultureInfo.InvariantCulture)
});
//get authenticate result, containing the current principal & properties
var currentAuthenticateResult = await currentContext.AuthenticateAsync("Cookies");
//store the updated tokens
currentAuthenticateResult.Properties.StoreTokens(updatedTokens);
//sign in
await currentContext.SignInAsync("Cookies", currentAuthenticateResult.Principal,
currentAuthenticateResult.Properties);
//return the new access token
return tokenResult.AccessToken;
}
throw new Exception("Problem encountered while refreshing tokens.", tokenResult.Exception);
}
}
我希望开始着手将我拥有的项目从一堆 classes 迁移到 DI 模式,我可以在其中注入对象并使我的项目可测试。
我需要调用第三方 api 来处理他们的一些身份验证(我使用多个第 3 方 API),我所做的其中一件事是处理不记名令牌 (OAuth),我想了解如何或什么是处理具有到期日期的 OAuth 令牌或不记名令牌的最佳方式。
最初我使用带有静态成员和静态函数的 class 来存储令牌(24 小时过期),如果它没有过期,则无需获取它,只需使用变量中的不记名令牌即可。
通过 DI 遵守此类令牌请求和响应的最佳方式是什么?我想在所有服务器端执行此操作,这将是 angular 或 jquery 将与之交互的 Web api。 .NET 框架标准。
我想补充一下,我目前正在使用 Unity 进行 DI。
你不能只用一个class来管理通过 DI 注册的单例吗?然后实际上与您的旧静态内容相同。
(我假设这是为了您的服务器与其他服务器之间的通信,并不直接涉及您的 api 客户端)
如果你不喜欢让某种臃肿的单例一直浮动的想法,你可以简单地抽象出令牌的存储,使用一些东西像这样:
public interface ITokenStore
{
string GetCurrentToken();
void SetToken(string token);
}
public class TokenStore : ITokenStore
{
private DateTime _tokenRefreshedAt;
private string _currentToken;
public string GetCurrentToken()
{
//if we last got the token more than 23 hours ago,
//just reset token
if (lastTokenRefreshed.AddHours(23) < DateTime.Now)
{
_currentToken = null;
}
return _currentToken;
}
public void SetCurrentToken(string token)
{
_currentToken = token;
}
}
然后将其注册为单例(不熟悉Unity,因此调整语法以适应):
container.RegisterSingleton<ITokenStore, TokenStore>();
然后您需要令牌的服务可以按请求或瞬态生命周期注册,只需执行以下操作:
class SomeService
{
private ITokenStore _tokenStore;
public SomeService(ITokenStore tokenStore)
{
_tokenStore = tokenStore;
}
public string DoThings(params..)
{
var currentToken = _tokenStore.GetCurrentToken();
if (currentToken == null)
{
currentToken = GetNewTokenSomehow();
_tokenStore.SetCurrentToken(currentToken);
}
.... Do other things....
}
}
你可以让 tokenstore class 自己获取一个新的 token,但是如果它的生命周期是单例的,那么你注入它的任何服务都必须是单例的,所以我可能会有一个per-request-lifetime TokenManager 处理所有这些,但它本身使用单例令牌存储或其他东西....
作为 DI 框架的 Unity 不再由 Microsoft 维护,现在由社区负责,请参阅此处 link:Unity Future
现在如果你想将项目迁移到新的 webapi,开始寻找 Aspnet 核心:ASPNet Core
现在就令牌而言,您可以开始寻找与 Identity Server it is an implementation of OAuth and OpenId and it has an integration with AspNet Core AspNet Core Security Video 的集成。在与身份提供者(可以是 Google、Facebook 等)通信时,您不需要随时存储令牌,如果您想刷新令牌,您可以自己处理。 请参阅下面的示例:
public interface IApplicationHttpClient
{
Task<HttpClient> GetClient();
}
public class ApplicationHttpClient : IApplicationHttpClient
{
private readonly IHttpContextAccessor _httpContextAccessor;
private HttpClient _httpClient = new HttpClient();
public ApplicationHttpClient(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
public async Task<HttpClient> GetClient()
{
string accessToken = string.Empty;
// get the current HttpContext to access the tokens
var currentContext = _httpContextAccessor.HttpContext;
// get access token
//accessToken = await currentContext.GetTokenAsync(OpenIdConnectParameterNames.AccessToken);
//should we renew access & refresh tokens?
//get expires_at value
var expires_at = await currentContext.GetTokenAsync("expires_at");
//compare -make sure to use the exact date formats for comparison(UTC, in this case)
if (string.IsNullOrWhiteSpace(expires_at) ||
((DateTime.Parse(expires_at).AddSeconds(-60)).ToUniversalTime() < DateTime.UtcNow))
{
accessToken = await RenewTokens();
}
else
{
//get access token
accessToken = await currentContext.GetTokenAsync(OpenIdConnectParameterNames.AccessToken);
}
if (!string.IsNullOrWhiteSpace(accessToken))
{
// set as Bearer token
_httpClient.SetBearerToken(accessToken);
}
//api url
_httpClient.BaseAddress = new Uri("https://localhost:44310/");
_httpClient.DefaultRequestHeaders.Accept.Clear();
_httpClient.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json"));
return _httpClient;
}
public async Task<string> RenewTokens()
{
//get the current HttpContext to access the tokens
var currentContext = _httpContextAccessor.HttpContext;
//get the metadata from the IDP
var discoveryClient = new DiscoveryClient("https://localhost:44329/");
var metaDataResponse = await discoveryClient.GetAsync();
//create a new token client to get new tokens
var tokenClient = new TokenClient(metaDataResponse.TokenEndpoint, "mywebapp", "secret");
//get the saved refresh token
var currentRefreshToken = await currentContext.GetTokenAsync(OpenIdConnectParameterNames.RefreshToken);
//refresh the tokens
var tokenResult = await tokenClient.RequestRefreshTokenAsync(currentRefreshToken);
if (!tokenResult.IsError)
{
var updatedTokens = new List<AuthenticationToken>();
updatedTokens.Add(new AuthenticationToken
{
Name = OpenIdConnectParameterNames.IdToken,
Value = tokenResult.IdentityToken
});
updatedTokens.Add(new AuthenticationToken
{
Name = OpenIdConnectParameterNames.AccessToken,
Value = tokenResult.AccessToken
});
updatedTokens.Add(new AuthenticationToken
{
Name = OpenIdConnectParameterNames.RefreshToken,
Value = tokenResult.RefreshToken
});
var expiresAt = DateTime.UtcNow + TimeSpan.FromSeconds(tokenResult.ExpiresIn);
updatedTokens.Add(new AuthenticationToken
{
Name = "expires-at",
Value = expiresAt.ToString("o", CultureInfo.InvariantCulture)
});
//get authenticate result, containing the current principal & properties
var currentAuthenticateResult = await currentContext.AuthenticateAsync("Cookies");
//store the updated tokens
currentAuthenticateResult.Properties.StoreTokens(updatedTokens);
//sign in
await currentContext.SignInAsync("Cookies", currentAuthenticateResult.Principal,
currentAuthenticateResult.Properties);
//return the new access token
return tokenResult.AccessToken;
}
throw new Exception("Problem encountered while refreshing tokens.", tokenResult.Exception);
}
}