feat: 添加JWKS token支持,更新用户和OAuth相关逻辑

This commit is contained in:
2026-02-21 06:29:11 +08:00
parent 672208ab6b
commit 71c238f953
6 changed files with 120 additions and 33 deletions

View File

@@ -38,6 +38,11 @@ export const redis = useContextKey<Redis>('redis');
type TokenOptions = {
expire?: number; // 过期时间,单位秒
ip?: string; // 用户ID默认为当前用户ID
browser?: string; // 浏览器信息
host?: string; // 主机信息
wx?: any;
loginWith?: string; // 登录方式,如 'cli', 'web', 'plugin' 等
}
/**
* 用户模型,使用 Drizzle ORM
@@ -85,21 +90,25 @@ export class User {
oauthUser.orgId = id;
}
if (loginType === 'jwks') {
const expiresIn = opts?.expire ?? 2 * 3600; // 2 hours
const accessToken = await jwksManager.sign({
sub: 'user:' + this.id,
name: this.username,
exp: Math.floor(Date.now() / 1000) + expiresIn,
});
const expiresIn = opts?.expire ?? 2 * 3600; // 2 hours
await oauth.setJwksToken(accessToken, { id: this.id, expire: expiresIn });
return {
type: 'jwks',
accessToken: accessToken,
refreshToken: null,
refreshToken: accessToken,
token: accessToken,
refreshTokenExpiresIn: null,
refreshTokenExpiresIn: expiresIn,
accessTokenExpiresIn: expiresIn
};
}
const token = await oauth.generateToken(oauthUser, { type: loginType, hasRefreshToken: true, ...opts });
return {
type: 'default',
accessToken: token.accessToken,
refreshToken: token.refreshToken,
token: token.accessToken,
@@ -121,8 +130,73 @@ export class User {
* @returns
*/
static async refreshToken(refreshToken: string) {
if (refreshToken?.includes?.('.')) {
// 可能是 jwks token
const jwksToken = await oauth.getJwksToken(refreshToken);
if (!jwksToken) {
throw new CustomError('Invalid refresh token');
}
const decoded = await jwksManager.decode(refreshToken);
const sub = decoded.sub;
const username = decoded.name;
const expiresIn = 2 * 3600; // 2 hours
const newToken = await jwksManager.sign({
sub,
name: username,
exp: Math.floor(Date.now() / 1000) + expiresIn,
});
oauth.setJwksToken(newToken, { id: sub.replace('user:', ''), expire: expiresIn });
return {
type: 'jwks',
accessToken: newToken,
refreshToken: newToken,
token: newToken,
refreshTokenExpiresIn: expiresIn,
accessTokenExpiresIn: expiresIn,
}
}
const token = await oauth.refreshToken(refreshToken);
return { accessToken: token.accessToken, refreshToken: token.refreshToken, token: token.accessToken };
return {
type: 'default',
accessToken: token.accessToken,
refreshToken: token.refreshToken,
token: token.accessToken,
accessTokenExpiresIn: token.accessTokenExpiresIn,
refreshTokenExpiresIn: token.refreshTokenExpiresIn,
};
}
/**
* 重置token立即过期token
* @param token
* @returns
*/
static async resetToken(refreshToken: string, expand?: Record<string, any>) {
if (refreshToken?.includes?.('.')) {
// 可能是 jwks token
const jwksToken = await oauth.getJwksToken(refreshToken);
if (!jwksToken) {
throw new CustomError('Invalid refresh token');
}
const decoded = await jwksManager.decode(refreshToken);
const sub = decoded.sub;
const username = decoded.name;
const expiresIn = 2 * 3600; // 2 hours
const newToken = await jwksManager.sign({
sub,
name: username,
exp: Math.floor(Date.now() / 1000) + expiresIn,
});
oauth.setJwksToken(newToken, { id: sub.replace('user:', ''), expire: expiresIn });
return {
type: 'jwks',
accessToken: newToken,
refreshToken: newToken,
token: newToken,
refreshTokenExpiresIn: expiresIn,
accessTokenExpiresIn: expiresIn,
};
}
return await oauth.resetToken(refreshToken, expand);
}
static async getOauthUser(token: string) {
return await UserSecret.verifyToken(token);

View File

@@ -74,7 +74,7 @@ interface Store<T> {
delKeys: (keys: string[]) => Promise<number>;
}
type TokenData = {
export type TokenData = {
accessToken: string;
accessTokenExpiresIn?: number;
refreshToken?: string;
@@ -401,4 +401,24 @@ export class OAuth<T extends OauthUser> {
const tokens = await this.store.keys('*');
await this.store.delKeys(tokens);
}
/**
* 设置 jwks token 用于jwt的验证 过期时间为2小时
*/
async setJwksToken(token: string, opts: { id: string; expire: number }) {
const expire = opts.expire ?? 2 * 3600; // 2 hours
const id = opts.id || '';
// jwks token的过期时间比accessToken多3天确保3天内可以用来refresh token
const addExpire = 3 * 24 * 3600;
await this.store.redis.set('user:jwks:' + token, id, 'EX', expire + addExpire);
}
async deleteJwsToken(token: string) {
await this.store.redis.expire('user:jwks:' + token, 0);
}
async getJwksToken(token: string) {
const id = await this.store.redis.get('user:jwks:' + token);
if (id) {
this.deleteJwsToken(token);
}
return id;
}
}