在C#中使用OAuth授权代码流请求access_token时收到400错误请求?

时间:2018-08-20 19:11:57

标签: c# asp.net-identity identity

以下Owin OAuth guide实现了以下authorization code flow。已成功从服务器接收授权代码,即直到文章中提到步骤D

当我使用access_token向服务器请求grant_type=authorization_code时。我收到 400错误的请求错误,提示invalid grant

代码如下:

Startup.Auth.cs

public void ConfigureAuth(IAppBuilder app)
        {
            app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);

            // Configure the db context and user manager to use a single instance per request
            app.CreatePerOwinContext(ApplicationDbContext.Create);
            app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);

            //use a cookie to temporarily store information about a user logging in with a third party login provider
            app.UseExternalSignInCookie(Microsoft.AspNet.Identity.DefaultAuthenticationTypes.ExternalCookie);
            OAuthBearerOptions = new OAuthBearerAuthenticationOptions();

            OAuthAuthorizationServerOptions OAuthServerOptions = new OAuthAuthorizationServerOptions()
            {

                AllowInsecureHttp = true,
                AuthorizeEndpointPath = new PathString("/oauth/authorize"),
                TokenEndpointPath = new PathString("/oauth/token"),
                AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(60),
                Provider = new SimpleOAuthProvider(),
                AuthorizationCodeProvider = new AuthenticationCodeProvider(),
                RefreshTokenProvider = new SimpleRefreshTokenProvider()
            };

            // Token Generation
            app.UseOAuthAuthorizationServer(OAuthServerOptions);
            app.UseOAuthBearerAuthentication(OAuthBearerOptions);

            // Uncomment the following lines to enable logging in with third party login providers
            //app.UseMicrosoftAccountAuthentication(
            //    clientId: "",
            //    clientSecret: "");

            //app.UseTwitterAuthentication(
            //    consumerKey: "",
            //    consumerSecret: "");

            //app.UseFacebookAuthentication(
            //    appId: "",
            //    appSecret: "");

            //app.UseGoogleAuthentication(new GoogleOAuth2AuthenticationOptions()
            //{
            //    ClientId = "",
            //    ClientSecret = ""
            //});
        }

AuthorizationCodeProvider

internal class AuthenticationCodeProvider:  IAuthenticationTokenProvider
{
    public async Task CreateAsync(AuthenticationTokenCreateContext context)
    {
        var code = Guid.NewGuid().ToString("n");

        using (AuthRepository _repo = new AuthRepository())
        {
            var token = new AuthCode()
            {
                Token = HashProvider.GetHash(code),
                IssuedUtc = DateTime.UtcNow
            };

            context.Ticket.Properties.IssuedUtc = token.IssuedUtc;
            token.ProtectedTicket = context.SerializeTicket();

            var result = await _repo.AddAuthCodeToken(token);

            if (result)
            {
                context.SetToken(token.Token);
            }

        }
    }

    public async Task ReceiveAsync(AuthenticationTokenReceiveContext context)
    {
        var allowedOrigin = context.OwinContext.Get<string>("as:clientAllowedOrigin");
        var headerPresent = context.OwinContext.Response.Headers.Any(x => x.Key == "Access-Control-Allow-Origin");
        if (headerPresent)
        {
            context.OwinContext.Response.Headers.Remove("Access-Control-Allow-Origin");
        }
        context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });

        string hashedToken = context.Token;
        hashedToken = Uri.UnescapeDataString(hashedToken);

        using (AuthRepository _repo = new AuthRepository())
        {
            var authCode = await _repo.FindAuthCodeToken(hashedToken);

            if (authCode != null)
            {
                //Get protectedTicket from refreshToken class
                context.DeserializeTicket(authCode.ProtectedTicket);
                var result = await _repo.RemoveAuthCodeToken(hashedToken);
            }
        }
    }

    public void Create(AuthenticationTokenCreateContext context)
    {
        throw new NotImplementedException();
    }

    public void Receive(AuthenticationTokenReceiveContext context)
    {
        throw new NotImplementedException();
    }
}

