Ant Design Pro 与 Open ID Connect 服务器的集成

你说我一个学客户端的,怎么去研究前后端了呢?

这段时间因为手里项目的关系,要写点前端和后端的东西。需求上还挺奇怪,要实现登录,不允许有注册功能而是要求接入企业微信。对我这么个写客户端的来说,前后端说陌生也不陌生,说熟悉也算不上。那需求摆在这里了,总得写吧。

后端

Duende Identity Server 的搭建与配置

虽说之前在实习的时候,组里出过一次名为《给 Kotlin 客户端研发的后端入门教程》,用到的技术栈是 JetBrains 官方的 Ktor 框架。但因为之前稍微用过 .NET Core 那一套,并且我一直很喜欢 C# 里的 LINQ,就还是继续用 .NET Core 了。Ktor 也稍微了解过一点,但看了下社区感觉不算很活跃的样子,还是算了。

.NET Core 这边有一个官方+社区的认证服务器框架,叫 Identity Server。出到版本 4 之后调整了一下策略,保持开源的情况下有了新的商业化运作。不过这都不是我们要研究的重点。

在 Github 上另外有一个开源的项目叫 Duende.IdentityServer.Admin,对 IS 进行了一些包装,在业务上没有比较个性化的需求时,可以几乎做到开箱即用。根据 Readme 完成脚手架的搭建,并配置好数据库之后,基本上就可以直接使用了。

对于需要接入认证的应用,首先我们需要在 IS 的后台配置一个 Client(客户端)。根据不同类型的客户端应用,Open ID Connect 提供了不同的认证流程(Flow)。由于 React 的 OIDC 客户端只支持 Authentication Code Flow,因此选提供了该 Flow 的选项即可。

然后需要对客户端进行一定的配置。如下图所示,只勾选“允许通过浏览器访问令牌”。默认情况下“需要 Pkce”也是被选中的,但 React 的 OIDC 客户端我尝试了很久都没能在这个选项开启的时候成功认证,那就索性先关闭了。

接下来配置“重定向 Uri”,这个选项对应了认证页面 URL 中的 redirect_uri 字段,只有在这里配置了的重定向 Uri 才会被允许跳转。在本地开发的时候注意也需要加上 localhost

在“认证/注销”选项卡中有一个“注销重定向 Uri”,这个配置用于指定用户主动注销会话后的重定向 URL。实际上是否会被用到取决于 OIDC 客户端的实现。

在“令牌”选项卡中,需要配置“允许跨域来源”。这个配置是相当重要的一个选项,关系到浏览器跨域请求能否进行。注意协议和端口号一定要完全一致。

最后在“同意屏幕”中可以选择是否关掉同意屏幕。所谓同意屏幕,就是类似于 QQ 授权登录的是否确定要登录目标网站。如果浏览器存在着认证系统的有效会话,就会直接跳过这个步骤,直接重定向。

至此 IS 的配置部分就完成了。

IS 接入企业微信登录

在解决方案的 *.STS.Identity 项目中添加 AspNet.Security.OAuth.WorkWeixin 包。这个包封装了和企业微信登录的交互,我们只需要配置好申请到的密钥等信息即可。

然后打开 *.STS.Identity 下的 StartupHelper.cs 文件,找到 AddExternalProviders 方法,加入下面的代码:

if (externalProviderConfiguration.UseWeComProvider)
{
    authenticationBuilder.AddWorkWeixin(options =>
    {
        options.ClientId = externalProviderConfiguration.WeComCorpId; // corpid
        options.ClientSecret = externalProviderConfiguration.WeComCorpSecret; // secret
        options.AgentId = externalProviderConfiguration.WeComAgentId; // agentid
    });
}

ClientIdClientSecretAgentId 三个参数分别对应了企业微信后台应用的 corpidsecretagentid

然后重新编译运行,就可以看到登录界面多了一个“外部登录”选项。

点击之后就可以正确跳转企业微信的扫码界面了。

前端

