Python - 使用 requests_oauthlib 在 Web 服务器中实现 Authentik SSO

问题描述 投票:0回答:1

我正在尝试使用 OpenID Connect 在 Web 应用程序中实现 SSO。

我正在使用什么

  • python 3.12
  • Authentik(身份提供商aka IdP
  • flask(公开网络服务器)
  • requests_oauthlib(处理 OAuth2 会话)

我做了什么

我正在尝试为 Web 应用程序复制 request-oauthlib 中的 example,但没有成功

  1. 在 IdP Authentik 上创建应用程序作为
    OAuth2/OpenID Provider
  2. 使用
    flask
    和 2 个测试端点创建 Web 服务器:重定向到 IdP 并检索(
    /login
    authorization_url
    )的
    state
    以及使用
    /callback
    client_id
    client_secret
    state
     
    authorization_response
    尝试检索访问令牌

不幸的是,当我尝试检索访问令牌时,我收到以下错误:

oauthlib.oauth2.rfc6749.errors.InvalidClientError: (invalid_client) Client authentication failed (e.g., unknown client, no client authentication included, or unsupported authentication method)

代码

import json
import os.path
from uuid import uuid4
from requests_oauthlib import OAuth2Session
from waitress import serve
from flask import Flask, jsonify, request, url_for, redirect, session
from pprint import pprint


with open(os.path.join("Config", "client_secrets.json"), "r") as f:
    idp = json.load(f)

os.environ["OAUTHLIB_INSECURE_TRANSPORT"] = "1"


def main():
    app = Flask(__name__)
    # This allows us to use a plain HTTP callback
    os.environ['OAUTHLIB_INSECURE_TRANSPORT'] = "1"
    app.config['SECRET_KEY'] = str(uuid4())

    @app.route('/')
    def index():
        return """
            <a href="/login">Login</a>
        """

    @app.route("/login")
    def login():
        oauth = OAuth2Session(client_id=idp["client_id"],
                              scope=idp["scope"],
                              redirect_uri=idp["callback"]
                              )
        authorization_url, state = oauth.authorization_url(idp["authorize"])
        session['oauth_state'] = state
        return redirect(authorization_url)

    @app.route("/callback")
    def callback():
        pprint(request.__dict__)
        oauth = OAuth2Session(client_id=idp["client_id"],
                              state=session['oauth_state']
                              )
        # When I try to get the token, nothing works
        token = oauth.fetch_token(
            idp["token"],
            client_secret=idp["client_secret"],
            authorization_response=request.url
        )
        # I never reach this line
        session['oauth_token'] = token
        return "I cannot see this :("

    print("Starting webserver")
    serve(app, host='0.0.0.0', port=5000)
    print("Webserver running")


if __name__ == "__main__":
    main()

回调请求:

{'cookies': ImmutableMultiDict([('session', 'REDACTED_SESSION')]),
 'environ': {'HTTP_ACCEPT': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8',
             'HTTP_ACCEPT_ENCODING': 'gzip, deflate, br',
             'HTTP_ACCEPT_LANGUAGE': 'en-US,en;q=0.5',
             'HTTP_CONNECTION': 'keep-alive',
             'HTTP_COOKIE': 'session=REDACTED_SESSION',
             'HTTP_DNT': '1',
             'HTTP_HOST': 'localhost:5000',
             'HTTP_SEC_FETCH_DEST': 'document',
             'HTTP_SEC_FETCH_MODE': 'navigate',
             'HTTP_SEC_FETCH_SITE': 'cross-site',
             'HTTP_UPGRADE_INSECURE_REQUESTS': '1',
             'HTTP_USER_AGENT': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; '
                                'rv:123.0) Gecko/20100101 Firefox/123.0',
             'PATH_INFO': '/callback',
             'QUERY_STRING': 'code=REDACTED_CODE&state=REDACTED_STATE',
             'REMOTE_ADDR': '127.0.0.1',
             'REMOTE_HOST': '127.0.0.1',
             'REMOTE_PORT': '64951',
             'REQUEST_METHOD': 'GET',
             'REQUEST_URI': '/callback?code=REDACTED_CODE&state=REDACTED_STATE',
             'SCRIPT_NAME': '',
             'SERVER_NAME': 'waitress.invalid',
             'SERVER_PORT': '5000',
             'SERVER_PROTOCOL': 'HTTP/1.1',
             'SERVER_SOFTWARE': 'waitress',
             'waitress.client_disconnected': <bound method HTTPChannel.check_client_disconnected of <waitress.channel.HTTPChannel connected 127.0.0.1:64951 at 0x285f5356ba0>>,
             'werkzeug.request': <Request 'http://localhost:5000/callback?code=REDACTED_CODE&state=REDACTED_STATE' [GET]>,
             'wsgi.errors': <_io.TextIOWrapper name='<stderr>' mode='w' encoding='utf-8'>,
             'wsgi.file_wrapper': <class 'waitress.buffers.ReadOnlyFileBasedBuffer'>,
             'wsgi.input': <_io.BytesIO object at 0x00000285F5391E90>,
             'wsgi.input_terminated': True,
             'wsgi.multiprocess': False,
             'wsgi.multithread': True,
             'wsgi.run_once': False,
             'wsgi.url_scheme': 'http',
             'wsgi.version': (1, 0)},
 'headers': EnvironHeaders([('Host', 'localhost:5000'), ('User-Agent', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:123.0) Gecko/20100101 Firefox/123.0'), ('Accept', 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8'), ('Accept-Language', 'en-US,en;q=0.5'), ('Accept-Encoding', 'gzip, deflate, br'), ('Dnt', '1'), ('Connection', 'keep-alive'), ('Cookie', 'session=REDACTED_SESSION'), ('Upgrade-Insecure-Requests', '1'), ('Sec-Fetch-Dest', 'document'), ('Sec-Fetch-Mode', 'navigate'), ('Sec-Fetch-Site', 'cross-site')]),
 'host': 'localhost:5000',
 'json_module': <flask.json.provider.DefaultJSONProvider object at 0x00000285F5354530>,
 'method': 'GET',
 'path': '/callback',
 'query_string': b'code=REDACTED_CODE&state=REDACTED_'
                 b'_STATE',
 'remote_addr': '127.0.0.1',
 'root_path': '',
 'scheme': 'http',
 'server': ('waitress.invalid', 5000),
 'shallow': False,
 'url': 'http://localhost:5000/callback?code=REDACTED_CODE&state=REDACTED_STATE',
 'url_rule': <Rule '/callback' (GET, OPTIONS, HEAD) -> callback>,
 'view_args': {}}
python flask oauth-2.0 single-sign-on openid-connect
1个回答
0
投票

当我尝试实例化 OAuth2Session 对象时,它没有创建正确的对象:

oauth = OAuth2Session(client_id=idp["client_id"],
                      state=session['oauth_state']
                      ### MISSING PARAMETER: redirect_uri
                     )

解决方案是实例化 OAuth2Session 对象,并在登录和回调中传递相同的参数:

oauth = OAuth2Session(client_id=idp["client_id"],
                      scope=idp["scope"],
                      redirect_uri=idp["callback"]
                     )

非常清楚,这是正确的回调函数:

    @app.route("/callback")
    def callback():
        pprint(request.__dict__)
        oauth = OAuth2Session(client_id=idp["client_id"],
                              state=session['oauth_state'],
                              redirect_uri=idp["callback"]
                              )
        # When I try to get the token, nothing works
        token = oauth.fetch_token(
            idp["token"],
            client_secret=idp["client_secret"],
            authorization_response=request.url
        )
        # I never reach this line
        session['oauth_token'] = token
        return "Ok, everithing is working"
© www.soinside.com 2019 - 2024. All rights reserved.