OwinOAuthProviders RefreshToken流程

时间:2017-05-04 23:42:11

标签: asp.net-web-api oauth-2.0 owin asp.net-mvc-5 katana

我正在为我的公司建立一个OwinOAuth(Oauth2)提供商 - https://github.com/TerribleDev/OwinOAuthProviders

我面临的挑战是我的公司也会生成刷新令牌以及访问令牌,并为访问令牌和刷新令牌提供到期时间。访问令牌到期时间设置为1小时,使用刷新令牌我可以反复刷新访问令牌100天。

我见过的任何实现都会创建一个我不需要的RefreshTokenProvider,因为我的公司已经发送给我了。我在这里的问题是 - 在AuthenticationHandler类中,由于令牌位于内存中而未保存在任何位置,并且默认情况下始终生成Oauth令牌,因此我不确定应该在何处执行基于刷新令牌访问令牌的调用/逻辑。

我是否需要为所有返回的令牌和到期时间创建持久性存储,然后匹配到期以根据它调用RefreshTokenHandler(grant_type = refresh_token)而不是AuthenticationHandler(grant_type = authorization_code)?

我真的很困惑,对此实施的任何帮助都表示赞赏。

EDIT-1 我已经用Hidden到处替换了我的提供商的名字。 添加HiddenAuthenticationHandler的代码。

using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.Owin.Infrastructure;
using Microsoft.Owin.Logging;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.Infrastructure;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Owin.Security.Providers.Hidden.Provider;
using System.Net.Http.Headers;
using System.Net;
using System.Linq;
using Microsoft.Owin.Security.OAuth;

namespace Owin.Security.Providers.Hidden
{
    public class HiddenAuthenticationHandler : AuthenticationHandler<HiddenAuthenticationOptions>
{
    private const string XmlSchemaString = "http://www.w3.org/2001/XMLSchema#string";
    private const string AuthorizationEndPoint = "https://appcenter.Hidden.com/connect/oauth2";
    private const string TokenEndpoint = "https://oauth.platform.Hidden.com/oauth2/v1/tokens/bearer";
    private const string UserInfoEndpoint = "https://sandbox-accounts.platform.Hidden.com/v1/openid_connect/userinfo";//"https://accounts.Hidden.com/v1/openid_connect/userinfo";//Nimisha
    private const string RevokeEndpoint = "https://developer.api.Hidden.com/v2/oauth2/tokens/revoke";//Nimisha
    private const string JWKSEndpoint = "https://oauth.platform.Hidden.com/op/v1/jwks";//Nimisha
    private const string IssuerEndpoint = "https://oauth.platform.Hidden.com/op/v1";//Nimisha
    private const string DiscoveryEndpoint = "https://developer.Hidden.com/.well-known/openid_configuration";//Nimisha


    private static string refreshToken;
    private static string refreshTokenExpiresIn;
    private static string tokenType;
    private readonly ILogger _logger;
    private readonly HttpClient _httpClient;

    public HiddenAuthenticationHandler(HttpClient httpClient, ILogger logger)
    {
        _httpClient = httpClient;
        _logger = logger;
    }
    //public async Task GrantRefreshToken(OAuthGrantRefreshTokenContext context)
    //{
    //    var originalClient = context.Ticket.Properties.Dictionary["as:client_id"];
    //    var currentClient = context.ClientId;

    //    if (originalClient != currentClient)
    //    {
    //        context.SetError("invalid_clientId", "Refresh token is issued to a different clientId.");
    //        return Task.FromResult<object>(null);
    //    }

    //    // Change auth ticket for refresh token requests
    //    var newIdentity = new ClaimsIdentity(context.Ticket.Identity);
    //    newIdentity.AddClaim(new Claim("newClaim", "newValue"));

    //    var newTicket = new AuthenticationTicket(newIdentity, context.Ticket.Properties);
    //    context.Validated(newTicket);

    //    return Task.FromResult<object>(null);
    //}

    public async Task<AuthenticationTicket> RefreshTokenAsync()
    {

        AuthenticationProperties properties = null;

        // OAuth2 10.12 CSRF
        if (!ValidateCorrelationId(properties, _logger))
        {
            return new AuthenticationTicket(null, properties);
        }

        //var requestPrefix = Request.Scheme + "://" + Request.Host;
        ////var redirectUri = requestPrefix + Request.PathBase + Options.CallbackPath;
        //var redirectUri = "https://be97d742.ngrok.io/signin-Hidden";

        // Build up the body for the token request
        var body = new List<KeyValuePair<string, string>>
            {
                new KeyValuePair<string, string>("grant_type", "refresh_token"),
                new KeyValuePair<string, string>("refresh_token", refreshToken)
            };

        // Request the token
        var refreshTokenResponse =
            await _httpClient.PostAsync(TokenEndpoint, new FormUrlEncodedContent(body));
        refreshTokenResponse.EnsureSuccessStatusCode();
        var text = await refreshTokenResponse.Content.ReadAsStringAsync();

        // Deserializes the token response
        dynamic response = JsonConvert.DeserializeObject<dynamic>(text);
        var accessToken = (string)response.access_token;
        var expires = (string)response.expires_in;
        //string refreshToken = null;
        refreshToken = (string)response.refresh_token;
        refreshTokenExpiresIn = (string)response.x_refresh_token_expires_in;
        tokenType = (string)response.token_type;

        return new AuthenticationTicket(null, properties);
    }


