如何在 Flask WebApp 中限制 Google 登录(Oauth2)只允许特定 Google 应用域的电子邮件登录?

7

我正在为公司开发一个Flask应用程序(Python3 / Heroku),并成功实现了基于brijieshb42的文章使用requests_oauthlib的Google登录(Oauth2)。

研究表明,如果在授权URL中传递参数“hd”(托管域),它应该能起作用。例如:

https://accounts.google.com/o/oauth2/auth?response_type=code&client_id=OUR_CLIENT_ID&redirect_uri=https%3A%2F%2FOUR_APP.herokuapp.com%2Fconnect&scope=profile+email&state=STATE&hd=our_google_apps_domain.com&access_type=offline

据我理解,该参数应该提供客户端限制,并且仅允许从我们的Google应用程序域中的电子邮件进行登录(服务器端在此之后处理!)基于Google文档, 这个邮件列表帖子以及这些stackoverflow 帖子:post1, post2

然而,尽管我的代码生成了上面粘贴的授权URL,但我仍然可以使用我的个人Gmail帐户(@gmail.com与@our apps domain.com不同)登录。

有人能解释一下为什么这不起作用吗?或者提供一个不同的方法?基本上希望防止非员工登录。

如果需要,我可以分享代码,但基本上是从brijeshb42文章中复制的,看起来像这样:

OAuth2Session(
  OUR_CLIENT_ID,
  redirect_uri=https://OUR_APP.herokuapp.com/connect,
  scope=['profile', 'email']).authorization_url(
      https://accounts.google.com/o/oauth2/auth,
      hd='our_google_apps_domain.com',
      access_type='offline')

返回我上面粘贴的认证URL!


如果我理解正确:在生成授权URL之前,您的应用程序应该检查电子邮件是否在员工电子邮件列表中。如果不是,则不希望他们登录。 - Cătălin Matei
请纠正我如果我错了,但是我的理解是oauth2流程中,直到用户登录/被认证后,你才会真正收到电子邮件,因为他们将电子邮件输入Google登录页面,然后你会根据授权流程收到带有电子邮件的响应,例如(抱歉,来自brijesh文章/与我相同的糟糕格式):google = get_google_auth(token=token) resp = google.get(Auth.USER_INFO) if resp.status_code == 200: user_data = resp.json() email = user_data['email'] - danchow
你说得对,你事先不知道电子邮件。我的建议是,无论Google说什么,也要在你的应用程序中检查用户的电子邮件。你必须将USER_INFO与你的应用程序中的用户匹配,所以如果你没有一个活跃的用户来匹配它,那么身份验证应该会失败。 - Cătălin Matei
hd参数仍然很有用,因为Google的认证系统可以自动选择正确的用户,并跳过认证过程中的一步。 - Chris
哦,我需要重新测试一下 - 我之前使用过 hd 参数(1.5年前),但是没有效果 - 或许现在会有用了? - danchow
2个回答

2

成功验证后,您必须自己检查提供的电子邮件。我已经添加了来自您参考文章的代码片段。我在评论之后添加了所需的额外检查。

@app.route('/gCallback')
def callback():
    # Redirect user to home page if already logged in.
    if current_user is not None and current_user.is_authenticated():
        return redirect(url_for('index'))
    if 'error' in request.args:
        if request.args.get('error') == 'access_denied':
            return 'You denied access.'
        return 'Error encountered.'
    if 'code' not in request.args and 'state' not in request.args:
        return redirect(url_for('login'))
    else:
        # Execution reaches here when user has
        # successfully authenticated our app.
        google = get_google_auth(state=session['oauth_state'])
        try:
            token = google.fetch_token(
                Auth.TOKEN_URI,
                client_secret=Auth.CLIENT_SECRET,
                authorization_response=request.url)
        except HTTPError:
            return 'HTTPError occurred.'
        google = get_google_auth(token=token)
        resp = google.get(Auth.USER_INFO)
        if resp.status_code == 200:
            user_data = resp.json()
            email = user_data['email']
            """
            Your Domain specific check will come here.
            """
            if email.split('@')[1] != 'domain.com':
                flash('You cannot login using this email', 'error')
                return redirect(url_for('login'))
            user = User.query.filter_by(email=email).first()
            if user is None:
                user = User()
                user.email = email
            user.name = user_data['name']
            print(token)
            user.tokens = json.dumps(token)
            user.avatar = user_data['picture']
            db.session.add(user)
            db.session.commit()
            login_user(user)
            return redirect(url_for('index'))
        return 'Could not fetch your information.'

谢谢Brijesh - 我现在就采用这个方案了。也许可以把这个作为答案接受。但我会保持未回答状态一两周,因为现在我的问题是:1.如果不是客户端域限制,hd参数是做什么的?2.有没有办法进行客户端限制。关于第二点的澄清 - 我并不想用客户端检查来替换服务器端检查(显然不安全),但如果有一个门控机制来避免人们使用个人电子邮件登录、授权后被拒绝登录的情况,那将是很好的。这不是为了安全,而是为了用户体验。 - danchow

1

哦,你的意思是hd参数将限制只有gmail域名的用户登录而不包括公司域名?以前好像没有这个问题。我最终通过检查域名并拒绝授权来解决了这个问题,因为hd参数似乎没有起作用。我需要再次测试以进行验证。 - danchow
我还没有使用hd ='gmail.com'进行测试(这对任何人都可以创建Gmail帐户并不是很有用),但是在hd ='corpdomain.com'的情况下,它将仅限制登录该域。您仍应该在服务器端检查域名,因为可能会有人可以伪造来自Gmail帐户的oauth过程。 - Chris

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