使用 Keycloak 做为 OIDC 身份提供商与 Bookstack 进行 SSO 集成
SSO integration with Bookstack using Keycloak as OIDC Identity Provider
I did not find the corresponding detailed records in English, if you need, please use Google Translate :D.
身份提供商 Identity Provider (IdP) ,你可以简单理解为中心用户管理、验证系统,如此处的 Keycloak。
服务提供商 Service Provider (SP),实际服务提供者,比如此处的 Bookstack(本站所用程序)。
看起来似乎没用那么难,但还是踩了很多坑的,也没有搜到稍微详细一点的教程,在摸索完后记录一下。
- Keycloak:https://www.keycloak.org/
- Bookstack:https://www.bookstackapp.com/
Keycloak + NGINX 安装
详细请参阅官方文档,此次仅列举一些官方文档没有提到或模糊的地方。
本例使用单机 Keycloak + NGINX 做为反向代理
使用 systemd 管理 Keycloak
我不太喜欢环境变量,于是配置都写在 <keycloak路径>/conf/keycloak.conf
/etc/systemd/system/keycloak.service
[Unit]
Description=Keycloak Open Source Identity and Access Management
After=syslog.target
After=network.target
[Service]
Type=simple
Restart=on-failure
RestartSec=2s
# Disable timeout logic and wait until process is stopped
TimeoutStopSec=0
# SIGTERM signal is used to stop the Java process
KillSignal=SIGTERM
# Send the signal only to the JVM rather than its control group
KillMode=process
# Java process is never killed
SendSIGKILL=no
# When a JVM receives a SIGTERM signal it exits with code 143
SuccessExitStatus=143
LimitMEMLOCK=infinity
LimitNOFILE=65535
User=<keycloak 用户>
Group=<keycloak 组>
WorkingDirectory=<工作目录>
# Database
#Environment=KC_DB=mysql
#Environment=KC_DB_URL_DATABASE={{ kc_db_name |default('keycloak')}}
#Environment=KC_DB_PASSWORD={{ kc_db_pass}}
#Environment=KC_DB_USERNAME={{ kc_db_user }}
#Environment=KC_DB_URL_HOST={{ kc_db_host}}
# Proxy
#Environment=KC_PROXY=edge
#Environment=KC_HOSTNAME_STRICT=false
# HTTPS
#Environment=KC_HTTPS_PORT={{ kc_port }}
## Debian default ssl-cert snakeoil cert
#Environment=KC_HTTPS_CERTIFICATE_FILE={{ kc_cert |default('/etc/ssl/certs/ssl-cert-snakeoil.pem') }}
#Environment=KC_HTTPS_CERTIFICATE_KEY_FILE={{ kc_cert_key |default('/etc/ssl/private/ssl-cert-snakeoil.key') }}
# Default user
Environment=KEYCLOAK_ADMIN=<管理员账号>
Environment=KEYCLOAK_ADMIN_PASSWORD=<管理员密码>
#{% if kc_port |int <= 1024 %}
## Allow ports below 1024.
#CapabilityBoundingSet=CAP_NET_BIND_SERVICE
#AmbientCapabilities=CAP_NET_BIND_SERVICE
#{% endif %}
ExecStart=<keycloak路径>/bin/kc.sh --config-file=<keycloak路径>/conf/keycloak.conf --verbose start --spi-connections-jpa-quarkus-migration-strategy=update
#WARNING: The '--auto-build' option for 'start' command is DEPRECATED and no longer needed. When executing the 'start' command, a new server image is automatically built based on the configuration. If you want to disable this behavior and achieve an optimal startup time, use the '--optimized' option instead.
[Install]
WantedBy=multi-user.target
使用 Mysql 做为数据库,由于使用 NGINX 进行本地反代,所以使用 http 模式就够了 (proxy=edge)。
<keycloak路径>/conf/keycloak.conf
# Basic settings for running in production. Change accordingly before deploying the server.
# Database
# The database vendor.
db=mysql
db-url-host=localhost
# The username of the database user.
db-username=
db-url-database=
# The password of the database user.
db-password=
# The full database JDBC URL. If not provided, a default URL is set based on the selected database vendor.
db-url=jdbc:mysql://localhost:3306/keycloak?useSSL=false&characterEncoding=UTF-8
# Observability
# If the server should expose healthcheck endpoints.
health-enabled=true
# If the server should expose metrics endpoints.
metrics-enabled=false
# HTTP
# Hostname for the Keycloak server.
#hostname=tree.pha.pub
#hostname-strict-https=true
hostname-strict=false
hostname-strict-https=false
https-port=8443
http-port=8444
# The file path to a server certificate or certificate chain in PEM format.
#https-certificate-file=/usr/local/nginx/conf/ssl/2x.pha.pub.crt
# The file path to a private key in PEM format.
#https-certificate-key-file=/usr/local/nginx/conf/ssl/2x.pha.pub.key
#https-protocols=TLSv1.3,TLSv1.2
# The proxy address forwarding mode if the server is behind a reverse proxy.
#proxy=reencrypt
proxy=edge
# Do not attach route to cookies and rely on the session affinity capabilities from reverse proxy
#spi-sticky-session-encoder-infinispan-should-attach-route=false
Java 安装
Keycloak 默认会直接使用 java 环境变量来运行,如果你有多个 java 安装,这可能不是那么稳定。
目前的 Keycloak 21.0.1要求使用 Java 11。
你可以在 <keycloak路径>/bin/kc.sh
中找到下面这几行代码,并将 java 路径修改至固定值。
当然,你也可以修改 JAVA_OPTS=
来自定义内存大小等。
if [ "x$JAVA" = "x" ]; then
if [ "x$JAVA_HOME" != "x" ]; then
JAVA="<keycloak路径>/graalvm-ce-java11-22.3.1/bin/java"
else
JAVA="<keycloak路径>/graalvm-ce-java11-22.3.1/bin/java"
fi
fi
NGINX 设置
https://www.keycloak.org/server/reverseproxy 文档中不建议直接暴露 /
路径,而是建议使用 /auth
等路径,但是说实话没搞太懂,所以我就直接暴露 /
路径了,当然你也可以改一下主页做一下伪装,或者屏蔽掉 /admin 的访问。
nginx-keycloak.conf
upstream keycloakserver {
server 127.0.0.1:9444;
keepalive 120;
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name keycloak.example.com;
#SSL 配置等略
location / {
proxy_pass http://keycloakserver;
proxy_set_header Host $host;
#注意你的真实IP请求头,如果你前面还有 CDN 什么的,需使用 CDN 提供的头
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Server $host;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
Keycloak 设置
使用你在 /etc/systemd/system/keycloak.service 中配置的账号登录。
1.创建 releam(可选)
2.切换到创建的 releam
3.Manage -> Clinet -> Create clinet
4.Next
-
- Client authentication:On
- Authorization:On
- Authentication flow:保持默认
5.Next
-
- Root URL:<你的Bookstack网址>
- Home URL:<你的Bookstack网址>
- Valid redirect URIs:<你的Bookstack网址>/oidc/callback
6.创建完成
7.点击 Credentials
-
- Client Authenticator:Client ID and Secret
- Save
- Client secret:先点击 regenerate 然后复制,等下要用。
8.Keycloak 客户端配置完成
BookStack 设置
https://www.bookstackapp.com/docs/admin/oidc-auth/
根据你的配置,在 bookstack/.env
中添加如下配置:
注:# Issuer URL
实际可以在你的 Keycloak -> Configure -> Realm settings -> Endpoints 处找到。
bookstack/.env
# Set OIDC to be the authentication method
AUTH_METHOD=oidc
#AUTH_METHOD=standard
#AUTH_METHOD=saml2
# Control if BookStack automatically initiates login via your OIDC system
# if it's the only authentication method. Prevents the need for the
# user to click the "Login with x" button on the login page.
# Setting this to true enables auto-initiation.
AUTH_AUTO_INITIATE=false
# Set the display name to be shown on the login button.
# (Login with <name>)
OIDC_NAME='登录提供商名'
# Name of the claims(s) to use for the user's display name.
# Can have multiple attributes listed, separated with a '|' in which
# case those values will be joined with a space.
# Example: OIDC_DISPLAY_NAME_CLAIMS=given_name|family_name
OIDC_DISPLAY_NAME_CLAIMS=preferred_username|username|name
# OAuth Client ID to access the identity provider
OIDC_CLIENT_ID='你刚刚填的Clinet ID'
# OAuth Client Secret to access the identity provider
OIDC_CLIENT_SECRET='你刚刚复制的Clinet secret'
# Issuer URL
# Must start with 'https://'
OIDC_ISSUER=https://<你的keycloak网址>/realms/<领域名>
#你可以访问 https://<你的keycloak网址>/realms/<领域名>/.well-known/openid-configuration 来测试是否正常
# Enable auto-discovery of endpoints and token keys.
# As per the standard, expects the service to serve a
# `<issuer>/.well-known/openid-configuration` endpoint.
OIDC_ISSUER_DISCOVER=true
# Configure a custom ID Token claim to be used as the
# "External Authentication ID" within BookStack.
OIDC_EXTERNAL_ID_CLAIM=sub
#以下是组同步,如果你不需要就先注释,否则无法登录
# Enable OIDC group sync.
OIDC_USER_TO_GROUPS=true
# Set the attribute from which BookStack will read groups names from.
OIDC_GROUPS_CLAIM=realm_access.roles
# Additional scopes to send with the authentication request.
# By default BookStack only sends the 'openid', 'profile' & 'email' scopes.
# Many platforms require specific scopes to be requested for group data.
# Multiple scopes can be added via comma separation.
OIDC_ADDITIONAL_SCOPES=roles
# Remove the user from roles that don't match OIDC groups upon login.
# Note: While this is enabled the "Default Registration Role", editable within the
# BookStack settings view, will be considered a matched role and assigned to the user.
OIDC_REMOVE_FROM_GROUPS=true
# Enable the BookStack general debug mode, Provides more error insight.
# Note, Can leak sensitive details so only use in private, secure environments.
APP_DEBUG=false
# Dump out the details fetched from the identity provider.
# Only set this option to true if debugging since it will block logins
# and potentially show private details.
OIDC_DUMP_USER_DETAILS=false
# Option to override settings passed to the underlying onelogin library
# used by BookStack. For development, debugging and testing only.
# Options provided will be recursively merged into other default & dynamic options.
# Defaults found within app/Config/saml2.php, under the 'onelogin' key.
#SAML2_ONELOGIN_OVERRIDES=<json_format_data>
组/角色同步
1.首先在 bookstack 中创建对应的角色,分配好权限,只能用英文;
2.在你的 keycloak 中创建名称相同的 Groups 和 Realm roles,在组设置的 Role mapping 将角色分配到同名组;
3.在你的 keycloak 中的 Client scopes 中找到 roles -> Mappers -> realm roles;
-
- Add to ID token:On
- Add to access token:On
- Add to userinfo:On
4.在你的 keycloak 中创建用户,并分配到对应的 Group;
5.然后如果你在 bookstack/.env
中设置 OIDC_DUMP_USER_DETAILS=true
然后登录,你应该可以在输出的 JSON 中找到
"realm_access": {
"roles": [
"default-roles-master",
"offline_access",
"uma_authorization",
"你的自定义组"
]
}
6.在 bookstack/.env
中添加如下字段:
OIDC_GROUPS_CLAIM=即指示从哪里获取组名。
# Set the attribute from which BookStack will read groups names from.
OIDC_GROUPS_CLAIM=realm_access.roles
# Additional scopes to send with the authentication request.
# By default BookStack only sends the 'openid', 'profile' & 'email' scopes.
# Many platforms require specific scopes to be requested for group data.
# Multiple scopes can be added via comma separation.
OIDC_ADDITIONAL_SCOPES=roles
# Remove the user from roles that don't match OIDC groups upon login.
# Note: While this is enabled the "Default Registration Role", editable within the
# BookStack settings view, will be considered a matched role and assigned to the user.
OIDC_REMOVE_FROM_GROUPS=true
7.登录后你应该会被自动分配到该组;
主要参考
- https://stackoverflow.com/questions/70974442/map-groups-to-roles-in-keycloak-when-using-oidc
- https://stackoverflow.com/questions/57997377/get-the-user-roles-with-the-keycloak-userinfo-endpoint
- https://keycloak.discourse.group/t/how-to-get-user-roles-in-access-token/3289
- https://github.com/BookStackApp/BookStack/issues/3897
- https://www.bookstackapp.com/docs/admin/oidc-auth/
无评论