将用户授权限制在我的Google域中

时间:2015-03-19 17:16:51

标签: asp.net-mvc google-api google-oauth google-api-dotnet-client

应该可以将Google API OAuth2请求限制为特定的Google域。过去可能会通过黑客攻击&hd=mydomain.com来处理请求。使用新的MVC auth东西似乎不再可能。有什么想法吗?

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

5 个答案:

答案 0 :(得分:3)

传递hd参数确实是将用户限制在域中的正确方法。但是,您验证非常重要,即用户确实属于该托管域。我在您的answer中看到您已经知道如何将此参数添加回您的请求中,因此我将解决此问题的第二部分。

问题是用户可以在其客户端中实际修改请求的URL并删除hd参数!因此,尽管将此参数传递给用户的最佳用户界面是件好事,但您还需要验证经过身份验证的用户确实属于该域。

要查看用户所属的 Google Apps for Work 域(如果有),您必须在您授权的范围列表中包含email。然后,执行以下操作之一:

选项1.验证ID令牌。

当您为访问令牌交换代码时,令牌端点还将在id_token参数中返回ID令牌(假设您在请求中包含身份范围,例如email)。如果用户是托管域的一部分,则会出现hd声明,您应该检查它是否存在,并且符合您的预期。

您可以在Google的OpenID Connect docs上阅读有关ID令牌的更多信息(包括示例代码和库的一些链接,以帮助您解码它们)。 This tool可以在测试期间解码ID令牌。

选项2.调用UserInfo

获得OAuth访问令牌后,请在标头中使用访问令牌向GET执行https://www.googleapis.com/plus/v1/people/me/openIdConnect请求。它将返回有关用户的声明的JSON字典。如果用户是托管域的一部分,则会出现hd声明,您应该检查它是否存在,并且符合您的预期。

documentation for Google's UserInfo endpoint中阅读更多内容。

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

答案 1 :(得分:3)

@AMH, to do in simplest way you should create your own Google Provider, override method ApplyRedirect and append additional parameter like hd to address which will be using to redirect to a new google auth page:

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

After that just link new provider to your options:

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

答案 2 :(得分:3)

使用更新后的。NET core package以前的答案不再合适。幸运的是,在新的实现中,有一种方法可以挂钩身份验证事件来执行此类任务。

你需要一个能够处理2个事件的课程 - 一个在你去谷歌之前被解雇的事件和一个用于回来的事件。首先,您可以限制可以使用哪个域进行登录,然后在第二个中确保具有正确域的电子邮件实际上用于登录:

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类来访问中间件:

app.UseGoogleAuthentication(new GoogleOptions
{
    Events = new GoogleAuthEvents(Configuration["Authentication:Google:LimitToDomain"])
})

答案 3 :(得分:1)

下载了源代码后,我能够看到将请求对象子类化并添加自定义参数很容易:

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

答案 4 :(得分:0)

我在搜索解决方案时发现了这篇文章,该解决方案指定了通过OpenID Connect与Google集成的托管域。我可以使用Google.Apis.Auth.AspNetCore软件包和以下代码来使其工作。

在Startup.cs

services.AddGoogleOpenIdConnect(options =>
  {
    options.ClientId = "*****";
    options.ClientSecret = "*****";
    options.SaveTokens = true;
    options.EventsType = typeof(GoogleAuthenticationEvents);
  });
services.AddTransient(provider => new GoogleAuthenticationEvents("example.com"));

在Startup.cs的app.UseAuthentication();方法中不要忘记Configure()

然后是身份验证事件类

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