在 React js 应用程序中使用 Keycloak 注销用户时出现问题

Problem in log out user using Keycloack in React js app

我正在 React js 应用程序中使用 Keycloak 实现身份验证。

我正在使用 React Context 来管理全局状态,在这种情况下是 KeycloackContext 中的 keycloackValueauthenticated

案例:

  1. 当应用首次 运行 时,应用会检查 keycloackValueauthenticated。它们的初始状态是 nullfalse.
  2. 如果keycloackValueauthenticatednullfalse,应用程序将初始化keycloak登录页面(通过在[=17中调用keycloak.init =]).
  3. 如果keycloackValueauthenticatednot nulltrue(意味着用户通过keycloak成功登录),那么应用程序将呈现[中的所有组件=32=].
  4. 如果用户点击 Header 组件中的 log out button,应用会将 keycloackValueauthenticated 设置为 nullfalse还调用 keycloak 来注销用户。之后调用keycloak登录页面。

问题:

我使用keycloak成功登录了。然后应用程序成功渲染了 MyApp.js.

中的所有组件

问题是当我点击Header.js中的log out button时,keycloackValueauthenticated被成功设置为nullfalse keycloak 也成功登出。然后应用程序自行刷新并呈现 MyApp.js 中的所有组件(应用程序未调用 keycloak 登录页面)。

代码:

index.js

import React from "react";
import ReactDOM from "react-dom";
import MyApp from './my/App'
import { KeycloackContextProvider } from "./my/contexts/KeycloackContext"
 
ReactDOM.render(
    <KeycloackContextProvider>
        <MyApp/>
    </KeycloackContextProvider>,
    document.getElementById("root")
);

KeycloackContext.js

import React, { createContext, useState, useEffect } from 'react'

// KEYCLOACK
import Keycloak from 'keycloak-js'

const KeycloackContext = createContext()

const KeycloackContextProvider = (props) => {
    const [ keycloackValue, setKeycloackValue ] = useState(null)
    const [ authenticated, setAuthenticated ] = useState(false)

    const setKeycloack = () => {
        const keycloak = Keycloak({
            realm: process.env.REACT_APP_WULING_KEYCLOAK_REALM,
            url: process.env.REACT_APP_WULING_KEYCLOAK_URL,
            clientId: process.env.REACT_APP_WULING_KEYCLOAK_CLIENTID,
        })

        keycloak.init({
            onLoad: 'login-required', 
            checkLoginIframe: false,
        }).then(authenticated => {
            setKeycloackValue(keycloak)
            setAuthenticated(authenticated)
        })
    }

    const logout = () => {
        setKeycloack(null)
        setAuthenticated(false)
        keycloackValue.logout()
    }

    useEffect(() => {
        setKeycloack()
    }, [])

    return (
        <KeycloackContext.Provider
            value={{
                keycloackValue,
                authenticated,
                logout
            }}
        >
            {props['children']}
        </KeycloackContext.Provider>
    )
}

export { KeycloackContextProvider, KeycloackContext }

MyApp.js

import React, { useContext } from 'react'
import {BrowserRouter as Router, Switch, Route, Redirect } from 'react-router-dom'

import Header from './components/Header/Header'
import Tab from './components/Tab/Tab'

// CONTEXTS
import { KeycloackContext } from './contexts/KeycloackContext'

import Device from './pages/Device/Device'
import Stock from './pages/Stock/Stock'

const MyApp = () => {
    const { keycloackValue, authenticated } = useContext(KeycloackContext)

    return (
        (keycloackValue && authenticated) &&
        <div className='app-root'>
            <Router>
                <Header/>
                <Tab/>

                <Redirect from='/' to='/device'/>
                <Switch>
                    <Route exact path='/device'>
                        <Device/>
                    </Route>
                    <Route exact path='/stock'>
                        <Stock/>
                    </Route>
                </Switch>
            </Router>
        </div>
    )
}

export default MyApp

Header.js

import React, { useContext } from 'react'

// CONTEXTS
import { KeycloackContext } from '../../contexts/KeycloackContext'

const Header = () => {
    const { logout } = useContext(KeycloackContext)

    return (
        <div className='logout-popup-root'>
            <p className='logout-popup-username'>Username</p>
            <p className='logout-popup-email'>username@gmail.com</p>
            <div className='logout-popup-divider'></div>
            <div 
                className='logout-popup-logout-button'
                onClick={() => logout()}
            >
                Log Out
            </div>
        </div>
    )
}

export default Header

相关依赖项:

"keycloak-js": "^15.0.0",
"react": "16.12.0",
"react-dom": "16.12.0",
"react-router-dom": "5.1.2",
"react-scripts": "3.2.0",

注:

  1. 我昨天尝试在另一个应用程序中使用此代码。该应用程序很好(一切如我所愿 运行)。不知道为什么这次代码不像之前的app那样运行
  2. 如果这个问题不够清楚,请告诉我。我会尽快更新的。

更新:

  1. 在以前的应用程序(昨天的应用程序)中,如果我单击注销按钮,应用程序将刷新一次(来自 keycloak)并出现 keycloak 登录页面。
  2. 在这个应用程序(今天的应用程序)中,如果我单击注销按钮,应用程序将刷新三次(来自 keycloak),keycloak 识别用户已通过身份验证(keycloackValueauthenticatednot nulltrue), keycloak 登录页面没有出现.

经过几天的尝试和调试,终于找到了解决办法。

只需评论或删除 KeycloackContext.js 中的 checkLoginIframe: false。它修复了注销问题。

Keycloack.js

import React, { createContext, useState, useEffect } from 'react'

// KEYCLOACK
import Keycloak from 'keycloak-js'

const KeycloackContext = createContext()

const KeycloackContextProvider = (props) => {
    const [ keycloackValue, setKeycloackValue ] = useState(null)
    const [ authenticated, setAuthenticated ] = useState(false)

    const setKeycloack = () => {
        const keycloak = Keycloak({
            realm: process.env.REACT_APP_WULING_KEYCLOAK_REALM,
            url: process.env.REACT_APP_WULING_KEYCLOAK_URL,
            clientId: process.env.REACT_APP_WULING_KEYCLOAK_CLIENTID,
        })

        keycloak.init({
            onLoad: 'login-required', 
            // checkLoginIframe: false, // remove checkLoginIframe here
        }).then(authenticated => {
            setKeycloackValue(keycloak)
            setAuthenticated(authenticated)
        })
    }

    const logout = () => {
        setKeycloack(null)
        setAuthenticated(false)
        keycloackValue.logout()
    }

    useEffect(() => {
        setKeycloack()
    }, [])

    return (
        <KeycloackContext.Provider
            value={{
                keycloackValue,
                authenticated,
                logout
            }}
        >
            {props['children']}
        </KeycloackContext.Provider>
    )
}

export { KeycloackContextProvider, KeycloackContext }