3

无法从 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 Coo​​kie 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
}
4

0 回答 0