应该可以将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;
}
}
答案 0 :(得分:3)
传递hd
参数确实是将用户限制在域中的正确方法。但是,您验证非常重要,即用户确实属于该托管域。我在您的answer中看到您已经知道如何将此参数添加回您的请求中,因此我将解决此问题的第二部分。
问题是用户可以在其客户端中实际修改请求的URL并删除hd
参数!因此,尽管将此参数传递给用户的最佳用户界面是件好事,但您还需要验证经过身份验证的用户确实属于该域。
要查看用户所属的 Google Apps for Work 域(如果有),您必须在您授权的范围列表中包含email
。然后,执行以下操作之一:
当您为访问令牌交换代码时,令牌端点还将在id_token
参数中返回ID令牌(假设您在请求中包含身份范围,例如email
)。如果用户是托管域的一部分,则会出现hd
声明,您应该检查它是否存在,并且符合您的预期。
您可以在Google的OpenID Connect docs上阅读有关ID令牌的更多信息(包括示例代码和库的一些链接,以帮助您解码它们)。 This tool可以在测试期间解码ID令牌。
获得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);
}
}