You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

193 lines
6.1 KiB
Markdown

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 新身份验证方式开发指南
LobeChat 使用 [Auth.js v5](https://authjs.dev/) 作为外部身份验证服务。Auth.js 是一个开源的身份验证库,它提供了一种简单的方式来实现身份验证和授权功能。本文档将介绍如何使用 Auth.js 来实现新的身份验证方式。
### TOC
- [添加新的身份验证提供者](#添加新的身份验证提供者)
- [准备工作:查阅官方的提供者列表](#准备工作查阅官方的提供者列表)
- [步骤 1: 新增关键代码](#步骤-1-新增关键代码)
- [步骤 2: 更新服务端配置代码](#步骤-2-更新服务端配置代码)
- [步骤 3: 修改前端页面](#步骤-3-修改前端页面)
- [步骤 4: 配置环境变量](#步骤-4-配置环境变量)
- [步骤 5: 修改服务端用户信息处理逻辑](#步骤-5-修改服务端用户信息处理逻辑)
## 添加新的身份验证提供者
为了在 LobeChat 中添加新的身份验证提供者(例如添加 Okta),你需要完成以下步骤:
### 准备工作:查阅官方的提供者列表
首先,你需要查阅 [Auth.js 提供者列表](https://authjs.dev/reference/core/providers) 来了解是否你的提供者已经被支持。如果你的提供者已经被支持,你可以直接使用 Auth.js 提供的 SDK 来实现身份验证功能。
接下来我会以 [Okta](https://authjs.dev/reference/core/providers/okta) 为例来介绍如何添加新的身份验证提供者
### 步骤 1: 新增关键代码
打开 `src/app/api/auth/next-auth.ts` 文件,引入 `next-auth/providers/okta`
```ts
import { NextAuth } from 'next-auth';
import Auth0 from 'next-auth/providers/auth0';
import Okta from 'next-auth/providers/okta';
// 引入 Okta 提供者
```
新增预定义的服务端配置
```ts
// 导入服务器配置
const { OKTA_CLIENT_ID, OKTA_CLIENT_SECRET, OKTA_ISSUER } = getServerConfig();
const nextAuth = NextAuth({
providers: [
// ... 其他提供者
Okta({
clientId: OKTA_CLIENT_ID,
clientSecret: OKTA_CLIENT_SECRET,
issuer: OKTA_ISSUER,
}),
],
});
```
### 步骤 2: 更新服务端配置代码
打开 `src/config/server/app.ts` 文件,在 `getAppConfig` 函数中新增 Okta 相关的环境变量
```ts
export const getAppConfig = () => {
// ... 其他代码
return {
// ... 其他环境变量
OKTA_CLIENT_ID: process.env.OKTA_CLIENT_ID || '',
OKTA_CLIENT_SECRET: process.env.OKTA_CLIENT_SECRET || '',
OKTA_ISSUER: process.env.OKTA_ISSUER || '',
};
};
```
### 步骤 3: 修改前端页面
修改在 `src/features/Conversation/Error/OAuthForm.tsx``src/app/settings/common/Common.tsx` 中的 `signIn` 函数参数
默认为 `auth0`,你可以将其修改为 `okta` 以切换到 Okta 提供者,或删除该参数以支持所有已添加的身份验证服务
该值为 Auth.js 提供者 的 id你可以阅读相应的 `next-auth/providers` 模块源码以读取默认 ID
### 步骤 4: 配置环境变量
在部署时新增 Okta 相关的环境变量 `OKTA_CLIENT_ID`、`OKTA_CLIENT_SECRET`、`OKTA_ISSUER`,并填入相应的值,即可使用
### 步骤 5: 修改服务端用户信息处理逻辑
#### 在前端获取用户信息
在前端页面中使用 `useOAuthSession()` 方法获取后端返回的用户信息 `user`
```ts
import { useOAuthSession } from '@/hooks/useOAuthSession';
const { user, isOAuthLoggedIn } = useOAuthSession();
```
默认的 `user` 类型为 `User`,类型定义为:
```ts
interface User {
id?: string;
name?: string | null;
email?: string | null;
image?: string | null;
}
```
#### 修改用户 `id` 处理逻辑
`user.id` 用于标识用户。当引入新身份 OAuth 提供者后,您需要在 `src/app/api/auth/next-auth.ts` 中处理 OAuth 回调所携带的信息。您需要从中选取用户的 `id`。在此之前,我们需要了解 `Auth.js` 的数据处理顺序:
```txt
authorize --> jwt --> session
```
默认情况下,在 `jwt --> session` 过程中,`Auth.js` 会[自动根据登陆类型](https://authjs.dev/reference/core/types#provideraccountid)将用户 `id` 赋值到 `account.providerAccountId` 中。 如果您需要选取其他值作为用户 `id` ,您需要实现以下处理逻辑。
```ts
callbacks: {
async jwt({ token, account, profile }) {
if (account) {
// 您可以从 `account` 或 `profile` 中选取其他值
token.userId = account.providerAccountId;
}
return token;
},
},
```
#### 自定义 `session` 返回
如果您想在 `session` 中携带更多关于 `profile``account` 的信息,根据上面提到的 `Auth.js` 数据处理顺序,那必须先将该信息复制到 `token` 上。
示例:把用户头像 URL`profile.picture` 添加到`session` 中:
```diff
callbacks: {
async jwt({ token, profile, account }) {
if (profile && account) {
token.userId = account.providerAccountId;
+ token.avatar = profile.picture;
}
return token;
},
async session({ session, token }) {
if (session.user) {
session.user.id = token.userId ?? session.user.id;
+ session.user.avatar = token.avatar;
}
return session;
},
},
```
然后补充对新增参数的类型定义:
```ts
declare module '@auth/core/jwt' {
interface JWT {
// ...
avatar?: string;
}
}
declare module 'next-auth' {
interface User {
avatar?: string;
}
}
```
> [更多`Auth.js`内置类型拓展](https://authjs.dev/getting-started/typescript#module-augmentation)
#### 在处理逻辑中区分多个身份验证提供者
如果您配置了多个身份验证提供者,并且他们的 `userId` 映射各不相同,可以在 `jwt` 方法中的 `account.provider` 参数获取身份提供者的默认 id ,从而进入不同的处理逻辑。
```ts
callbacks: {
async jwt({ token, profile, account }) {
if (profile && account) {
if (account.provider === 'Authing')
token.userId = account.providerAccountId ?? token.sub;
else if (acount.provider === 'Okta')
token.userId = profile.sub ?? token.sub;
else
// other providers
}
return token;
},
}
```