C# Google数据API授权重定向URI不匹配 背景

C# Google数据API授权重定向URI不匹配 背景,c#,asp.net-core,google-api,youtube-api,asp.net-core-1.1,C#,Asp.net Core,Google Api,Youtube Api,Asp.net Core 1.1,我想在.NET Core 1.1中编写一个小型的个人web应用程序,与YouTube进行交互,使一些事情对我来说更容易完成,我正在按照中的教程/示例进行操作。听起来很简单,对吧?;) 用谷歌的API进行认证似乎是不可能的!我已经做了以下工作: 在Google开发者控制台中创建了一个帐户 在Google开发者控制台中创建了一个新项目 创建了Web应用程序OAuth客户端ID,并将我的Web应用程序调试URI添加到已批准的重定向URI列表中 将生成OAuth客户端ID后提供的json文件保存到我的系

我想在.NET Core 1.1中编写一个小型的个人web应用程序,与YouTube进行交互,使一些事情对我来说更容易完成,我正在按照中的教程/示例进行操作。听起来很简单,对吧?;)

用谷歌的API进行认证似乎是不可能的!我已经做了以下工作:

  • 在Google开发者控制台中创建了一个帐户
  • 在Google开发者控制台中创建了一个新项目
  • 创建了Web应用程序OAuth客户端ID,并将我的Web应用程序调试URI添加到已批准的重定向URI列表中
  • 将生成OAuth客户端ID后提供的json文件保存到我的系统
  • 在我的应用程序中,设置了调试服务器url(当我的应用程序在调试中启动时,它使用的是我设置的url,即)
  • 但是,当我尝试使用Google的API进行身份验证时,我收到以下错误:

  • 那是个错误
  • 错误:重定向\u uri\u不匹配

    请求中的重定向URI, 与为OAuth客户端授权的不匹配

    问题 所以现在,对于这个问题。在寻找解决方案时,我唯一能找到的就是人们说

    只需将重定向URI放入已批准的重定向URI中

    不幸的是,问题在于每次我的代码尝试使用Google的API进行身份验证时,它使用的重定向URI都会发生变化(即使我在项目的属性中设置了静态端口,端口也会发生变化)。我似乎找不到办法让它使用静态端口。任何帮助或信息都会很棒

    注意:请不要说“你为什么不换一种根本无法回答你问题的方式呢?”

    代码 客户端id.json

    {
        "web": {
            "client_id": "[MY_CLIENT_ID]",
            "project_id": "[MY_PROJECT_ID]",
            "auth_uri": "https://accounts.google.com/o/oauth2/auth",
            "token_uri": "https://accounts.google.com/o/oauth2/token",
            "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
            "client_secret": "[MY_CLIENT_SECRET]",
            "redirect_uris": [
                "http://127.0.0.1:60077/authorize/"
            ]
        }
    }
    
    试图使用API的方法

    public async Task<IActionResult> Test()
    {
        string ClientIdPath = @"C:\Path\To\My\client_id.json";
        UserCredential credential;
    
        using (var stream = new FileStream(ClientIdPath, FileMode.Open, FileAccess.Read))
        {
            credential = await GoogleWebAuthorizationBroker.AuthorizeAsync(
                GoogleClientSecrets.Load(stream).Secrets,
                new[] { YouTubeService.Scope.YoutubeReadonly },
                "user",
                CancellationToken.None,
                new FileDataStore(this.GetType().ToString())
            );
        }
    
        var youtubeService = new YouTubeService(new BaseClientService.Initializer()
        {
            HttpClientInitializer = credential,
            ApplicationName = this.GetType().ToString()
        });
    
        var channelsListRequest = youtubeService.Channels.List("contentDetails");
        channelsListRequest.Mine = true;
    
        // Retrieve the contentDetails part of the channel resource for the authenticated user's channel.
        var channelsListResponse = await channelsListRequest.ExecuteAsync();
    
        return Ok(channelsListResponse);
    }
    
    公共异步任务测试()
    {
    字符串clientipath=@“C:\Path\To\My\client\u id.json”;
    用户凭证;
    使用(var stream=newfilestream(clientdpath,FileMode.Open,FileAccess.Read))
    {
    凭证=等待GoogleWebAuthorizationBroker.AuthorizationAsync(
    GoogleClientSecrets.Load(stream.Secrets),
    新[]{YouTubeService.Scope.YoutubeReadonly},
    “用户”,
    取消令牌。无,
    新文件数据存储(this.GetType().ToString())
    );
    }
    var youtubeService=new youtubeService(new BaseClientService.Initializer()
    {
    HttpClientInitializer=凭证,
    ApplicationName=this.GetType().ToString()
    });
    var channelsListRequest=youtubeService.Channels.List(“contentDetails”);
    channelsListRequest.Mine=true;
    //检索已验证用户频道的频道资源的contentDetails部分。
    var channelsListResponse=等待channelsListRequest.ExecuteAsync();
    返回Ok(信道响应);
    }
    
    项目属性

    如前所述,您需要为ASP.NET development server指定一个修复端口,如,并将此url连同修复端口添加到允许的url中。同样如本文所述,当您的浏览器将用户重定向到Google的oAuth页面时,您应该将您希望Google服务器返回的重定向URI作为参数传递给令牌响应。

    原始答案有效,但对于ASP.NET Web应用程序来说,这不是最好的方法。有关处理ASP.NET Web应用程序流的更好方法,请参阅下面的更新。
    原始答案 所以,我明白了。问题在于,谷歌认为web应用程序是基于JavaScript的web应用程序,而不是具有服务器端处理的web应用程序。因此,您无法在Google开发者控制台中为基于服务器的Web应用程序创建Web应用程序OAuth客户端ID

    解决方案是在googledeveloper控制台中创建OAuth客户端ID时选择类型Other。这将使Google将其视为已安装的应用程序,而不是JavaScript应用程序,因此不需要重定向URI来处理回调

    这有点令人困惑,因为Google的.NET文档告诉您创建Web应用OAuth客户端ID


    2018年2月16日更新了更好的答案: 我想提供这个答案的更新。尽管我上面所说的有效,但这并不是为ASP.NET解决方案实现OAuth工作流的最佳方法。有一种更好的方法实际使用适当的OAuth2.0流。谷歌的文档在这方面很糟糕(特别是对于.NET),所以我将在这里提供一个简单的实现示例。该示例使用ASP.NET核心,但它很容易适应完整的.NET框架:)

    注意:谷歌确实有一个Google.API.Auth.MVC包来帮助简化这个OAuth 2.0流程,但不幸的是,它与特定的MVC实现相耦合,不适用于ASP.NET核心或Web API。所以,我不会用它。我将给出的示例适用于所有ASP.NET应用程序。这个相同的代码流可以用于您启用的任何Google API,因为它取决于您请求的作用域

    另外,我假设您已经在您的Google开发者仪表板中设置了应用程序。也就是说,您已经创建了一个应用程序,启用了必要的YouTube API,创建了一个Web应用程序客户端,并正确设置了允许的重定向URL

    流程的工作原理如下:

  • 用户单击按钮(例如添加YouTube)
  • 视图调用控制器上的方法以获取授权URL
  • 在controller方法中,我们要求Google根据我们的客户端凭据(在Google Developer Dashboard中创建的凭据)为我们提供授权URL,并为Google提供我们应用程序的重定向URL(此重定向URL必须位于您的Google应用程序可接受的重定向URL列表中)
  • 谷歌给了我们一个授权URL
  • 我们改变美国的方向
    public class ExampleModel
    {
        public bool UserHasYoutubeToken { get; set; }
    }
    
    public class ExampleController : Controller
    {
        // I'm assuming you have some sort of service that can read users from and update users to your database
        private IUserService userService;
    
        public ExampleController(IUserService userService)
        {
            this.userService = userService;
        }
    
        public async Task<IActionResult> Index()
        {
            var userId = // Get your user's ID however you get it
    
            // I'm assuming you have some way of knowing if a user has an access token for YouTube or not
            var userHasToken = this.userService.UserHasYoutubeToken(userId);
    
            var model = new ExampleModel { UserHasYoutubeToken = userHasToken }
            return View(model);
        }
    
        // This is a method we'll use to obtain the authorization code flow
        private AuthorizationCodeFlow GetGoogleAuthorizationCodeFlow(params string[] scopes)
        {
            var clientIdPath = @"C:\Path\To\My\client_id.json";
            using (var fileStream = new FileStream(clientIdPath, FileMode.Open, FileAccess.Read))
            {
                var clientSecrets = GoogleClientSecrets.Load(stream).Secrets;
                var initializer = new GoogleAuthorizationCodeFlow.Initializer { ClientSecrets = clientSecrets, Scopes = scopes };
                var googleAuthorizationCodeFlow = new GoogleAuthorizationCodeFlow(initializer);
    
                return googleAuthorizationCodeFlow;
            }
        }
    
        // This is a route that your View will call (we'll call it using JQuery)
        [HttpPost]
        public async Task<string> GetAuthorizationUrl()
        {
            // First, we need to build a redirect url that Google will use to redirect back to the application after the user grants access
            var protocol = Request.IsHttps ? "https" : "http";
            var redirectUrl = $"{protocol}://{Request.Host}/{Url.Action(nameof(this.GetYoutubeAuthenticationToken)).TrimStart('/')}";
    
            // Next, let's define the scopes we'll be accessing. We are requesting YouTubeForceSsl so we can manage a user's YouTube account.
            var scopes = new[] { YouTubeService.Scope.YoutubeForceSsl };
    
            // Now, let's grab the AuthorizationCodeFlow that will generate a unique authorization URL to redirect our user to
            var googleAuthorizationCodeFlow = this.GetGoogleAuthorizationCodeFlow(scopes);
            var codeRequestUrl = googleAuthorizationCodeFlow.CreateAuthorizationCodeRequest(redirectUrl);
            codeRequestUrl.ResponseType = "code";
    
            // Build the url
            var authorizationUrl = codeRequestUrl.Build();
    
            // Give it back to our caller for the redirect
            return authorizationUrl;
        }
    
        public async Task<IActionResult> GetYoutubeAuthenticationToken([FromQuery] string code)
        {
            if(string.IsNullOrEmpty(code))
            {
                /* 
                    This means the user canceled and did not grant us access. In this case, there will be a query parameter
                    on the request URL called 'error' that will have the error message. You can handle this case however.
                    Here, we'll just not do anything, but you should write code to handle this case however your application
                    needs to.
                */
            }
    
            // The userId is the ID of the user as it relates to YOUR application (NOT their Youtube Id).
            // This is the User ID that you assigned them whenever they signed up or however you uniquely identify people using your application
            var userId = // Get your user's ID however you do (whether it's on a claim or you have it stored in session or somewhere else)
    
            // We need to build the same redirect url again. Google uses this for validaiton I think...? Not sure what it's used for
            // at this stage, I just know we need it :)
            var protocol = Request.IsHttps ? "https" : "http";
            var redirectUrl = $"{protocol}://{Request.Host}/{Url.Action(nameof(this.GetYoutubeAuthenticationToken)).TrimStart('/')}";
    
            // Now, let's ask Youtube for our OAuth token that will let us do awesome things for the user
            var scopes = new[] { YouTubeService.Scope.YoutubeForceSsl };
            var googleAuthorizationCodeFlow = this.GetYoutubeAuthorizationCodeFlow(scopes);
            var token = await googleAuthorizationCodeFlow.ExchangeCodeForTokenAsync(userId, code, redirectUrl, CancellationToken.None);
    
            // Now, you need to store this token in rlation to your user. So, however you save your user data, just make sure you
            // save the token for your user. This is the token you'll use to build up the UserCredentials needed to act on behalf
            // of the user.
            var tokenJson = JsonConvert.SerializeObject(token);
            await this.userService.SaveUserToken(userId, tokenJson);
    
            // Now that we've got access to the user's YouTube account, let's get back
            // to our application :)
            return RedirectToAction(nameof(this.Index));
        }
    }
    
    @using YourApplication.Controllers
    @model YourApplication.Models.ExampleModel
    
    <div>
        @if(Model.UserHasYoutubeToken)
        {
            <p>YAY! We have access to your YouTube account!</p>
        }
        else
        {
            <button id="addYoutube">Add YouTube</button>
        }
    </div>
    
    <script>
        $(document).ready(function () {
            var addYoutubeUrl = '@Url.Action(nameof(ExampleController.GetAuthorizationUrl))';
    
            // When the user clicks the 'Add YouTube' button, we'll call the server
            // to get the Authorization URL Google built for us, then redirect the
            // user to it.
            $('#addYoutube').click(function () {
                $.post(addYoutubeUrl, function (result) {
                    if (result) {
                        window.location.href = result;
                    }
                });
            });
        });
    </script>