限制使用Google OAuth2.0登录的电子邮件只允许特定域名

101

我似乎找不到任何关于如何限制我的网页应用程序(使用OAuth2.0和Google API)只接受来自特定域名或一组域名的用户认证请求的文档。我想要白名单而不是黑名单。

是否有人可以提出如何做到这一点的建议,官方认可方法的文档,或者一个简单,安全的解决办法?

为了记录,除非他们通过 Google 的 OAuth 认证尝试登录,否则我不知道任何有关用户的信息。我只收到基本的用户信息和电子邮件地址。


3
我也在进行这方面的研究。我有一个应用程序,希望只能由在我们的 Google 商业应用程序域上拥有帐户的人访问。Google 的 OpenID 实现可能更适合我们双方使用...... - Aaron Bruce
1
我该如何使用Google SDK和C#实现域用户登录? - user1021583
1
请问有人能看一下这个问题吗?https://dev59.com/CFsX5IYBdhLWcg3wHcbA - user4391541
1
请注意,我在那个问题上设置了一个开放的赏金,请有人帮助我。 - user4391541
6个回答

45

我为你准备了一个答案。在OAuth请求中,你可以添加 hd=example.com ,这将限制身份验证的用户来自该域名(我不知道是否可以使用多个域)。你可以在这里找到有关hd参数的文档。

我正在使用这里的Google API库:http://code.google.com/p/google-api-php-client/wiki/OAuth2,所以我需要手动编辑/auth/apiOAuth2.php文件如下:

public function createAuthUrl($scope) {
    $params = array(
        'response_type=code',
        'redirect_uri=' . urlencode($this->redirectUri),
        'client_id=' . urlencode($this->clientId),
        'scope=' . urlencode($scope),
        'access_type=' . urlencode($this->accessType),
        'approval_prompt=' . urlencode($this->approvalPrompt),
        'hd=example.com'
    );

    if (isset($this->state)) {
        $params[] = 'state=' . urlencode($this->state);
    }
    $params = implode('&', $params);
    return self::OAUTH2_AUTH_URL . "?$params";
}

我仍在开发这个应用程序,并发现这个链接可能是对这个问题更正确的答案。 https://developers.google.com/google-apps/profiles/


37
重要提示:尽管您在createAuthUrl函数中指定了hd参数,但仍需要验证用户是否使用您的域名电子邮件地址登录。很容易更改链接参数以允许所有电子邮件地址,并随后获得对您的应用程序的访问权限。 - VictorKilo
我曾经进行过类似的黑客攻击,以设置loginHint(用户电子邮件),如果其中包含托管域,则会重定向到SAML登录。不幸的是,我没有先找到这个线程,也不知道hd参数的存在。http://stackoverflow.com/questions/19653680/google-api-allow-connections-only-with-google-apps-domain/19691368#19691368 - HdN8
hd参数对我来说完全没有任何作用。 - Tyguy7
1
有关hd参数用法的Google文档,请参见https://developers.google.com/identity/work/it-apps,`hd` URI参数的参考资料可在https://developers.google.com/identity/protocols/OpenIDConnect#authenticationuriparameters找到。简而言之,`hd`参数应被视为基于域的显示过滤器,用于Google身份验证方面,但仍应在您的方面进行验证。 - Will B.
2
很好,目前在“hd”参数中,我只能限制一个域名,那么如果我想限制两个或三个域名怎么办? - Jay Patel
显示剩余5条评论

13

客户端:

使用auth2初始化函数,您可以传递hosted_domain参数来限制登录弹出窗口中列出的帐户仅匹配您的hosted_domain。您可以在此文档中查看:https://developers.google.com/identity/sign-in/web/reference

服务器端:

即使使用受限客户端列表,您仍需要验证id_token是否与您指定的托管域匹配。对于某些实现,这意味着检查从Google验证令牌后收到的hd属性。

全栈示例:

Web 代码:

gapi.load('auth2', function () {
    // init auth2 with your hosted_domain
    // only matching accounts will show up in the list or be accepted
    var auth2 = gapi.auth2.init({
        client_id: "your-client-id.apps.googleusercontent.com",
        hosted_domain: 'your-special-domain.example'
    });

    // setup your signin button
    auth2.attachClickHandler(yourButtonElement, {});

    // when the current user changes
    auth2.currentUser.listen(function (user) {
        // if the user is signed in
        if (user && user.isSignedIn()) {
            // validate the token on your server,
            // your server will need to double check that the
            // `hd` matches your specified `hosted_domain`;
            validateTokenOnYourServer(user.getAuthResponse().id_token)
                .then(function () {
                    console.log('yay');
                })
                .catch(function (err) {
                    auth2.then(function() { auth2.signOut(); });
                });
        }
    });
});

服务器端代码(使用Google的Node.js库):

如果你不使用Node.js,你可以在这里查看其他示例:https://developers.google.com/identity/sign-in/web/backend-auth

const GoogleAuth = require('google-auth-library');
const Auth = new GoogleAuth();
const authData = JSON.parse(fs.readFileSync(your_auth_creds_json_file));
const oauth = new Auth.OAuth2(authData.web.client_id, authData.web.client_secret);

const acceptableISSs = new Set(
    ['accounts.google.com', 'https://accounts.google.com']
);

const validateToken = (token) => {
    return new Promise((resolve, reject) => {
        if (!token) {
            reject();
        }
        oauth.verifyIdToken(token, null, (err, ticket) => {
            if (err) {
                return reject(err);
            }
            const payload = ticket.getPayload();
            const tokenIsOK = payload &&
                  payload.aud === authData.web.client_id &&
                  new Date(payload.exp * 1000) > new Date() &&
                  acceptableISSs.has(payload.iss) &&
                  payload.hd === 'your-special-domain.example';
            return tokenIsOK ? resolve() : reject();
        });
    });
};

9

1
这可以很容易地被规避,从而允许使用其他域名登录。它只能限制向用户显示的可用帐户。 - homaxto

2
这是我在node.js中使用passport所做的事情。`profile`是试图登录的用户。
//passed, stringified email login
var emailString = String(profile.emails[0].value);
//the domain you want to whitelist
var yourDomain = '@google.com';
//check the x amount of characters including and after @ symbol of passed user login.
//This means '@google.com' must be the final set of characters in the attempted login 
var domain = emailString.substr(emailString.length - yourDomain.length);

//I send the user back to the login screen if domain does not match 
if (domain != yourDomain)
   return done(err);

然后,只需创建逻辑以查找多个域而不仅仅是一个。我相信这种方法是安全的,因为:1. '@'符号不是电子邮件地址的第一部分或第二部分中的有效字符。我无法通过创建像mike@fake@google.com这样的电子邮件地址来欺骗函数。2. 在传统的登录系统中,我可以这样做,但这个电子邮件地址在Google中永远不存在。如果它不是有效的Google帐户,则无法登录。


2
自 2015 年以来,该库中就有一个函数用于设置此项,而不需要像 aaron-bruce 的解决方法中那样编辑库的源代码。
在生成 URL 之前,只需调用 setHostedDomain 即可针对您的 Google 客户端设置。
$client->setHostedDomain("HOSTED DOMAIN")

0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接