本篇主要介绍superset如何整合单点登陆系统keycloak,现在网上的博客大部分都是失效了,这里我相当于更新一下,避免大家再走弯路
一、环境配置
Macos
keycloak:18.0.0
superset:2.1.0
keycloak规定:每一个要接入keycloak的三方系统必须要有一个client与之相对应,所以上来就要创建一个,假设名字为superset
二、代码配置
这部分内容在网上大部分都是过期的,基于网上的博客,进行部分依赖库的修改,这里是用macos进行开发,环境部署在linux docker容器中,以下是我修改的文件列表:
下面分别一个一个将代码进行展示出来:
2.1、Makefile
主要增加环境变量,指定PYTHONPATH所在目录,目的是后面增加文件后能找到变量声明
# 环境变量配置,env,在Makefile 开头附近加上就可以
CUR_DIR:=$(shell pwd)
export PYTHONPATH=$(CUR_DIR)/superset
2.2、base.txt & development.txt
在base.txt增加依赖:
Flask-OpenID==1.3.0
flask-oidc-ext==1.4.5
在development.txt修改依赖:
requests==2.26.0 ==> requests==2.28.2
2.3、superset/config.py
修改AUTH_TYPE = AUTH_DB ==> AUTH_TYPE = AUTH_OID
2.4、新增superset/client_secret.json
{
"web": {
"issuer": "https://sso.test.com/auth/realms/master",
"auth_uri":"https://sso.test.com/auth/realms/master/protocol/openid-connect/auth",
"client_id": "superset",
"client_secret": "这个key是从keycloak中web界面里面有提供",
"verify_ssl_server": false,
"post_logout_redirect_uri": "http://superset.test.com/",
"userinfo_uri":"https://sso.test.com/auth/realms/master/protocol/openid-connect/userinfo",
"token_uri":"https://sso.test.com/auth/realms/master/protocol/openid-connect/token",
"token_introspection_uri":"https://sso.test.com/auth/realms/master/protocol/openid-connect/token/introspect"
}
}
2.5、新增superset/superset_config.py
import os
from superset.keycloak_sso import OIDCSecurityManager
#---------------------------------------------------------
# Flask App Builder configuration
#---------------------------------------------------------
# Your App secret key,这个地方SECRET_KEY,需要是随机字符串,代表当前superset app
# 此处的key,和上文中client key不一样
SECRET_KEY='IKyZMVz0hz+Uq097CmD22ghww8oxYvot4yFj2dy3'
OIDC_CLIENT_SECRETS = os.path.dirname(os.path.realpath(__file__)) + '/client_secret.json'
OIDC_ID_TOKEN_COOKIE_SECURE = False
OIDC_REQUIRE_VERIFIED_EMAIL = False
AUTH_USER_REGISTRATION = True
AUTH_USER_REGISTRATION_ROLE = 'Public'
CUSTOM_SECURITY_MANAGER = OIDCSecurityManager
OVERWRITE_REDIRECT_URI = 'http://superset.test.com/oidc_callback'
2.6、新增superset/keycloak_sso.py
import logging
import json
from flask_appbuilder.security.manager import AUTH_OID
from superset.security import SupersetSecurityManager
from flask_appbuilder.security.views import AuthOIDView
from flask_login import login_user
from urllib.parse import quote
from flask_appbuilder.views import expose
from flask import request, redirect
from flask_oidc_ext import OpenIDConnect
from oauth2client.client import OAuth2Credentials
logger = logging.getLogger(__name__)
class OIDCSecurityManager(SupersetSecurityManager):
def __init__(self, appbuilder):
super(OIDCSecurityManager, self).__init__(appbuilder)
if self.auth_type == AUTH_OID:
self.oid = OpenIDConnect(self.appbuilder.get_app)
self.authoidview = AuthOIDCView
class AuthOIDCView(AuthOIDView):
@expose('/login/', methods=['GET', 'POST'])
def login(self, flag=True):
sm = self.appbuilder.sm
oidc = sm.oid
superset_roles = ["Admin", "Alpha", "Gamma", "Public", "granter", "sql_lab"]
default_role = "Gamma"
@self.appbuilder.sm.oid.require_login
def handle_login():
user = sm.auth_user_oid(oidc.user_getfield('email'))
if user is None:
info = oidc.user_getinfo(
['preferred_username', 'given_name', 'family_name', 'email', 'roles'])
roles = [role for role in superset_roles if
role in info.get('roles', [])]
roles += [default_role, ] if not roles else []
user = sm.add_user(info.get('preferred_username'),
info.get('given_name', ''),
info.get('family_name', ''),
info.get('email'),
[sm.find_role(role) for role in roles])
login_user(user, remember=False)
return redirect(self.appbuilder.get_url_for_index)
return handle_login()
@expose('/logout/', methods=['GET', 'POST'])
def logout(self):
try:
oidc = self.appbuilder.sm.oid
info = oidc.user_getinfo(
['preferred_username', 'email', 'sub', 'given_name', 'iss'])
id_token_jwt = OAuth2Credentials.from_json(
oidc.credentials_store[info.get('sub')]).id_token_jwt
oidc.logout()
super(AuthOIDCView, self).logout()
logoutUri = oidc.client_secrets.get('issuer') + '/protocol/openid-connect/logout'
post_logout_redirect_uri = oidc.client_secrets.get('post_logout_redirect_uri')
return redirect(logoutUri + '?id_token_hint=' + id_token_jwt + '&post_logout_redirect_uri=' + \
quote(post_logout_redirect_uri))
except Exception as err:
msg = repr(err)
if msg.find("User was not authenticated") != -1:
return redirect('/login/')
raise
三、linux docker环境修改
上面介绍的内容是在本地直接运行,如果在docker中需要将superset_config.py文件合并到superset/config.py中,主要原因是环境变量问题。所以总体来说,superset_config.py文件没有用的。