第七色在线视频,2021少妇久久久久久久久久,亚洲欧洲精品成人久久av18,亚洲国产精品特色大片观看完整版,孙宇晨将参加特朗普的晚宴

為了賬號安全,請及時綁定郵箱和手機立即綁定

大型銀行應(yīng)用中如何使用TypeScript實現(xiàn)狀態(tài)同步

標簽:
Typescript

介绍

在开发一个大型银行软件时,我遇到了与令牌刷新周期相关的问题。

该应用程序使用了Keycloak来实现OpenID Connect (OIDC)认证,其中访问令牌被设置为每5分钟过期,以此来增强安全性。前端应用程序是用React.jsViteTypeScript开发的。

注意:本文中的所有图片和代码示例仅供示意,与提及的项目无关。这些场景被设计为示意用途,以保护项目的隐私和代码。

如何在多标签浏览器中安全存储认证信息

我们优先考虑安全性,将经过身份验证的用户数据存储在会话存储中,而不是放在本地存储或cookies中。这种架构选择通过会话隔离技术增强了安全性。

使用会话存储的一个关键好处是它的每个标签的特定范围。与本地存储或 cookies 在所有浏览器标签中保持持久性不同,会话存储为每个标签创建独立的认证环境

这到底是什么意思?

本地存储(即使浏览器关闭后数据仍然被保存)或cookies(可能被某些类型的攻击利用)不同,session storage 确保认证数据仅在会话期间存在。一旦用户关闭浏览器标签,数据就会自动被删除。

提取自定义的声明

在前端界面上,我们有一个页面显示已登录用户的权限信息(这些权限信息是在我们的身份提供程序——Keycloak 中预设的)。但等等!我是如何获取这些权限信息的呢?

页面草稿

当用户登录时,我会收到一个包含各种声明的响应,这些声明存储在令牌内。声明是存储在令牌内的信息片段。它们告诉系统你是以及你能做什么事情。

可以这么想,就像一张身份证明。

想象你有一张学生证,上面写着:

  • 你的名字和姓
  • 你的学号
  • 你的身份(例如,学生)

我从token中提取这些声明并将用户信息存储在会话存储中。最重要的是,我检索一个包含profile对象,该对象包含一个“authorities”数组,列出了已认证用户的全部权限。示例如下。

可以在这个GitHub Gist里找到代码。

就在这里我碰到了头一个问题。

