How to Log Out from Keycloak from Django Code

偶尔善良 提交于 2019-12-11 11:40:51

问题


Can not log out from keycloak IDP from inside of Django app code. All stackoverflow answers did not work fo me (most are for older version of the components involved), the same goes for the keycloak documentation.

Recently we have implemented keycloak-based athentication for our Django-based website. Works fine for auth. The app is build by docker, three containers: the website on port 8000, keycloak db (postgres image), keycloak (jboss/keycloak image) on port 8080.

Now I have to add "Logout" functionality to it, meaning singing out of keycloak from my Django code, and redirect the user back to the keycloak login screen.

My setup:

Django 2.2
Python 3.5
keycloak 7
social-auth-app-django         3.1.0
social-auth-core               3.2.0

The keycloak documentation suggests this:

POST /{realm}/users/{id}/logout

with user id and realm name.

That produces nothing but 404 so I switched to this pattern (fond on stackoverflow): POST /auth/realms/acc/protocol/openid-connect/logout

This returns 403 and the error : "Forbidden (CSRF cookie not set.): /{realm name}/users/{user id}/logout/"

which I managed to solve (see update below)

urls.py

urlpaterns = [
....
    url(r'logout/$', logout_view, name='logout'),
...
]

views.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")

That sends me back to the frontpage of my website, I am still logged in and can browse the site normally. Hitting keycloak login screen at this point sends me back to my website front page already logged in, so I am still authenticated. Killing session before the POST in Django code does not change anything. Basically, there's no logout from either websste or keycloak when I am authenticated with keycloak. Keycloak expiration with time does happen, so realm.json settings work.

I did try to set this in my settings.py:

CSRF_COOKIE_SECURE = False
SESSION_COOKIE_SECURE = False

..but no effect.

So, where can I find an example of working Django code that logs out a user from Keycloak? Thanks

UPDATE The first part of the puzzle is solved (reading Django CSRF Cookie Not Set , the answer by Евгений Смирнов and similar posts): to get rid of "CSRF cookie not set" error I had to do one simple thing:

from django.views.decorators.csrf import csrf_exempt

and add the decorator for my logout view:

@csrf_exempt
def logout_view(request):
   ..........

After that the logout request is executed and comes back with this:

[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

I am guessing I am not sending back the correct refresh_token, and I don't know how to test this suspicion.

-------------------------------------------------------

Other pieces (probably irrelevant):

settings.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',
)
....

docker-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
}

来源:https://stackoverflow.com/questions/58198713/how-to-log-out-from-keycloak-from-django-code

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!