docs: 基本完成初版数据库设计
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
import { Inject, Injectable, UnauthorizedException } from '@nestjs/common';
|
||||
import { JwtService } from '@nestjs/jwt';
|
||||
import { SERVER_CONFIG, type ServerConfig } from '../config/server-config';
|
||||
|
||||
/** 最小身份载荷;authVersion 用于与账户当前版本比较,不在 token 中固化角色。 */
|
||||
export interface AccessTokenClaims {
|
||||
sub: string;
|
||||
authVersion: number;
|
||||
iat: number;
|
||||
exp: number;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
/** 统一签发和验证访问凭证,固定算法、签发方、受众和有效期。 */
|
||||
export class JwtTokenService {
|
||||
constructor(
|
||||
private readonly jwt: JwtService,
|
||||
@Inject(SERVER_CONFIG) private readonly config: ServerConfig,
|
||||
) {}
|
||||
|
||||
/** 仅供可信登录业务调用;调用方须先验证微信身份并取得真实账户。 */
|
||||
async signAccessToken(
|
||||
userId: string,
|
||||
authVersion: number,
|
||||
): Promise<string> {
|
||||
if (
|
||||
!userId ||
|
||||
userId.length > 128 ||
|
||||
!Number.isSafeInteger(authVersion) ||
|
||||
authVersion < 0
|
||||
) {
|
||||
throw new Error('Invalid access token identity');
|
||||
}
|
||||
return this.jwt.signAsync(
|
||||
{ sub: userId, authVersion },
|
||||
{
|
||||
secret: this.config.jwtSecret,
|
||||
algorithm: 'HS256',
|
||||
issuer: this.config.jwtIssuer,
|
||||
audience: this.config.jwtAudience,
|
||||
expiresIn: this.config.jwtExpiresInSeconds,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/** 验签及校验载荷;当前账户状态与权限仍由全局守卫继续核对。 */
|
||||
async verifyAccessToken(token: string): Promise<AccessTokenClaims> {
|
||||
try {
|
||||
const claims = await this.jwt.verifyAsync<AccessTokenClaims>(
|
||||
token,
|
||||
{
|
||||
secret: this.config.jwtSecret,
|
||||
// 不根据 token 头自由选择算法,并限制凭证用途和签发方。
|
||||
algorithms: ['HS256'],
|
||||
issuer: this.config.jwtIssuer,
|
||||
audience: this.config.jwtAudience,
|
||||
},
|
||||
);
|
||||
// 签名正确仍需检查字段类型及必填时间,拒绝无期限或畸形身份载荷。
|
||||
if (
|
||||
typeof claims.sub !== 'string' ||
|
||||
!claims.sub ||
|
||||
claims.sub.length > 128 ||
|
||||
!Number.isSafeInteger(claims.authVersion) ||
|
||||
claims.authVersion < 0 ||
|
||||
!Number.isSafeInteger(claims.iat) ||
|
||||
!Number.isSafeInteger(claims.exp) ||
|
||||
claims.exp <= claims.iat
|
||||
) {
|
||||
throw new Error('Invalid access token claims');
|
||||
}
|
||||
return claims;
|
||||
} catch {
|
||||
// 过期、伪造或载荷错误统一返回 401,不向客户端暴露验签细节。
|
||||
throw new UnauthorizedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user