前端除了要配置 OIDC 客户端之外,还需要对原有的 AntD Pro 脚手架做比较大的一些改动。

我这里使用的 AntD Pro 是最新版本,语言选的 TypeScript。首先给项目中添加 oidc-client-ts 这个包。从名字就能看出来是 OIDC 的客户端。

首先新建一个文件用于存放认证服务器信息,这里将认证信息存放到了浏览器的 Local Storage 中:

export const oidcConfig = {
    client_id: 'your_client_id',
    redirect_uri: `${window.location.origin}/user/login`,
    scope: 'openid roles profile',
    authority: 'your_auth_server_uri',
    userStore: new WebStorageStateStore({ store: window.localStorage }),
    loadUserInfo: true
}

export const oidcLocalStorageKey = `oidc.user:${oidcConfig.authority}:${oidcConfig.client_id}`

找到 app.tsx 文件,将 getInitialState 修改为:

export async function getInitialState(): Promise<{
  settings?: Partial<LayoutSettings>;
  currentUser?: User;
  loading?: boolean;
  fetchUserInfo?: () => Promise<User | undefined>;
}> {
  const fetchUserInfo = async () => {
    const oidcStorage = localStorage.getItem(oidcLocalStorageKey)
    return oidcStorage ? User.fromStorageString(oidcStorage) : undefined
  };

  if (history.location.pathname !== loginPath) {
    const currentUser = await fetchUserInfo();
    return {
      fetchUserInfo,
      currentUser,
      settings: defaultSettings,
    };
  }
  return {
    fetchUserInfo,
    settings: defaultSettings,
  };
}

注意这里的 Useroidc-client-ts 的一个类。fetchUserInfo 闭包主要用于将 Local Storage 里序列化存储的登录信息反序列化为 User 对象。

然后,在 RunTimeLayoutConfig 的返回值中,rightContentRenderfooterRenderchildrenRender 分别用 <AuthProvider {...oidcConfig}></AuthProvider> 包裹一下,用于为组件开发提供认证信息 Context。注意导入一下上面提前定义好的 oidcConfig

onPageChange 也需要进行一些改动,这里给出一种参考实现。主要思路是在本地没有用户信息或者 Token 过期的时候能重定向到登录页面。

onPageChange: () => {
    const oidcStorage = localStorage.getItem(oidcLocalStorageKey)
    if (!oidcStorage && location.pathname !== loginPath) {
        history.push(loginPath)
    } else if (oidcStorage && location.pathname !== loginPath) {
        const currentUser = User.fromStorageString(oidcStorage!)
        if (currentUser.expired) {
            history.push(loginPath)
        }
    }
}

src/components/RightContent/AvatarDropdown.tsx 进行修改,使得在头像下拉菜单中能显示用户名:

const auth = useAuth() // 用于提供退出登录等用户登录态管理功能
const { currentUser } = initialState // 从 initialState 中获得当前用户信息

return (
    <HeaderDropdown overlay={menuHeaderDropdown}>
        <span className={`${styles.action} ${styles.account}`}>
            <Avatar
                size="small"
                style={{
                    background: backgroundColor,
                    color: foregroundColor
                }}
                className={styles.avatar}
                alt="avatar">
                {currentUser.profile.preferred_username[0].toLocaleUpperCase()}
            </Avatar>
            <span className={`${styles.name} anticon`}>{currentUser.profile.preferred_username}</span>
        </span>
    </HeaderDropdown>
)

access.ts 中可以对接 AntD Pro 的权限系统,这里只简单区分一下是否为管理员用户。User 还是 oidc-client-ts 提供的类型。

export default function access(initialState: { currentUser?: User } | undefined) {
    const { currentUser } = initialState ?? {};
    return {
        canAdmin: currentUser && currentUser.profile.role === 'Administrator',
    };
}

这样基本上就完成了 IS 和 AntD Pro 的对接。至于如何在 .NET Core 中完成和 IS 的对接,网络上已经有不少的资料,就不再赘述了。

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