如何使用依赖注入调用使用令牌 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);
    }
}