如何在 Flask REST API 服务中使用 Keycloak
How to use Keycloak with Flask REST API Service
我正在尝试为我的 Flask Rest 服务实现 Keycloak,但它总是给出以下错误。
{"error": "invalid_token", "error_description": "Token required but invalid"}
client_secrets.json
{
"web": {
"issuer": "http://localhost:18080/auth/realms/Dev-Auth",
"auth_uri": "http://localhost:18080/auth/realms/Dev-Auth/protocol/openid-connect/auth",
"client_id": "flask_api",
"client_secret": "0bff8456-9be2-4f82-884e-c7f9bea65bd1",
"redirect_uris": [
"http://localhost:5001/*"
],
"userinfo_uri": "http://localhost:18080/auth/realms/Dev-Auth/protocol/openid-connect/userinfo",
"token_uri": "http://localhost:18080/auth/realms/Dev-Auth/protocol/openid-connect/token",
"token_introspection_uri": "http://localhost:18080/auth/realms/Dev-Auth/protocol/openid-connect/token/introspect",
"bearer_only": "true"
}
}
run.py
import json
import logging
from flask import Flask, g, jsonify
from flask_oidc import OpenIDConnect
import requests
app = Flask(__name__)
app.config.update({
'SECRET_KEY': 'TESTING-ANURAG',
'TESTING': True,
'DEBUG': True,
'OIDC_CLIENT_SECRETS': 'client_secrets.json',
'OIDC_OPENID_REALM': 'Dev-Auth',
'OIDC_INTROSPECTION_AUTH_METHOD': 'bearer',
'OIDC-SCOPES': ['openid']
})
oidc = OpenIDConnect(app)
@app.route('/api', methods=['GET'])
@oidc.accept_token(require_token=True, scopes_required=['openid'])
def hello_api():
"""OAuth 2.0 protected API endpoint accessible via AccessToken"""
return json.dumps({'hello': 'Welcome %s' % g.oidc_token_info['sub']})
if __name__ == '__main__':
任何人都有想法,如果这里有任何问题。
我遇到了同样的问题,我(终于\o/)解决了这个问题。尝试以下操作:
'OIDC_INTROSPECTION_AUTH_METHOD': 'client_secret_post'
'OIDC_TOKEN_TYPE_HINT': 'access_token'
同时删除所需范围的列表以避免出现任何可能的错误:
@oidc.accept_token(require_token=True)
如果您正尝试访问您的 Rest 服务,例如:
那就不行了,因为没有access token。
您可以做的是访问 http://127.0.0.1:5001/private 并从内部调用 /api 在 header =)
中传递令牌
@app.route('/private')
@oidc.require_login
def hello_me():
info = oidc.user_getinfo(['email', 'openid_id'])
if user_id in oidc.credentials_store:
try:
from oauth2client.client import OAuth2Credentials
access_token = OAuth2Credentials.from_json(oidc.credentials_store[user_id]).access_token
headers = {'Authorization': f'Bearer {access_token}'}
access_like_this = requests.get('http://localhost:5001/api', headers=headers).text
except:
access_like_this = "we failed"
return f'Hello, api: {access_like_this} <a href="/">Return</a>'
else:
return f'Ops, <a href="/">Return</a>'
documentation 说了以下关于 accept_token 装饰器
Tokens are accepted as part of the query URL (access_token value) or a POST form value (access_token).
所以当你有类似
的东西时
@app.route('/api', methods=['GET'])
@oidc.accept_token(require_token=True, scopes_required=['openid'])
def hello_api():
...
您的 GET 请求应该是
https://yourhost/api?access_token=blahblahsomethingquiteopaque
其中 blahblahsomethingquiteopaque 是您的 OAUTH2 访问令牌。
对于 POST api 调用,您必须在数据中提供 access_token key/value。
这是我项目中的一个示例:
@app.route('/api/new_employee', methods=['POST'])
@oidc.accept_token(require_token=True)
def new_employee():
...
API 路由用于提交由该视图呈现的表单
from flask import Flask, g, redirect, request, Response, render_template
from flask_oidc import OpenIDConnect
from oauth2client.client import OAuth2Credentials
def auth(user):
user_id = user.get('sub')
username = user.get('preferred_username')
if user_id in oidc.credentials_store:
access_token = OAuth2Credentials.from_json(oidc.credentials_store[user_id]).access_token
else:
access_token = None
return username, access_token
app = Flask(__name__)
app.config.update({
'SECRET_KEY': 'SomethingNotEntirelySecret',
'TESTING': True,
'DEBUG': True,
'OIDC_CLIENT_SECRETS': 'client_secrets.json',
'OIDC_ID_TOKEN_COOKIE_SECURE': False,
'OIDC_REQUIRE_VERIFIED_EMAIL': False,
'OIDC_USER_INFO_ENABLED': True,
'OIDC_OPENID_REALM': 'flask-demo',
'OIDC_SCOPES': ['openid', 'email', 'profile'],
'OIDC_INTROSPECTION_AUTH_METHOD': 'client_secret_post',
})
oidc = OpenIDConnect(app)
@app.route('/grants/employees')
@oidc.require_login
def grants_employees():
user = oidc.user_getinfo(['preferred_username', 'email', 'sub', 'groups'])
vars['username'], vars['access_token'] = auth(user)
if not vars['access_token']:
oidc.logout()
return redirect(request.url)
return render_template('grants_employees.html', vars=vars)
然后在表单内的 grants_employees.html 模板中,我有一个隐藏字段,其中访问令牌在 vars[[=41=]]
中传递
<input type="hidden" class="form-control" id="inputAccessToken" name="access_token" value="{{ vars.access_token }}" required>
还值得一提的是文档说
Note that this only works if a token introspection url is configured, as that URL will be queried for the validity and scopes of a token.
因此您必须确保您的 client_secrets.json 包含 token_introspection_uri
的正确值
{
"web": {
"issuer": "https://yourkeycloak/auth/realms/master",
"auth_uri": "https://yourkeycloak/auth/realms/master/protocol/openid-connect/auth",
"client_id": "your_client_id",
"client_secret": "your_client_secret",
"redirect_uris": [
"http://127.0.0.1/*"
],
"userinfo_uri": "https://yourkeycloak/auth/realms/master/protocol/openid-connect/userinfo",
"token_uri": "https://yourkeycloak/auth/realms/master/protocol/openid-connect/token",
"token_introspection_uri": "https://yourkeycloak/auth/realms/master/protocol/openid-connect/token/introspect"
}
}
我正在尝试为我的 Flask Rest 服务实现 Keycloak,但它总是给出以下错误。
{"error": "invalid_token", "error_description": "Token required but invalid"}
client_secrets.json
{
"web": {
"issuer": "http://localhost:18080/auth/realms/Dev-Auth",
"auth_uri": "http://localhost:18080/auth/realms/Dev-Auth/protocol/openid-connect/auth",
"client_id": "flask_api",
"client_secret": "0bff8456-9be2-4f82-884e-c7f9bea65bd1",
"redirect_uris": [
"http://localhost:5001/*"
],
"userinfo_uri": "http://localhost:18080/auth/realms/Dev-Auth/protocol/openid-connect/userinfo",
"token_uri": "http://localhost:18080/auth/realms/Dev-Auth/protocol/openid-connect/token",
"token_introspection_uri": "http://localhost:18080/auth/realms/Dev-Auth/protocol/openid-connect/token/introspect",
"bearer_only": "true"
}
}
run.py
import json
import logging
from flask import Flask, g, jsonify
from flask_oidc import OpenIDConnect
import requests
app = Flask(__name__)
app.config.update({
'SECRET_KEY': 'TESTING-ANURAG',
'TESTING': True,
'DEBUG': True,
'OIDC_CLIENT_SECRETS': 'client_secrets.json',
'OIDC_OPENID_REALM': 'Dev-Auth',
'OIDC_INTROSPECTION_AUTH_METHOD': 'bearer',
'OIDC-SCOPES': ['openid']
})
oidc = OpenIDConnect(app)
@app.route('/api', methods=['GET'])
@oidc.accept_token(require_token=True, scopes_required=['openid'])
def hello_api():
"""OAuth 2.0 protected API endpoint accessible via AccessToken"""
return json.dumps({'hello': 'Welcome %s' % g.oidc_token_info['sub']})
if __name__ == '__main__':
任何人都有想法,如果这里有任何问题。
我遇到了同样的问题,我(终于\o/)解决了这个问题。尝试以下操作:
'OIDC_INTROSPECTION_AUTH_METHOD': 'client_secret_post'
'OIDC_TOKEN_TYPE_HINT': 'access_token'
同时删除所需范围的列表以避免出现任何可能的错误:
@oidc.accept_token(require_token=True)
如果您正尝试访问您的 Rest 服务,例如:
那就不行了,因为没有access token。
您可以做的是访问 http://127.0.0.1:5001/private 并从内部调用 /api 在 header =)
中传递令牌 @app.route('/private')
@oidc.require_login
def hello_me():
info = oidc.user_getinfo(['email', 'openid_id'])
if user_id in oidc.credentials_store:
try:
from oauth2client.client import OAuth2Credentials
access_token = OAuth2Credentials.from_json(oidc.credentials_store[user_id]).access_token
headers = {'Authorization': f'Bearer {access_token}'}
access_like_this = requests.get('http://localhost:5001/api', headers=headers).text
except:
access_like_this = "we failed"
return f'Hello, api: {access_like_this} <a href="/">Return</a>'
else:
return f'Ops, <a href="/">Return</a>'
documentation 说了以下关于 accept_token 装饰器
Tokens are accepted as part of the query URL (access_token value) or a POST form value (access_token).
所以当你有类似
的东西时@app.route('/api', methods=['GET'])
@oidc.accept_token(require_token=True, scopes_required=['openid'])
def hello_api():
...
您的 GET 请求应该是
https://yourhost/api?access_token=blahblahsomethingquiteopaque
其中 blahblahsomethingquiteopaque 是您的 OAUTH2 访问令牌。
对于 POST api 调用,您必须在数据中提供 access_token key/value。
这是我项目中的一个示例:
@app.route('/api/new_employee', methods=['POST'])
@oidc.accept_token(require_token=True)
def new_employee():
...
API 路由用于提交由该视图呈现的表单
from flask import Flask, g, redirect, request, Response, render_template
from flask_oidc import OpenIDConnect
from oauth2client.client import OAuth2Credentials
def auth(user):
user_id = user.get('sub')
username = user.get('preferred_username')
if user_id in oidc.credentials_store:
access_token = OAuth2Credentials.from_json(oidc.credentials_store[user_id]).access_token
else:
access_token = None
return username, access_token
app = Flask(__name__)
app.config.update({
'SECRET_KEY': 'SomethingNotEntirelySecret',
'TESTING': True,
'DEBUG': True,
'OIDC_CLIENT_SECRETS': 'client_secrets.json',
'OIDC_ID_TOKEN_COOKIE_SECURE': False,
'OIDC_REQUIRE_VERIFIED_EMAIL': False,
'OIDC_USER_INFO_ENABLED': True,
'OIDC_OPENID_REALM': 'flask-demo',
'OIDC_SCOPES': ['openid', 'email', 'profile'],
'OIDC_INTROSPECTION_AUTH_METHOD': 'client_secret_post',
})
oidc = OpenIDConnect(app)
@app.route('/grants/employees')
@oidc.require_login
def grants_employees():
user = oidc.user_getinfo(['preferred_username', 'email', 'sub', 'groups'])
vars['username'], vars['access_token'] = auth(user)
if not vars['access_token']:
oidc.logout()
return redirect(request.url)
return render_template('grants_employees.html', vars=vars)
然后在表单内的 grants_employees.html 模板中,我有一个隐藏字段,其中访问令牌在 vars[[=41=]]
中传递<input type="hidden" class="form-control" id="inputAccessToken" name="access_token" value="{{ vars.access_token }}" required>
还值得一提的是文档说
Note that this only works if a token introspection url is configured, as that URL will be queried for the validity and scopes of a token.
因此您必须确保您的 client_secrets.json 包含 token_introspection_uri
的正确值{
"web": {
"issuer": "https://yourkeycloak/auth/realms/master",
"auth_uri": "https://yourkeycloak/auth/realms/master/protocol/openid-connect/auth",
"client_id": "your_client_id",
"client_secret": "your_client_secret",
"redirect_uris": [
"http://127.0.0.1/*"
],
"userinfo_uri": "https://yourkeycloak/auth/realms/master/protocol/openid-connect/userinfo",
"token_uri": "https://yourkeycloak/auth/realms/master/protocol/openid-connect/token",
"token_introspection_uri": "https://yourkeycloak/auth/realms/master/protocol/openid-connect/token/introspect"
}
}