Google Data API授权重定向URI不匹配

时间:2017-06-24 18:16:18

标签: c# asp.net-core google-api youtube-api asp.net-core-1.1

背景

我想在.NET Core 1.1中编写一个小型的个人Web应用程序来与YouTube进行交互,让我更容易做些事情,并且我正在关注Google's YouTube documentation中的教程/示例。听起来很简单吧? ;)

使用Google的API进行身份验证似乎不可能!我做了以下事情:

  1. 在Google Developer Console中创建了一个帐户
  2. 在Google Developer Console中创建了一个新项目
  3. 创建了一个Web应用程序OAuth客户端ID,并将我的Web App调试URI添加到已批准的重定向URI列表中
  4. 保存在为系统生成OAuth客户端ID后提供的json文件
  5. 在我的应用程序中,我的调试服务器url已设置(当我的应用程序在调试中启动时,它使用我设置的网址http://127.0.0.1:60077)。
  6. 但是,当我尝试使用Google的API进行身份验证时,我收到以下错误:

      
        
    1. 这是一个错误。
    2.         

      错误:redirect_uri_mismatch

           

      请求中的重定向URI http://127.0.0.1:63354/authorize/,   与OAuth客户端授权的不匹配。

    问题

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

    的人
      

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

    即可

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

    注意:请不要说"为什么不按照其他方式执行此操作,而根本不回答您的问题#" 34。

    代码

    client_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);
    }
    

    项目属性

    Project Properties

4 个答案:

答案 0 :(得分:3)

原始答案有效,但它不是为ASP.NET Web应用程序执行此操作的最佳方法。请参阅下面的更新,以获得更好的方法来处理ASP.NET Web应用程序的流程。

原始答案

所以,我想出来了。问题是Google认为Web应用程序是基于JavaScript的Web应用程序,而不是具有服务器端处理的Web应用程序。因此,您无法在Google Developer Console中为基于服务器的Web应用程序创建Web应用程序OAuth客户端ID。

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

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

2018年2月16日更新了更好的答案:

我想提供此答案的更新。虽然,我上面所说的,但这不是实现ASP.NET解决方案的OAuth工作流的最佳方式。有一种更好的方法可以实际使用正确的OAuth 2.0流程。谷歌的文档在这方面很糟糕(特别是对于.NET),所以我在这里提供一个简单的实现示例。该示例使用的是ASP.NET核心,但它很容易适应完整的.NET框架:)

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

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

流程将如下工作:

  1. 用户点击按钮(例如添加YouTube)
  2. View调用Controller上的方法以获取授权URL
  3. 在控制器方法上,我们要求Google根据我们的客户凭据(在Google开发人员信息中心中创建的凭据)向我们提供授权网址,并为Google提供应用程序的重定向网址(此重定向网址必须在您的您的Google应用程序的已接受重定向网址列表
  4. Google向我们提供了授权网址
  5. 我们将用户重定向到该授权网址
  6. 用户授予我们的应用程序访问权限
  7. Google使用我们根据请求提供Google的重定向网址为我们的应用程序提供了一个特殊的访问代码
  8. 我们使用该访问代码获取用户的Oauth令牌
  9. 我们为用户保存Oauth令牌
  10. 您需要以下NuGet包

    1. Google.Apis
    2. Google.Apis.Auth
    3. Google.Apis.Core
    4. Google.apis.YouTube.v3
    5. 模型

      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>
      

答案 1 :(得分:0)

如引用here,您需要为ASP.NET开发服务器指定一个修复端口,如How to fix a port number in asp.NET development server,并将此url与修复端口一起添加到允许的URL。另外,如此thread中所述,当您的浏览器将用户重定向到Google的oAuth页面时,您应该将您希望Google服务器返回的重定向URI作为参数传递给令牌响应。

答案 2 :(得分:0)

我注意到有很容易的非编程方式。

如果您具有使用典型MS约定构建的典型Monotlith应用程序(因此与12factor和典型DDD不兼容),则可以选择告诉您的代理WWW服务器将所有请求从HTTP重写为HTTPS,即使您已经设置了Web在http://localhost:5000上的应用程序,然后添加到Google API网址中,例如:http://your.domain.net/sigin-google,它将完美运行,而且不是那样,因为设置主WWW将所有内容重写为HTTPS更加安全。

我猜这不是一个很好的实践,但是它是有道理的并且可以完成工作。

答案 3 :(得分:0)

我在.net Core应用程序中为这个问题苦苦挣扎了好几个小时。最终为我解决的问题是,在Google开发人员控制台中,为“桌面应用”而非“ Web应用”创建并使用了凭据。

enter image description here enter image description here