OAuthAuthorizationServerProvider

    public class SimpleOAuthProvider : OAuthAuthorizationServerProvider
{
    public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
    {
        //Check if client details are valid
        string clientId = string.Empty;
        string clientSecret = string.Empty;
        AuthClient client = null;

        if (!context.TryGetBasicCredentials(out clientId, out clientSecret))
        {
            context.TryGetFormCredentials(out clientId, out clientSecret);
        }

        if (context.ClientId == null)
        {
            //Remove the comments from the below line context.SetError, and invalidate context 
            //if you want to force sending clientId/secrects once obtain access tokens. 
            context.Validated();
            //context.SetError("invalid_clientId", "ClientId should be sent.");
            return Task.FromResult<object>(null);
        }

        using (AuthRepository _repo = new AuthRepository())
        {
            client = _repo.FindClient(context.ClientId);
        }

        if (client == null)
        {
            context.SetError("invalid_clientId", string.Format("Client '{0}' is not registered in the system.", context.ClientId));
            return Task.FromResult<object>(null);
        }

        if (client.Type == (Int16)ClientTypeEnum.Native)
        {
            if (string.IsNullOrWhiteSpace(clientSecret))
            {
                context.SetError("invalid_clientId", "Client secret should be sent.");
                return Task.FromResult<object>(null);
            }
            else
            {
                if (client.Secret != clientSecret)
                {
                    context.SetError("invalid_clientId", "Client secret is invalid.");
                    return Task.FromResult<object>(null);
                }
            }
        }

        if (!client.Active)
        {
            context.SetError("invalid_clientId", "Client is inactive.");
            return Task.FromResult<object>(null);
        }

        context.OwinContext.Set<string>("as:clientAllowedOrigin", client.AllowedOrigin);
        context.OwinContext.Set<string>("as:clientRefreshTokenLifeTime", client.RefreshTokenLifeTime.ToString());

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

    public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
    {
        var allowedOrigin = context.OwinContext.Get<string>("as:clientAllowedOrigin");

        if (allowedOrigin == null) allowedOrigin = "*";

        var headerPresent = context.OwinContext.Response.Headers.Any(x => x.Key == "Access-Control-Allow-Origin");
        if (headerPresent)
        {
            context.OwinContext.Response.Headers.Remove("Access-Control-Allow-Origin");
        }
        context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { allowedOrigin });

        var userManager = context.OwinContext.GetUserManager<ApplicationUserManager>();
        ApplicationUser user = await userManager.FindAsync(context.UserName, context.Password);
        using (AuthRepository _repo = new AuthRepository())
        {
            if (user == null)
            {
                context.SetError("invalid_grant", "The user name or password is incorrect.");
                return;
            }

            if (!user.EmailConfirmed)
            {
                context.SetError("unconfirmed_email", "Please confirm your email id.");
                return;
            }
        }

        var identity = new ClaimsIdentity(context.Options.AuthenticationType);
        identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()));
        identity.AddClaim(new Claim(ClaimTypes.Name, context.UserName));
        identity.AddClaim(new Claim("FullName", user.FullName));
        identity.AddClaim(new Claim(ClaimTypes.Role, "user"));
        identity.AddClaim(new Claim("sub", context.UserName));

        var props = new AuthenticationProperties(new Dictionary<string, string>
            {
                {
                    "client_id", (context.ClientId == null) ? string.Empty : context.ClientId
                },
                {
                    "session_id", Guid.NewGuid().ToString("n")
                },
                {
                    "email", user.Email
                },
                {
                    "user_id", user.Id.ToString()
                },
                {
                    "fullname", user.FullName.ToString()
                }
            });

        var ticket = new AuthenticationTicket(identity, props);
        context.Validated(ticket);

    }

    public override Task GrantRefreshToken(OAuthGrantRefreshTokenContext context)
    {
        var originalClient = context.Ticket.Properties.Dictionary["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);

        //var newClaim = newIdentity.Claims.Where(c => c.Type == "newClaim").FirstOrDefault();
        //if (newClaim != null)
        //{
        //    newIdentity.RemoveClaim(newClaim);
        //}
        //newIdentity.AddClaim(new Claim("newClaim", "newValue"));

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

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

    public override Task TokenEndpoint(OAuthTokenEndpointContext context)
    {
        foreach (KeyValuePair<string, string> property in context.Properties.Dictionary)
        {
            context.AdditionalResponseParameters.Add(property.Key, property.Value);
        }

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


    //Authorization Code flow
    public override Task ValidateClientRedirectUri(OAuthValidateClientRedirectUriContext context)
    {
        string clientId = context.ClientId;
        AuthClient client = null;

        using (AuthRepository _repo = new AuthRepository())
        {
            client = _repo.FindClient(context.ClientId);
        }

        if (client == null)
        {
            context.SetError("invalid_clientId", string.Format("Client '{0}' is not registered in the system.", context.ClientId));
            return Task.FromResult<object>(null);
        }

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

    public override async Task GrantClientCredentials(OAuthGrantClientCredentialsContext context)
    {
        var identity = new ClaimsIdentity(new GenericIdentity(
            context.ClientId, OAuthDefaults.AuthenticationType),
            context.Scope.Select(x => new Claim("urn:oauth:scope", x)));

        context.Validated(identity);
    }
    public override async Task AuthorizeEndpoint(OAuthAuthorizeEndpointContext context)
    {
        //Generate auth code to be sent to oauth/token endpoint for getting access_token
        if (context.AuthorizeRequest.IsImplicitGrantType)
        {
            var identity = new ClaimsIdentity("Bearer");
            context.OwinContext.Authentication.SignIn(identity);
            context.RequestCompleted();
        }
        else if (context.AuthorizeRequest.IsAuthorizationCodeGrantType)
        {
            var redirectUri = context.Request.Query["redirect_uri"];
            var clientId = context.Request.Query["client_id"];
            var userId = context.Request.Query["user_id"];
            var state = context.Request.Query["state"];
            var scope = context.Request.Query["scope"];
            var sessionId = Guid.NewGuid().ToString("n");
            var identity = new ClaimsIdentity(new GenericIdentity(
                clientId, OAuthDefaults.AuthenticationType));

            identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, userId.ToString()));

            var authorizeCodeContext = new AuthenticationTokenCreateContext(
                context.OwinContext,
                context.Options.AuthorizationCodeFormat,
                new AuthenticationTicket(
                    identity,
                    new AuthenticationProperties(new Dictionary<string, string>
                    {
                        {"client_id", clientId},
                        {"user_id",userId },
                        {"session_id", sessionId },
                        {"redirect_uri", redirectUri}
                    })
                    {
                        IssuedUtc = DateTimeOffset.UtcNow,
                        ExpiresUtc = DateTimeOffset.UtcNow.Add(context.Options.AuthorizationCodeExpireTimeSpan)
                    }));

            await context.Options.AuthorizationCodeProvider.CreateAsync(authorizeCodeContext);

            //Set Cors
            var allowedOrigin = context.OwinContext.Get<string>("as:clientAllowedOrigin");
            if (allowedOrigin == null) allowedOrigin = "*";
            var headerPresent = context.OwinContext.Response.Headers.Any(x => x.Key == "Access-Control-Allow-Origin");
            if (headerPresent)
            {
                context.OwinContext.Response.Headers.Remove("Access-Control-Allow-Origin");
            }
            context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { allowedOrigin });

            var model = new { RedirectUri = redirectUri + "?code=" + Uri.EscapeDataString(authorizeCodeContext.Token) + "&state=" + state + "&scope=" + scope };
            var json = Newtonsoft.Json.JsonConvert.SerializeObject(model);
            context.Response.Write(json);
            context.RequestCompleted();
        }
    }

    /// <summary>
    /// Verify the request for authorization_code
    /// </summary>
    public override async Task ValidateAuthorizeRequest(OAuthValidateAuthorizeRequestContext context)
    {
        if ((context.AuthorizeRequest.IsAuthorizationCodeGrantType || context.AuthorizeRequest.IsImplicitGrantType))
        {
            context.Validated();
        }
        else
        {
            context.Rejected();
        }
    }

    //public override async Task GrantAuthorizationCode(OAuthGrantAuthorizationCodeContext context)
    //{
    //    var allowedOrigin = context.OwinContext.Get<string>("as:clientAllowedOrigin");

    //    if (allowedOrigin == null) allowedOrigin = "*";

    //    var headerPresent = context.OwinContext.Response.Headers.Any(x => x.Key == "Access-Control-Allow-Origin");
    //    if (headerPresent)
    //    {
    //        context.OwinContext.Response.Headers.Remove("Access-Control-Allow-Origin");
    //    }
    //    context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { allowedOrigin });

    //    var userIdTemp = context.Ticket.Identity.Claims.FirstOrDefault(x => x.Type == ClaimTypes.NameIdentifier);
    //    if(userIdTemp == null)
    //    {

    //    }
    //    long userId = Convert.ToInt64(userIdTemp.Value);

    //    var userManager = context.OwinContext.GetUserManager<ApplicationUserManager>();
    //    ApplicationUser user = await userManager.FindByIdAsync(userId);

    //    var identity = new ClaimsIdentity(context.Options.AuthenticationType);
    //    identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()));
    //    identity.AddClaim(new Claim(ClaimTypes.Name, user.UserName));
    //    identity.AddClaim(new Claim("FullName", user.FullName));
    //    identity.AddClaim(new Claim(ClaimTypes.Role, "user"));
    //    //identity.AddClaim(new Claim("sub", context.UserName));

    //    var props = new AuthenticationProperties(new Dictionary<string, string>
    //        {
    //            //{
    //            //    "client_id", (context.Ticket.Properties.Dictionary.FirstOrDefault.ClientId == null) ? string.Empty : context.ClientId
    //            //},
    //            {
    //                "session_id", Guid.NewGuid().ToString("n")
    //            },
    //            {
    //                "email", user.Email
    //            },
    //            {
    //                "user_id", user.Id.ToString()
    //            },
    //            {
    //                "fullname", user.FullName.ToString()
    //            }
    //        });

    //    var ticket = new AuthenticationTicket(identity, props);
    //    context.Validated(ticket);
    //}


}