    protected override async Task<AuthenticationTicket> AuthenticateCoreAsync()
    {
        AuthenticationProperties properties = null;

        try
        {
            string code = null;
            string state = null;

            var query = Request.Query;
            var values = query.GetValues("code");
            if (values != null && values.Count == 1)
            {
                code = values[0];
            }
            values = query.GetValues("state");
            if (values != null && values.Count == 1)
            {
                state = values[0];
            }

            properties = Options.StateDataFormat.Unprotect(state);
            if (properties == null)
            {
                return null;
            }

            // OAuth2 10.12 CSRF
            if (!ValidateCorrelationId(properties, _logger))
            {
                return new AuthenticationTicket(null, properties);
            }

            var requestPrefix = Request.Scheme + "://" + Request.Host;
            //var redirectUri = requestPrefix + Request.PathBase + Options.CallbackPath;
            var redirectUri = "https://be97d742.ngrok.io/signin-Hidden";

            // Build up the body for the token request
            var body = new List<KeyValuePair<string, string>>
            {
                new KeyValuePair<string, string>("grant_type", "authorization_code"),
                new KeyValuePair<string, string>("code", code),
                new KeyValuePair<string, string>("redirect_uri", redirectUri),
                new KeyValuePair<string, string>("client_id", Options.ClientId),
                new KeyValuePair<string, string>("client_secret", Options.ClientSecret)
            };

            // Request the token
            var tokenResponse =
                await _httpClient.PostAsync(TokenEndpoint, new FormUrlEncodedContent(body));
            tokenResponse.EnsureSuccessStatusCode();
            var text = await tokenResponse.Content.ReadAsStringAsync();

            // Deserializes the token response
            dynamic response = JsonConvert.DeserializeObject<dynamic>(text);
            var accessToken = (string)response.access_token;
            var expires = (string)response.expires_in;
            //string refreshToken = null; //Nimisha
            refreshToken = (string)response.refresh_token;
            refreshTokenExpiresIn = (string)response.x_refresh_token_expires_in;
            tokenType = (string)response.token_type;

            // Get the Hidden user
            var userRequest = new HttpRequestMessage(HttpMethod.Get, Options.Endpoints.UserInfoEndpoint );
            userRequest.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
            userRequest.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);

            var userResponse = await _httpClient.SendAsync(userRequest, Request.CallCancelled);
            userResponse.EnsureSuccessStatusCode();
            text = await userResponse.Content.ReadAsStringAsync();


            var user = JObject.Parse(text);


            var context = new HiddenAuthenticatedContext(Context, user, accessToken, expires, refreshToken, refreshTokenExpiresIn, tokenType)
            {
                Identity = new ClaimsIdentity(
                    Options.AuthenticationType,
                    ClaimsIdentity.DefaultNameClaimType,
                    ClaimsIdentity.DefaultRoleClaimType)
            };
            if (!string.IsNullOrEmpty(context.Sub))
            {
                context.Identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, context.Sub, XmlSchemaString, Options.AuthenticationType));
            }
            if (!string.IsNullOrEmpty(context.GivenName))
            {
                string Name = "";
                Name = context.GivenName;
                if (!string.IsNullOrEmpty(context.FamilyName))
                {
                    Name = Name +" " + context.FamilyName;
                }
                context.Identity.AddClaim(new Claim(ClaimsIdentity.DefaultNameClaimType, Name, XmlSchemaString, Options.AuthenticationType));
                context.Identity.AddClaim(new Claim("urn:Hidden:name", Name, XmlSchemaString, Options.AuthenticationType));

            }
            if ((!string.IsNullOrEmpty(context.Email)) && (!string.IsNullOrEmpty(context.EmailVerified)))
            {
                if (String.Equals(context.EmailVerified, "true"))
                {
                    context.Identity.AddClaim(new Claim(ClaimTypes.Email, context.Email, XmlSchemaString, Options.AuthenticationType));

                }
            }
            if ((!string.IsNullOrEmpty(context.PhoneNumber)) && (!string.IsNullOrEmpty(context.PhoneNumberVerified)))
            {
                if (String.Equals(context.PhoneNumberVerified, "true"))
                {
                    context.Identity.AddClaim(new Claim(ClaimTypes.MobilePhone, context.Email, XmlSchemaString, Options.AuthenticationType));

                }
            }



            if (!string.IsNullOrEmpty(context.StreetAddress))
            {
                context.Identity.AddClaim(new Claim(ClaimsIdentity.DefaultNameClaimType, context.StreetAddress, XmlSchemaString, Options.AuthenticationType));
            }