我使用了 [**react-oidc-context**](https://www.npmjs.com/package/react-oidc-context) 这个库来处理应用的身份验证。在后台,这个库会把上述响应转换成他们的 UserManager 对象。

很遗憾地,其_UserManager_对象中的“profile”对象里没有包含“authorities”数组。在这种情况下,因此,“authorities”是一个自定义属性。

所以我在这个“profile”对象里实现了添加自定义声明的逻辑。我所需要做的就是从响应中的“authorities”数组提取权限,并在初始化用户数据时添加这些权限。

你可以在这个GitHub Gist中找到代码。

我在他们的GitHub仓库中为此创建了一个问题。然而,在这个库能够支持自定义声明之前,我们需要手动在系统中添加这些自定义声明。这是一种两面性的做法,后面你会明白其中的原因。

之后,我用这个库里的 useAuth() 钩子来获取已认证的用户对象。

代码:

    // userPermissions.tsx

    const { user } = useAuth();
    const [authorities, setAuthorities] = useState<string[]>([]);

    useEffect(() => {
    if (user?.profile?.authorities) {
      setAuthorities(user.profile.authorities as string[]);
    }
    }, [user]);

    // 这用于首次渲染以初始化数据
    useEffect(() => {
        const updateAuthorities = async () => {
          const oidcKey = `${KEYCLOAK_URL}:${KEYCLOAK_REALM_CLIENT_ID}`;
          const storedUserData = JSON.parse(
            sessionStorage.getItem(oidcKey) || "{}",
          );
          const storedAuthorities =
            storedUserData?.profile?.["authorities"] || [];

          setAuthorities(storedAuthorities);
        };

        updateAuthorities();

        userManager.events.addUserLoaded(handleUserLoaded);

        return () => {
          userManager.events.removeUserLoaded(handleUserLoaded);
        };
    }, []);

    // 其他内容

    return (
    <tbody>
    {authorities.map((permission) => (
     <tr key={permission}>
       <td>
         {formatPermissionName(permission)}
       </td>
     </tr>
    ))}
    </tbody>
    )

现在每次用户打开“权限设置”页面时,组件就会从存储中获取权限列表并显示出来。

token过期了会咋办?

当令牌过期时,应用程序会自动请求一个新的令牌。整个过程是全自动的,并且会在当前令牌过期前一分钟请求新的访问令牌。一旦应用程序收到新令牌,它会把更新的用户数据存储在会话中。

而且一切看起来都很棒,但其实不然。就像我在之前的帖子中经常提到的一样,你知道总有一个方面需要我们去关注。那么,这种方法到底哪里不对劲呢?

明确的问题:

有一种特殊情况。所以如果用户打开“权限设置”页面(当页面重新加载时),页面会不断从存储中获取最新的信息,这会额外产生一些开销。

但用户停留在页面上超过30分钟的持续时间而没有进行互动操作或刷新会怎样呢?你知道发生了什么吗……这可就麻烦了

是的,我知道这是一个特殊情况,但客观来说,这种情况确实可能发生。让我给你举个例子:你在一个标签页中打开了权限页面,然后喝杯咖啡。当你回来后,在另一个标签页中参加了一个45分钟的会议。会议结束后,你回到第一个标签页时,突然看到权限页面还在那里,然后就出现了错误!

为什么呢?

为什么最后保存的权限没有显示呢?

我们有一个 useEffect,它监听用户状态的变化。一旦用户状态发生变化,它就会更新 权限 列表,这个列表保存在 useState 里。

在我们的情况下,当 token 更新时,它会检测用户的变化,然后更新会话存储。然而,因为我的自定义方法 getCustomClaims 还没有被执行,这个过程中不会包含 ‘authorities’ 数组。为解决这个问题,我需要触发它的执行,以更新状态并正确反映权限。

那就是事件插手的时候了。

活动

为了解决这个问题,我们可以引入一个自定义窗口事件处理程序,当新的声明(claims)被填充到user对象中时触发该事件。当事件被触发时,我们可以手动更新authorities,确保新的权限被显示,而无需重新加载页面。

在TypeScript中操作窗口事件的步骤:

  1. 创建一个自定义事件: 首先,例如,我们需要定义一个自定义事件,当新的声明(claims)添加到会话存储时触发。我们可以使用 JavaScript/TypeScript 中的 CustomEvent 构造函数来创建一个新的事件,该事件可以在全局范围内被监听。
  2. 在令牌刷新后触发事件: 当新的声明(如 authorities)在令牌刷新后成功添加到会话存储中时,我们将触发这个自定义事件。
  3. userPermissions.tsx 文件中,我们可以监听该事件:userPermissions.tsx 文件中,我们可以监听该事件并相应地更新状态信息。

修改了 getCustomClaims 方法,来触发一个事件:

修改了 userPermissions.tsx 文件来识别事件,这样就能检测到事件。

    const { user } = useAuth();  
    const [authorities, setAuthorities] = useState<string[]>([]);  

    // 新的方法  
    const updateAuthorities = () => {  
      const oidcKey = `${OIDC_USER_PREFIX}:${KEYCLOAK_URL}:${KEYCLOAK_REALM_CLIENT_ID}`;  
      const storedUserData = JSON.parse(sessionStorage.getItem(oidcKey) || "{}");  
      const storedAuthorities =  
       storedUserData?.profile?.[AUTHORITIES_KEY_NAME] || [];  

      setAuthorities(storedAuthorities);  
    };  

    useEffect(() => {  
      if (user?.profile?.authorities) {  
        setAuthorities(user.profile.authorities as string[]);  
      }  
    }, [user]);  

    useEffect(() => {  
      updateAuthorities();  

      const handleAuthoritiesUpdated = () => {  
        updateAuthorities();  
      };  

      const handleUserLoaded = () => {  
        updateAuthorities();  
      };  

      // 添加事件监听器  
      window.addEventListener(  
        AUTHORITIES_UPDATED_EVENT,  
        handleAuthoritiesUpdated as EventListener,  
      );  
      userManager.events.addUserLoaded(handleUserLoaded);  

      return () => {  
        // 清理事件监听器  
        window.removeEventListener(  
          AUTHORITIES_UPDATED_EVENT,  
          handleAuthoritiesUpdated as EventListener,  
        );  
        userManager.events.removeUserLoaded(handleUserLoaded);  
      };  
    }, []);  

    // 其他相关代码
清理活动

别忘了哦!

当组件卸载或不再需要监听事件时,移除事件监听器非常重要。这可以防止内存泄漏并确保正确释放资源。

还有一些小故事

加我一下

GitHub, LinkedIn, dev.to (GitHub: 一个面向开源及私人软件项目的版本控制系统,LinkedIn: 一个职场社交网站,dev.to: 一个面向开发者的博客平台)

希望你今天学到了新知识!✌️

點擊查看更多內(nèi)容
TA 點贊

若覺得本文不錯,就分享一下吧!

評論

作者其他優(yōu)質(zhì)文章

正在加載中
  • 推薦
  • 評論
  • 收藏
  • 共同學(xué)習(xí),寫下你的評論
感謝您的支持,我會繼續(xù)努力的~
掃碼打賞,你說多少就多少
贊賞金額會直接到老師賬戶
支付方式
打開微信掃一掃,即可進行掃碼打賞哦
今天注冊有機會得

100積分直接送

付費專欄免費學(xué)

大額優(yōu)惠券免費領(lǐng)

立即參與 放棄機會
微信客服

購課補貼
聯(lián)系客服咨詢優(yōu)惠詳情

幫助反饋 APP下載

慕課網(wǎng)APP
您的移動學(xué)習(xí)伙伴

公眾號

掃描二維碼
關(guān)注慕課網(wǎng)微信公眾號

舉報

0/150
提交
取消