Asp.net mvc 将用户授权限制到我的Google域

Asp.net mvc 将用户授权限制到我的Google域,asp.net-mvc,google-api,google-oauth,google-api-dotnet-client,Asp.net Mvc,Google Api,Google Oauth,Google Api Dotnet Client,应该可以将Google API OAuth2请求限制到特定的Google域。过去可以通过在请求的末尾&hd=mydomain.com进行黑客攻击。使用新的MVCAuth工具似乎不再可能了。你知道怎么做吗 public class AppFlowMetadata : FlowMetadata { private static readonly IAuthorizationCodeFlow flow = new AppGoogleAuthorizat

应该可以将Google API OAuth2请求限制到特定的Google域。过去可以通过在请求的末尾&hd=mydomain.com进行黑客攻击。使用新的MVCAuth工具似乎不再可能了。你知道怎么做吗

 public class AppFlowMetadata : FlowMetadata
    {
        private static readonly IAuthorizationCodeFlow flow =
            new AppGoogleAuthorizationCodeFlow(new GoogleAuthorizationCodeFlow.Initializer
            {
                ClientSecrets = new ClientSecrets
                {
                    ClientId = "***.apps.googleusercontent.com",
                    ClientSecret = "******"
                },
                Scopes = new[] { DriveService.Scope.Drive },
                DataStore = new FileDataStore(HttpContext.Current.Server.MapPath("~/App_Data"), true) ,
            });  

        public override string GetUserId(Controller controller)
        {
            // In this sample we use the session to store the user identifiers.
            // That's not the best practice, because you should have a logic to identify
            // a user. You might want to use "OpenID Connect".
            // You can read more about the protocol in the following link:
            // https://developers.google.com/accounts/docs/OAuth2Login.
            var user = controller.Session["user"];
            if (user == null)
            {
                user = Guid.NewGuid();
                controller.Session["user"] = user;
            }
            return user.ToString();

        }

        public override IAuthorizationCodeFlow Flow
        {
            get { return flow; }
        }
    }

public class AppGoogleAuthorizationCodeFlow : GoogleAuthorizationCodeFlow
    {
        public AppGoogleAuthorizationCodeFlow(GoogleAuthorizationCodeFlow.Initializer initializer) : base(initializer) { }

        public override AuthorizationCodeRequestUrl CreateAuthorizationCodeRequest(String redirectUri)
        {

            var authorizeUri = new Uri(AuthorizationServerUrl).AddQuery("hd", "ourgoogledomain.com"); //is not in the request
            var authUrl = new GoogleAuthorizationCodeRequestUrl(authorizeUri)
            {
                ClientId = ClientSecrets.ClientId,
                Scope = string.Join(" ", Scopes),
                RedirectUri = redirectUri,
                //AccessType = "offline",
               // ApprovalPrompt = "force"
            };
            return authUrl;
        }
    }

下载了源代码后,我可以很容易地对请求对象进行子类化,并添加自定义参数:

    public class GoogleDomainAuthorizationCodeRequestUrl : GoogleAuthorizationCodeRequestUrl
    {
        /// <summary>
        /// Gets or sets the hosted domain. 
        /// When you want to limit authorizing users from a specific domain 
        /// </summary>
        [Google.Apis.Util.RequestParameterAttribute("hd", Google.Apis.Util.RequestParameterType.Query)]
        public string Hd { get; set; }

        public GoogleDomainAuthorizationCodeRequestUrl(Uri authorizationServerUrl) : base(authorizationServerUrl)
        {
        }
    }

    public class AppGoogleAuthorizationCodeFlow : GoogleAuthorizationCodeFlow
    {
        public AppGoogleAuthorizationCodeFlow(GoogleAuthorizationCodeFlow.Initializer initializer) : base(initializer) { }

        public override AuthorizationCodeRequestUrl CreateAuthorizationCodeRequest(String redirectUri)
        {
            var authUrl = new GoogleDomainAuthorizationCodeRequestUrl(new Uri(AuthorizationServerUrl))
            {
                Hd = "mydomain.com",
                ClientId = ClientSecrets.ClientId,
                Scope = string.Join(" ", Scopes),
                RedirectUri = redirectUri
            };

            return authUrl;
        }
    }

下载了源代码后,我可以很容易地对请求对象进行子类化,并添加自定义参数:

    public class GoogleDomainAuthorizationCodeRequestUrl : GoogleAuthorizationCodeRequestUrl
    {
        /// <summary>
        /// Gets or sets the hosted domain. 
        /// When you want to limit authorizing users from a specific domain 
        /// </summary>
        [Google.Apis.Util.RequestParameterAttribute("hd", Google.Apis.Util.RequestParameterType.Query)]
        public string Hd { get; set; }

        public GoogleDomainAuthorizationCodeRequestUrl(Uri authorizationServerUrl) : base(authorizationServerUrl)
        {
        }
    }

    public class AppGoogleAuthorizationCodeFlow : GoogleAuthorizationCodeFlow
    {
        public AppGoogleAuthorizationCodeFlow(GoogleAuthorizationCodeFlow.Initializer initializer) : base(initializer) { }

        public override AuthorizationCodeRequestUrl CreateAuthorizationCodeRequest(String redirectUri)
        {
            var authUrl = new GoogleDomainAuthorizationCodeRequestUrl(new Uri(AuthorizationServerUrl))
            {
                Hd = "mydomain.com",
                ClientId = ClientSecrets.ClientId,
                Scope = string.Join(" ", Scopes),
                RedirectUri = redirectUri
            };

            return authUrl;
        }
    }
传递hd参数确实是将用户限制到您的域的正确方法。但是,验证用户是否确实属于该托管域是很重要的。我在您的示例中看到,您已经了解了如何将此参数添加回您的请求中,因此我将介绍本文的第二部分

问题是用户实际上可以在客户端修改请求的URL并删除hd参数!因此,虽然传递此参数可以为用户提供最佳UI,但您还需要验证经过身份验证的用户是否确实属于该域

若要查看用户所属的托管Google Apps for Work domain,必须在授权范围列表中包含电子邮件。然后,执行以下操作之一:

备选案文1。验证ID令牌。 当您将代码交换为访问令牌时,令牌端点还将在ID_token参数中返回一个ID令牌,假设您在请求(如电子邮件)中包含一个标识作用域。如果用户是托管域的一部分,将出现hd声明,您应该检查它是否存在,并与您期望的内容相匹配

您可以在Google上阅读更多关于ID令牌的信息,包括一些指向示例代码和库的链接,以帮助您解码它们。可以在测试期间解码ID令牌

备选案文2。呼叫用户信息 获得OAuth访问令牌后,执行GET请求https://www.googleapis.com/plus/v1/people/me/openIdConnect 在标头中使用访问令牌。它将返回一个关于用户声明的JSON字典。如果用户是托管域的一部分,将出现hd声明,您应该检查它是否存在,并与您期望的内容相匹配

阅读更多的文章

选项1和选项2之间的主要区别在于,使用ID令牌,您可以避免到服务器的另一个HTTP往返,从而使其速度更快,更不容易出错。您可以使用交互方式尝试这两个选项。

传递hd参数确实是将用户限制到您的域的正确方法。但是,验证用户是否确实属于该托管域是很重要的。我在您的示例中看到,您已经了解了如何将此参数添加回您的请求中,因此我将介绍本文的第二部分

问题是用户实际上可以在客户端修改请求的URL并删除hd参数!因此,虽然传递此参数可以为用户提供最佳UI,但您还需要验证经过身份验证的用户是否确实属于该域

若要查看用户所属的托管Google Apps for Work domain,必须在授权范围列表中包含电子邮件。然后,执行以下操作之一:

备选案文1。验证ID令牌。 当您将代码交换为访问令牌时,令牌端点还将在ID_token参数中返回一个ID令牌,假设您在请求(如电子邮件)中包含一个标识作用域。如果用户是托管域的一部分,将出现hd声明,您应该检查它是否存在,并与您期望的内容相匹配

您可以在Google上阅读更多关于ID令牌的信息,包括一些指向示例代码和库的链接,以帮助您解码它们。可以在测试期间解码ID令牌

备选案文2。呼叫用户信息 获得OAuth访问令牌后,执行GET请求https://www.googleapis.com/plus/v1/people/me/openIdConnect 在标头中使用访问令牌。它将返回一个关于用户声明的JSON字典。如果用户是托管域的一部分,将出现hd声明,您应该检查它是否存在,并与您期望的内容相匹配

阅读更多的文章


选项1和选项2之间的主要区别在于,使用ID令牌,您可以避免到服务器的另一个HTTP往返,从而使其速度更快,更不容易出错。您可以使用.

@AMH以交互方式尝试这两个选项,要以最简单的方式进行,您应该创建自己的Google Provider,重写方法ApplyRedirect,并将附加参数(如hd)附加到address,该地址将用于重定向到新的Google auth页面:

public class GoogleAuthProvider : GoogleOAuth2AuthenticationProvider
{
    public override void ApplyRedirect(GoogleOAuth2ApplyRedirectContext context)
    {
        var newRedirectUri = context.RedirectUri;
        newRedirectUri += string.Format("&hd={0}", "your_domain.com");

        context.Response.Redirect(newRedirectUri);
    }
}
之后,只需将新提供商链接到您的选项:

app.UseGoogleAuthentication(new GoogleOAuth2AuthenticationOptions()
{
    ClientId = "your id",
    ClientSecret = "your secret",
    Provider = new GoogleAuthProvider(),
});

@AMH,要以最简单的方式执行此操作,您应该创建自己的Google Provider,重写方法ApplyRedirect,并将附加参数(如hd)附加到address,该参数将用于重定向到新的Google auth页面:

public class GoogleAuthProvider : GoogleOAuth2AuthenticationProvider
{
    public override void ApplyRedirect(GoogleOAuth2ApplyRedirectContext context)
    {
        var newRedirectUri = context.RedirectUri;
        newRedirectUri += string.Format("&hd={0}", "your_domain.com");

        context.Response.Redirect(newRedirectUri);
    }
}
之后,只需将新提供商链接到您的选项:

app.UseGoogleAuthentication(new GoogleOAuth2AuthenticationOptions()
{
    ClientId = "your id",
    ClientSecret = "your secret",
    Provider = new GoogleAuthProvider(),
});
与upda 特德。以前的答案不再适用。幸运的是,在新的实现中,有一种方法可以钩住身份验证事件来执行这样的任务

你需要一个类来处理两个事件——一个是在你去谷歌之前触发的,另一个是回来时触发的。首先,您限制可用于登录的域,然后,您确保具有正确域的电子邮件实际上用于登录:

internal class GoogleAuthEvents : OAuthEvents
{
    private string _domainName;

    public GoogleAuthEvents(string domainName)
    {
        this._domainName = domainName?.ToLower() ?? throw new ArgumentNullException(nameof(domainName));
    }

    public override Task RedirectToAuthorizationEndpoint(OAuthRedirectToAuthorizationContext context)
    {
        return base.RedirectToAuthorizationEndpoint(new OAuthRedirectToAuthorizationContext(
            context.HttpContext,
            context.Options,
            context.Properties,
            $"{context.RedirectUri}&hd={_domainName}"));
    }

    public override Task TicketReceived(TicketReceivedContext context)
    {
        var emailClaim = context.Ticket.Principal.Claims.FirstOrDefault(
                c => c.Type == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress");

        if (emailClaim == null || !emailClaim.Value.ToLower().EndsWith(_domainName))
        {
            context.Response.StatusCode = 403; // or redirect somewhere
            context.HandleResponse();
        }

        return base.TicketReceived(context);
    }
}
然后需要通过GoogleOptions类将此事件处理程序传递给中间件:

与更新的。以前的答案不再适用。幸运的是,在新的实现中,有一种方法可以钩住身份验证事件来执行这样的任务

你需要一个类来处理两个事件——一个是在你去谷歌之前触发的,另一个是回来时触发的。首先,您限制可用于登录的域,然后,您确保具有正确域的电子邮件实际上用于登录:

internal class GoogleAuthEvents : OAuthEvents
{
    private string _domainName;

    public GoogleAuthEvents(string domainName)
    {
        this._domainName = domainName?.ToLower() ?? throw new ArgumentNullException(nameof(domainName));
    }

    public override Task RedirectToAuthorizationEndpoint(OAuthRedirectToAuthorizationContext context)
    {
        return base.RedirectToAuthorizationEndpoint(new OAuthRedirectToAuthorizationContext(
            context.HttpContext,
            context.Options,
            context.Properties,
            $"{context.RedirectUri}&hd={_domainName}"));
    }

    public override Task TicketReceived(TicketReceivedContext context)
    {
        var emailClaim = context.Ticket.Principal.Claims.FirstOrDefault(
                c => c.Type == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress");

        if (emailClaim == null || !emailClaim.Value.ToLower().EndsWith(_domainName))
        {
            context.Response.StatusCode = 403; // or redirect somewhere
            context.HandleResponse();
        }

        return base.TicketReceived(context);
    }
}
然后需要通过GoogleOptions类将此事件处理程序传递给中间件:


我在搜索一个解决方案时发现了这篇文章,该解决方案使用OpenID指定托管域,并将其连接到Google。我能够使用这个包和下面的代码使它工作

在Startup.cs中

services.AddGoogleOpenIdConnect(options =>
  {
    options.ClientId = "*****";
    options.ClientSecret = "*****";
    options.SaveTokens = true;
    options.EventsType = typeof(GoogleAuthenticationEvents);
  });
services.AddTransient(provider => new GoogleAuthenticationEvents("example.com"));
别忘了app.UseAuthentication;在Startup.cs的配置方法中

然后是身份验证事件类

public class GoogleAuthenticationEvents : OpenIdConnectEvents
{
  private readonly string _hostedDomain;

  public GoogleAuthenticationEvents(string hostedDomain)
  {
    _hostedDomain = hostedDomain;
  }

  public override Task RedirectToIdentityProvider(RedirectContext context)
  {
    context.ProtocolMessage.Parameters.Add("hd", _hostedDomain);
    return base.RedirectToIdentityProvider(context);
  }

  public override Task TicketReceived(TicketReceivedContext context)
  {
    var email = context.Principal.FindFirstValue(ClaimTypes.Email);
    if (email == null || !email.ToLower().EndsWith(_hostedDomain))
    {
      context.Response.StatusCode = 403;
      context.HandleResponse();
    }
    return base.TicketReceived(context);
  }
}

我在搜索一个解决方案时发现了这篇文章,该解决方案使用OpenID指定托管域,并将其连接到Google。我能够使用这个包和下面的代码使它工作

在Startup.cs中

services.AddGoogleOpenIdConnect(options =>
  {
    options.ClientId = "*****";
    options.ClientSecret = "*****";
    options.SaveTokens = true;
    options.EventsType = typeof(GoogleAuthenticationEvents);
  });
services.AddTransient(provider => new GoogleAuthenticationEvents("example.com"));
别忘了app.UseAuthentication;在Startup.cs的配置方法中

然后是身份验证事件类

public class GoogleAuthenticationEvents : OpenIdConnectEvents
{
  private readonly string _hostedDomain;

  public GoogleAuthenticationEvents(string hostedDomain)
  {
    _hostedDomain = hostedDomain;
  }

  public override Task RedirectToIdentityProvider(RedirectContext context)
  {
    context.ProtocolMessage.Parameters.Add("hd", _hostedDomain);
    return base.RedirectToIdentityProvider(context);
  }

  public override Task TicketReceived(TicketReceivedContext context)
  {
    var email = context.Principal.FindFirstValue(ClaimTypes.Email);
    if (email == null || !email.ToLower().EndsWith(_hostedDomain))
    {
      context.Response.StatusCode = 403;
      context.HandleResponse();
    }
    return base.TicketReceived(context);
  }
}

您可以附加用于hd参数的文档吗?您是否尝试将&hd=mydomain.com附加到authUrl?@abraham,它是一个在其构造函数中接受Uri的对象请参见代码。我已将参数添加到此Uri。但是,对象必须重建url并删除参数。如果我在浏览器中更改一次url并重新添加它,它将按预期工作。您可以附加用于hd参数的文档吗?您是否尝试将&hd=mydomain.com附加到authUrl?@abraham,它是一个在其构造函数中接受Uri的对象请参见代码。我已将参数添加到此Uri。但是,对象必须重建url并删除参数。如果我在浏览器中更改一次url并重新添加,它将按预期工作。谢谢。非常有用的信息。谢谢。非常有用的信息。如何使用它请我使用类似app.UseGoogleAuthenticationnew GoogleOAuth2AuthenticationOptions{ClientId=ddddddd,ClientSecret=xxxxx};如何使用它请我使用类似app.UseGoogleAuthenticationnew GoogleOAuth2AuthenticationOptions{ClientId=ddddddd,ClientSecret=xxxxx};