            if (!string.IsNullOrEmpty(context.Locality))
            {
                context.Identity.AddClaim(new Claim(ClaimsIdentity.DefaultNameClaimType, context.Locality, XmlSchemaString, Options.AuthenticationType));
            }

            if (!string.IsNullOrEmpty(context.Region))
            {
                context.Identity.AddClaim(new Claim(ClaimsIdentity.DefaultNameClaimType, context.Locality, XmlSchemaString, Options.AuthenticationType));
            }

            if (!string.IsNullOrEmpty(context.PostalCode))
            {
                context.Identity.AddClaim(new Claim(ClaimsIdentity.DefaultNameClaimType, context.Locality, XmlSchemaString, Options.AuthenticationType));
            }

            if (!string.IsNullOrEmpty(context.Country))
            {
                context.Identity.AddClaim(new Claim(ClaimsIdentity.DefaultNameClaimType, context.Locality, XmlSchemaString, Options.AuthenticationType));
            }

            context.Properties = properties;

            await Options.Provider.Authenticated(context);

            return new AuthenticationTicket(context.Identity, context.Properties);
        }
        catch (Exception ex)
        {
            _logger.WriteError(ex.Message);
        }
        return new AuthenticationTicket(null, properties);
    }

    protected override Task ApplyResponseChallengeAsync()//Nimisha-1
    {
        if (Response.StatusCode != 401)
        {
            return Task.FromResult<object>(null);
        }

        //If Response is 401 then check here based on saved Expiration duration for token and refreshtoken and then make either Refresh Token call or Access Token call
        //if(SavedTokenExpiryTime is not null and is >=DateTime.Now) then call RefreshToken api call 
        //or
        //Call below



        var challenge = Helper.LookupChallenge(Options.AuthenticationType, Options.AuthenticationMode);

        if (challenge == null) return Task.FromResult<object>(null);
        var baseUri =
            Request.Scheme +
            Uri.SchemeDelimiter +
            Request.Host +
            Request.PathBase;

        var currentUri =
            baseUri +
            Request.Path +
            Request.QueryString;

        //var redirectUri =
        //    baseUri +
        //    Options.CallbackPath;//Nimisha check callback path
        var redirectUri = "https://be97d742.ngrok.io/signin-Hidden";

        var properties = challenge.Properties;
        if (string.IsNullOrEmpty(properties.RedirectUri))
        {
            properties.RedirectUri = currentUri;
        }

        // OAuth2 10.12 CSRF
        GenerateCorrelationId(properties);

        // comma separated
        var scope = string.Join(" ", Options.Scope);

        var state = Options.StateDataFormat.Protect(properties);

        //Nimisha //"https://appcenter.Hidden.com/connect/oauth2" + //Nimisha check
        var authorizationEndpoint =              
            Options.Endpoints.AuthorizationEndpoint+
            "?client_id=" + Uri.EscapeDataString(Options.ClientId) +
            "&response_type=code" +
            "&scope=" + Uri.EscapeDataString(scope) +
            "&redirect_uri=" + Uri.EscapeDataString(redirectUri) +
            "&state=" + Uri.EscapeDataString(state);


        Response.Redirect(authorizationEndpoint);

        return Task.FromResult<object>(null);
    }

    public override async Task<bool> InvokeAsync()
    {
        return await InvokeReplyPathAsync();
    }

    private async Task<bool> InvokeReplyPathAsync()
    {
        if (!Options.CallbackPath.HasValue || Options.CallbackPath != Request.Path) return false;
        // TODO: error responses


        var ticket = await AuthenticateAsync();
        if (ticket == null)
        {
            _logger.WriteWarning("Invalid return state, unable to redirect.");
            Response.StatusCode = 500;
            return true;
        }

        var context = new HiddenReturnEndpointContext(Context, ticket)
        {
            SignInAsAuthenticationType = Options.SignInAsAuthenticationType,
            RedirectUri = ticket.Properties.RedirectUri
        };

        await Options.Provider.ReturnEndpoint(context);

        if (context.SignInAsAuthenticationType != null &&
            context.Identity != null)
        {
            var grantIdentity = context.Identity;
            if (!string.Equals(grantIdentity.AuthenticationType, context.SignInAsAuthenticationType, StringComparison.Ordinal))
            {
                grantIdentity = new ClaimsIdentity(grantIdentity.Claims, context.SignInAsAuthenticationType, grantIdentity.NameClaimType, grantIdentity.RoleClaimType);
            }
            Context.Authentication.SignIn(context.Properties, grantIdentity);
        }

        if (context.IsRequestCompleted || context.RedirectUri == null) return context.IsRequestCompleted;
        var redirectUri = context.RedirectUri;
        if (context.Identity == null)
        {
            // add a redirect hint that sign-in failed in some way
            redirectUri = WebUtilities.AddQueryString(redirectUri, "error", "access_denied");
        }
        Response.Redirect(redirectUri);
        context.RequestCompleted();

        return context.IsRequestCompleted;
    }




}
}

0 个答案:

没有答案