【问题标题】:How to Call 3rd Party API that uses a Token OAuth To Allow Calls Through Web API Using Dependency Injection如何调用使用令牌 OAuth 的第 3 方 API 以允许使用依赖注入通过 Web API 进行调用
【发布时间】:2018-10-23 12:48:21
【问题描述】:

我希望开始着手将我拥有的项目从杂乱无章的类迁移到 DI 模式,我可以在其中注入对象并使我的项目可测试。

我需要调用第三方 api 来处理他们的一些身份验证(我使用多个 3rd 方 API),我要做的一件事是处理不记名令牌 (OAuth),我想知道如何或什么是最好的处理具有到期日期的 OAuth 令牌或不记名令牌的方法。

最初我使用一个具有静态成员和静态函数的类来存储令牌(24 小时到期),如果它没有过期,则无需去获取它,只需使用变量中的不记名令牌。

通过 DI 遵守此类令牌请求和响应的最佳方式或方式是什么?我想在所有服务器端执行此操作,这将是 angular 或 jquery 将与之交互的 Web api。 .NET 框架标准。

我想补充一下,我目前正在使用 Unity for DI。

【问题讨论】:

  • 我认为您的问题可以使用更好的标题。现在它只是一堆标签,描述性不是很好。

标签: c# dependency-injection asp.net-web-api2


【解决方案1】:

你不能只使用一个类来管理通过 DI 注册为单例的类吗?那么实际上与您的旧静态内容相同。

(我假设这是用于您的服务器和其他服务器之间的通信,不直接涉及您的 api 客户端)

如果您不喜欢一直漂浮着某种臃肿的大单例的想法,您可以使用类似这样的方法简单地将令牌的 storage 抽象掉:

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 类本身获取新令牌,但如果它的生命周期是单例的,那么您注入的任何服务也必须如此,所以我可能会有一个按请求生命周期的 TokenManager它处理所有这些,但它本身使用单例令牌存储或其他东西....

【讨论】:

  • 我认为可能有比这更好的方法。我可以在每个处理第三方 api 的类中调用我的令牌管理器,如果令牌未过期,则使用它,否则再次调用 auth API。例如,我正在考虑为每种类型的调用创建一个类,创建用户,获取数据。我还有一个调用需要将第二个 API 调用链接到第二个第三方 API。我有一个获得一些定价的 API,但第二个第三方 api 提供了更高的定价,并被合并到第一个 api 调用中。抱歉有点扯远了。
  • 令牌通常在令牌中具有到期日期。如果可以刷新令牌,您只需要跟踪它们。如果是这种情况,OP 最好使用已经实现的身份验证提供程序之一,而不是自己滚动。
  • @Amy 我对此的理解是后端使用此令牌与其他服务通信,因此如果后端 API 没有记住令牌的内容,它'将在 API 处理初始请求后消失 - 此令牌不会返回给原始客户端...
  • @whreed 我已经编辑了我的答案以扩展我可能会做的事情 - 你确实需要对单身人士有点小心(特别是,没有依赖服务的单身人士注入它可能有更短的生命周期!)但如果它们的范围非常小,那么这应该是非常易于管理的。
  • 不确定注射过多是什么意思?将太多服务注入一件事?我有很多东西注入了多种不同的服务,但不要认为我曾经拥有超过 8 个或其他东西。许多较小的服务通常使测试更容易,并且具有明确定义的角色,但与任何事情一样,你可以做得太过分了。
【解决方案2】:

Unity 作为 DI 框架不再由 Microsoft 维护,现在由社区负责,请参阅此处的链接:Unity Future

现在如果您想将项目迁移到新的 webapi,请开始寻找 Aspnet 核心:ASPNet Core

现在就令牌而言,您可以开始寻找与 Identity Server 的集成,它是 OAuth 和 OpenId 的实现,并与 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);
    }
}

【讨论】:

  • 我应该说我不能去Core。这个 API 将存在于 Kentico 中。长话短说,但只能说它不能生活在这之外。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-03-29
  • 2018-05-07
  • 1970-01-01
  • 1970-01-01
  • 2021-08-21
  • 1970-01-01
相关资源
最近更新 更多