无法从 Django 应用程序代码内部从 keycloak IDP 注销。所有 stackoverflow 的答案都对我不起作用(大多数是针对所涉及组件的旧版本),keycloak 文档也是如此。
最近我们为基于 Django 的网站实现了基于 keycloak 的身份验证。适用于身份验证。该应用程序由 docker 构建,三个容器:8000 端口上的网站,keycloak db(postgres 映像),8080 端口上的 keycloak(jboss/keycloak 映像)。
现在我必须为其添加“注销”功能,这意味着从我的 Django 代码中退出 keycloak,并将用户重定向回 keycloak 登录屏幕。
我的设置:
Django 2.2
Python 3.5
keycloak 7
social-auth-app-django 3.1.0
social-auth-core 3.2.0
keycloak 文档表明了这一点:
POST /{realm}/users/{id}/logout
带有用户 ID 和域名。
这只会产生 404,所以我切换到这种模式(喜欢 stackoverflow):POST /auth/realms/acc/protocol/openid-connect/logout
这将返回 403 和错误:“Forbidden (CSRF cookie not set.): /{realm name}/users/{user id}/logout/”
我设法解决了(见下面的更新)
网址.py
urlpaterns = [
....
url(r'logout/$', logout_view, name='logout'),
...
]
视图.py
from django.http import HttpResponseRedirect
import requests
def logout_view(request):
url = '%s<realm name>/users/<user id>/logout/' % settings.LOGOUT_REDIRECT_URL
r = requests.post(url, data = {})
#r.status_code at this point is 403
#also tried another sugestion:
url = '%sauth/realms/%s/protocol/openid-connect/logout/' % (settings.LOGOUT_REDIRECT_URL, '<some realm name>')
r = requests.post(url, data = {})
#r.status_code at this point is 403
#also tried with refresh_token and client_id:
url = "http://<website ip>:8000/<realm name>/users/<user id>/logout/"
refresh_token = '<some refresh token pulled out from stout>'
r = requests.post(url, data = {'refresh_token': refresh_token, 'client_id': '<user id>'})
#for 'client_id' above tried both user id and user name
#r.status_code at this point is also 403
return HttpResponseRedirect("http://<website ip>:8000")
这让我回到了我网站的首页,我仍然登录并且可以正常浏览该网站。此时点击 keycloak 登录屏幕会将我返回到已经登录的网站首页,因此我仍然通过了身份验证。在 Django 代码中的 POST 之前终止会话不会改变任何内容。基本上,当我使用 keycloak 进行身份验证时,websste 或 keycloak 都不会注销。Keycloak 会随着时间的推移而过期,因此 realm.json 设置有效。
我确实尝试在我的 settings.py 中设置它:
CSRF_COOKIE_SECURE = False
SESSION_COOKIE_SECURE = False
..但没有效果。
那么,我在哪里可以找到从 Keycloak 注销用户的工作 Django 代码示例?谢谢
更新 难题的第一部分已解决(阅读Django CSRF Cookie Not Set,Евгений Смирнов和类似帖子的答案):为了摆脱“CSRF cookie not set”错误,我必须做一件简单的事情:
from django.views.decorators.csrf import csrf_exempt
并为我的注销视图添加装饰器:
@csrf_exempt
def logout_view(request):
..........
之后注销请求被执行并返回:
[org.keycloak.events] (default task-12) type=LOGOUT_ERROR, realmId=acclims, clientId=63cb82ff-17f4-4f2d-95e1-a8b3c742e28b, userId=null, ipAddress=172.22.0.1, error=invalid_client_credentials
status_code: 400
我猜我没有发回正确的 refresh_token,而且我不知道如何测试这种怀疑。
-------------------------------------------------- -----
其他部分(可能无关紧要):
设置.py
.....
LOGIN_URL = SCRIPT_NAME + '/login/keycloak'
#yes, I tried this:
CSRF_COOKIE_SECURE = False
SESSION_COOKIE_SECURE = False
SOCIAL_AUTH_KEYCLOAK_KEY = '<some keycloak key>'
SOCIAL_AUTH_KEYCLOAK_SECRET = 'some keycloak secret'
SOCIAL_AUTH_KEYCLOAK_PUBLIC_KEY = 'some public key'
SOCIAL_AUTH_KEYCLOAK_AUTHORIZATION_URL = 'http://<some ip>:8080/auth/realms/<some realm>/protocol/openid-connect/auth'
SOCIAL_AUTH_KEYCLOAK_ACCESS_TOKEN_URL = 'http://<some ip>:8080/auth/realms/<some realm>/protocol/openid-connect/token'
SOCIAL_AUTH_STRATEGY = 'social_django.strategy.DjangoStrategy'
SOCIAL_AUTH_STORAGE = 'social_django.models.DjangoStorage'
SOCIAL_AUTH_KEYCLOAK_ID_KEY = 'email'
SOCIAL_AUTH_POSTGRES_JSONFIELD = True
SOCIAL_AUTH_URL_NAMESPACE = 'social'
LOGIN_REDIRECT_URL = 'http://<website ip>:8000/'
LOGOUT_REDIRECT_URL = 'http://<website ip>:8000/'
SOCIAL_AUTH_POSTGRES_JSONFIELD = True
SOCIAL_AUTH_PIPELINE = (
'social_core.pipeline.social_auth.social_details',
'social_core.pipeline.social_auth.social_uid',
'social_core.pipeline.social_auth.auth_allowed',
'social_core.pipeline.social_auth.social_user',
'social_core.pipeline.user.get_username',
'social_core.pipeline.mail.mail_validation',
'social_core.pipeline.user.create_user',
'social_core.pipeline.social_auth.associate_user',
'social_core.pipeline.debug.debug',
'social_core.pipeline.social_auth.load_extra_data',
'social_core.pipeline.user.user_details',
'social_core.pipeline.debug.debug',
)
....
码头工人-compose.yml
version: '3'
services:
web:
restart: unless-stopped
container_name: web-container
build:
context: .
dockerfile: Dockerfile.dev
ports:
- "8000:8000"
environment:
PRODUCTION: 'false'
DEBUG: 'True'
DJANGO_SETTINGS_MODULE: '<something>.settings.postgres'
DB_NAME: '<some name>'
DB_USER: '<some user>'
DB_PASS: '<some password>'
DB_HOST: '<some host>'
depends_on:
- db
volumes:
- ./:/usr/src/app/
db:
image: postgres:9.6.2
# volumes:
# - ../docker-postgresql-multiple-databases:/docker-entrypoint-initdb.d
volumes:
- data:/var/lib/postgresql/data
environment:
- POSTGRES_MULTIPLE_DATABASES=<something>
- POSTGRES_USER=<some user>
- POSTGRES_PASSWORD=<some password>
keycloakdb:
image: postgres
volumes:
- keycloak_postgres_data:/var/lib/postgresql/data
environment:
POSTGRES_DB: keycloak
POSTGRES_USER: keycloak
POSTGRES_PASSWORD: password
keycloak:
image: jboss/keycloak
command: -b 0.0.0.0 -Dkeycloak.migration.action=import -Dkeycloak.migration.provider=dir -Dkeycloak.migration.dir=/tmp/
environment:
DB_VENDOR: POSTGRES
DB_ADDR: keycloakdb
DB_DATABASE: keycloak
DB_USER: keycloak
DB_SCHEMA: public
DB_PASSWORD: <some password>
KEYCLOAK_USER: <some user>
KEYCLOAK_PASSWORD: <some password>
#KEYCLOAK_IMPORT: /tmp/
ports:
- 8080:8080
volumes:
- ./keycloak/:/tmp/
depends_on:
- keycloakdb
volumes:
data:
# external: true
keycloak_postgres_data:
driver: local
keycloak/realm.json
{
"id" : "<some realm id>",
"realm" : "<some realm name>",
"notBefore" : 0,
"revokeRefreshToken" : false,
"refreshTokenMaxReuse" : 0,
"accessTokenLifespan" : 300,
"accessTokenLifespanForImplicitFlow" : 900,
"ssoSessionIdleTimeout" : 1800,
"ssoSessionMaxLifespan" : 36000,
"ssoSessionIdleTimeoutRememberMe" : 0,
"ssoSessionMaxLifespanRememberMe" : 0,
"offlineSessionIdleTimeout" : 2592000,
"offlineSessionMaxLifespanEnabled" : false,
"offlineSessionMaxLifespan" : 5184000,
"accessCodeLifespan" : 60,
"accessCodeLifespanUserAction" : 300,
"accessCodeLifespanLogin" : 1800,
"actionTokenGeneratedByAdminLifespan" : 43200,
"actionTokenGeneratedByUserLifespan" : 300,
"enabled" : true,
"sslRequired" : "external",
"registrationAllowed" : false,
"registrationEmailAsUsername" : false,
"rememberMe" : false,
"verifyEmail" : false,
"loginWithEmailAllowed" : true,
"duplicateEmailsAllowed" : false,
"resetPasswordAllowed" : false,
"editUsernameAllowed" : false,
"bruteForceProtected" : false,
"permanentLockout" : false,
"maxFailureWaitSeconds" : 900,
"minimumQuickLoginWaitSeconds" : 60,
"waitIncrementSeconds" : 60,
"quickLoginCheckMilliSeconds" : 1000,
"maxDeltaTimeSeconds" : 43200,
"failureFactor" : 30,
"roles" : {
....bunch of roles
}