Как вызвать сторонний API, использующий токен OAuth, чтобы разрешить вызовы через веб-API с помощью внедрения зависимостей - PullRequest
0 голосов
/ 23 октября 2018

Я хочу начать работу над переносом проекта из беспорядка классов в шаблон DI, где я могу внедрить объект и сделать свой проект тестируемым.

Мне нужно вызвать сторонние API для обработки их аутентификации (я использую несколько сторонних API), и одна из вещей, которые я делаю, это обрабатывать токен на предъявителя (OAuth), и я хотел бы знать, как и чтоэто лучший способ обработки токена OAuth или токена на предъявителя, срок действия которого истекает.

Первоначально я использовал класс со статическими членами и статическими функциями для хранения токена (срок действия 24 часа), и если он не истек, не нужно его получать, просто используйте токен носителя в переменной.

Каков наилучший способ или как придерживаться этого типа запроса токена и ответа через DI?Я хочу сделать все это на стороне сервера, это будет веб-API, с которым будет работать angular или jquery..NET Framework Standard.

Я хотел бы добавить, что в настоящее время я использую Unity для DI.

Ответы [ 2 ]

0 голосов
/ 23 октября 2018

Unity как DI Framework больше не поддерживается 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);
    }
}
0 голосов
/ 23 октября 2018

Разве вы не можете просто использовать класс для управления тем, который зарегистрирован через 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....
    }

}

, вы можете заставить сам класс токенстара делать выборку нового токена, но если его время жизни одноэлементное, то любые сервисы, которые вы добавляете в него, должны будутбыть, так что я бы, вероятно, имел TokenManager на каждый срок действия запроса, который имеет дело со всем этим, но сам использует хранилище Token-одиночки или что-то в этом роде ...

...