0

首先,提前感谢任何阅读我的问题和评论的人。我有一个 CRA 应用程序正在使用keycloak-jsReactKeycloakProvcer来自 `@react-keycloak/web. 当您第一次加载应用程序页面并登录时,keycloak 已正确初始化并正常运行。提供者以非常标准的方式获取 KC 的实例。

import keycloak from './authentication/keycloak'

const KeycloakProviderBlock = ({children}) => {
    return (
        <ReactKeycloakProvider authClient={keycloak} initOptions={{onLoad: 'login-required'}}>
            {children}
        </ReactKeycloakProvider>
    );
};

稍后在我的 axios 包装器中,我将 KC 令牌拉出以添加到所有请求中作为不记名令牌,如下所示:

import keycloak from "./authentication/keycloak";
const {authenticated} = keycloak;
    if (authenticated) {
        client.defaults.headers.common = {
            ...client.defaults.headers.common,
            Authorization: `Bearer ${keycloak.token}`,
        };
    } else {
        logger.error("Request client used before KeyCloak initialized");
    }

我的 keycloak 文件只返回一个新的 KC 实例 --> /authentication/keycloak.js 的内容

import Keycloak from "keycloak-js";

const keycloak = new Keycloak({
    realm: process.env.REACT_APP_KEYCLOAK_REALM,
    url: process.env.REACT_APP_KEYCLOAK_URL,
    clientId: process.env.REACT_APP_KEYCLOAK_CLIENT,
})

export default keycloak

一切正常,直到用户硬刷新页面。当页面重新加载时,KC 对象上不存在 keycloak.authenticated,因此所有 HTTP 调用都会失败,因为没有 Bearer 令牌。

我正在使用 keycloak-js 版本 15.0.2。任何/所有想法表示赞赏。

4

1 回答 1

0

我想出了如何解决这个问题,并想我会发布答案,以防它帮助其他人。事实证明,当页面刷新时,KC 从 cookie 中恢复会话信息所需的时间比运行代码所需的时间要长。因此,当页面重新加载时,它试图在 KC 有机会验证用户确实登录之前到达后端时存在竞争条件。事实证明,KeycloakProvider 会发出事件并告诉您何时刷新令牌。事件未触发,因为我将 K​​CProvider 包装在 JSX 组件中,因此事件未正确绑定。我删除了不必要的块,事件开始触发。从那里开始,很容易显示 Loading throbber 并阻止其余组件呈现,直到提供者实际获得 onReady 事件。

在 App.js 中

   // Keycloak
    onKeycloakEvent = (event, error) => {
        console.log('onKeycloakEvent', event, error)
        console.log(`Keycloak Event ${event}`);
        if(event && event === 'onReady'){
            this.setState({keycloakReady: true})
        }
    }

    onKeycloakTokens = (tokens) => {
        console.log('onKeycloakTokens', tokens)
    }

...在渲染中将功能传递给提供者:

<ReactKeycloakProvider authClient={keycloak} initOptions={{onLoad: 'login-required'}}
                        keycloak={keycloak}
                        onEvent={this.onKeycloakEvent}
                        onTokens={this.onKeycloakTokens}>
                        <SplashScreen keycloakReady={keycloakReady}>
                        ....
                        </SplashScren>
</ReactKeycloakProvder>

然后在 SplashScreen 中仅在 KC 准备好时渲染子项:

import React, {Component, PureComponent, ReactChildren} from "react";
import LoadingSpinner from "../navigation/LoadingSpinner";

type PassedProps = {
    keycloakReady: boolean;
}

class SplashScreen extends PureComponent<PassedProps, any> {
    constructor(props: PassedProps) {
        super(props);
    }

    render() {
        console.log(`SplashScreen keycloak ready ${this.props.keycloakReady}`);
        if(!this.props.keycloakReady){
            return <LoadingSpinner/>
        }else{
            return this.props.children
        }
    }
}

export default SplashScreen
于 2021-12-15T17:59:16.777 回答