1 个答案:

答案 0 :(得分:0)

中间件似乎会检查键 redirect_uri 是否存在于 AuthenticationProperties 的字典中,将其删除,一切正常(使用经过验证的上下文)。

AuthorizationCodeProvider 的简化示例如下所示:

public class AuthorizationCodeProvider:AuthenticationTokenProvider {
    public override void Create(AuthenticationTokenCreateContext context) {
        context.SetToken(context.SerializeTicket());
    }

    public override void Receive(AuthenticationTokenReceiveContext context) {
        context.DeserializeTicket(context.Token);

        context.Ticket.Properties.Dictionary.Remove("redirect_uri"); // <-
    }
}

并且不要忘记在重写的方法 OAuthAuthorizationServerProvider.ValidateClientAuthentication 中验证上下文。同样,这是一个从模板项目的 ApplicationOAuthProvider 类继承的简化示例:

public partial class DefaultOAuthProvider:ApplicationOAuthProvider {
    public override Task ValidateClientRedirectUri(OAuthValidateClientRedirectUriContext context) {
        if(null!=context.RedirectUri) {
            context.Validated(context.RedirectUri);
            return Task.CompletedTask;
        }

        return base.ValidateClientRedirectUri(context);
    }

    public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context) {
        if(context.TryGetFormCredentials(out String clientId, out String clientSecret)) {
            // Specify the actual expected client id and secret in your case
            if(("expected-clientId"==clientId)&&("expected-clientSecret"==clientSecret)) {

                context.Validated(); // <-

                return Task.CompletedTask;
            }
        }

        return base.ValidateClientAuthentication(context);
    }

    public DefaultOAuthProvider(String publicClientId) : base(publicClientId) {
    }
}

请注意,如果您使用特定的客户端 ID 调用 context.Validated,则必须将相同的 client_id 放入票证的属性中,您可以使用方法 AuthenticationTokenProvider.Receive