我正在尝试使用 OpenID Connect 在 Web 应用程序中实现 SSO。
我正在尝试为 Web 应用程序复制 request-oauthlib 中的 example,但没有成功
OAuth2/OpenID Provider
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': {}}
当我尝试实例化 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"