feat: 添加jwt相关校验
This commit is contained in:
parent
3f4a7e4e55
commit
caa43298ef
|
|
@ -26,7 +26,7 @@
|
||||||
"@nestjs/core": "^10.0.0",
|
"@nestjs/core": "^10.0.0",
|
||||||
"@nestjs/jwt": "^10.2.0",
|
"@nestjs/jwt": "^10.2.0",
|
||||||
"@nestjs/mapped-types": "*",
|
"@nestjs/mapped-types": "*",
|
||||||
"@nestjs/passport": "^10.0.3",
|
"@nestjs/passport": "^10.0.0",
|
||||||
"@nestjs/platform-express": "^10.0.0",
|
"@nestjs/platform-express": "^10.0.0",
|
||||||
"@nestjs/swagger": "^11.0.2",
|
"@nestjs/swagger": "^11.0.2",
|
||||||
"@nestjs/typeorm": "^10.0.2",
|
"@nestjs/typeorm": "^10.0.2",
|
||||||
|
|
@ -40,7 +40,7 @@
|
||||||
"dotenv": "^16.4.7",
|
"dotenv": "^16.4.7",
|
||||||
"install": "^0.13.0",
|
"install": "^0.13.0",
|
||||||
"mysql2": "^3.12.0",
|
"mysql2": "^3.12.0",
|
||||||
"passport": "^0.7.0",
|
"passport": "^0.6.0",
|
||||||
"passport-github2": "^0.1.12",
|
"passport-github2": "^0.1.12",
|
||||||
"passport-jwt": "^4.0.1",
|
"passport-jwt": "^4.0.1",
|
||||||
"passport-local": "^1.0.0",
|
"passport-local": "^1.0.0",
|
||||||
|
|
@ -57,7 +57,7 @@
|
||||||
"@types/express": "^4.17.17",
|
"@types/express": "^4.17.17",
|
||||||
"@types/jest": "^29.5.2",
|
"@types/jest": "^29.5.2",
|
||||||
"@types/node": "^20.3.1",
|
"@types/node": "^20.3.1",
|
||||||
"@types/passport-jwt": "^4.0.1",
|
"@types/passport-jwt": "^3.0.9",
|
||||||
"@types/supertest": "^6.0.0",
|
"@types/supertest": "^6.0.0",
|
||||||
"@typescript-eslint/eslint-plugin": "^6.0.0",
|
"@typescript-eslint/eslint-plugin": "^6.0.0",
|
||||||
"@typescript-eslint/parser": "^6.0.0",
|
"@typescript-eslint/parser": "^6.0.0",
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ specifiers:
|
||||||
'@nestjs/core': ^10.0.0
|
'@nestjs/core': ^10.0.0
|
||||||
'@nestjs/jwt': ^10.2.0
|
'@nestjs/jwt': ^10.2.0
|
||||||
'@nestjs/mapped-types': '*'
|
'@nestjs/mapped-types': '*'
|
||||||
'@nestjs/passport': ^10.0.3
|
'@nestjs/passport': ^10.0.0
|
||||||
'@nestjs/platform-express': ^10.0.0
|
'@nestjs/platform-express': ^10.0.0
|
||||||
'@nestjs/schematics': ^10.0.0
|
'@nestjs/schematics': ^10.0.0
|
||||||
'@nestjs/swagger': ^11.0.2
|
'@nestjs/swagger': ^11.0.2
|
||||||
|
|
@ -19,7 +19,7 @@ specifiers:
|
||||||
'@types/jest': ^29.5.2
|
'@types/jest': ^29.5.2
|
||||||
'@types/node': ^20.3.1
|
'@types/node': ^20.3.1
|
||||||
'@types/passport-github2': ^1.2.9
|
'@types/passport-github2': ^1.2.9
|
||||||
'@types/passport-jwt': ^4.0.1
|
'@types/passport-jwt': ^3.0.9
|
||||||
'@types/supertest': ^6.0.0
|
'@types/supertest': ^6.0.0
|
||||||
'@types/uuid': ^10.0.0
|
'@types/uuid': ^10.0.0
|
||||||
'@typescript-eslint/eslint-plugin': ^6.0.0
|
'@typescript-eslint/eslint-plugin': ^6.0.0
|
||||||
|
|
@ -35,7 +35,7 @@ specifiers:
|
||||||
install: ^0.13.0
|
install: ^0.13.0
|
||||||
jest: ^29.5.0
|
jest: ^29.5.0
|
||||||
mysql2: ^3.12.0
|
mysql2: ^3.12.0
|
||||||
passport: ^0.7.0
|
passport: ^0.6.0
|
||||||
passport-github2: ^0.1.12
|
passport-github2: ^0.1.12
|
||||||
passport-jwt: ^4.0.1
|
passport-jwt: ^4.0.1
|
||||||
passport-local: ^1.0.0
|
passport-local: ^1.0.0
|
||||||
|
|
@ -60,7 +60,7 @@ dependencies:
|
||||||
'@nestjs/core': 10.4.15_akwwzhtnoftcbx5g7sbkce2laq
|
'@nestjs/core': 10.4.15_akwwzhtnoftcbx5g7sbkce2laq
|
||||||
'@nestjs/jwt': 10.2.0_@nestjs+common@10.4.15
|
'@nestjs/jwt': 10.2.0_@nestjs+common@10.4.15
|
||||||
'@nestjs/mapped-types': 2.0.6_lhexpmbaszs2uqyaxe2vaiymm4
|
'@nestjs/mapped-types': 2.0.6_lhexpmbaszs2uqyaxe2vaiymm4
|
||||||
'@nestjs/passport': 10.0.3_zkygu43hvfc54d4x4wrnlrmh5q
|
'@nestjs/passport': 10.0.3_kyxbe6v4wnr5hwxeohqnnyhglu
|
||||||
'@nestjs/platform-express': 10.4.15_5u4hn6whjn5aawl2edmluzh4i4
|
'@nestjs/platform-express': 10.4.15_5u4hn6whjn5aawl2edmluzh4i4
|
||||||
'@nestjs/swagger': 11.0.2_f2iyopv64iutag35mprrixece4
|
'@nestjs/swagger': 11.0.2_f2iyopv64iutag35mprrixece4
|
||||||
'@nestjs/typeorm': 10.0.2_5ay6scu5luhdsc23ie3iqrw3sm
|
'@nestjs/typeorm': 10.0.2_5ay6scu5luhdsc23ie3iqrw3sm
|
||||||
|
|
@ -74,7 +74,7 @@ dependencies:
|
||||||
dotenv: 16.4.7
|
dotenv: 16.4.7
|
||||||
install: 0.13.0
|
install: 0.13.0
|
||||||
mysql2: 3.12.0
|
mysql2: 3.12.0
|
||||||
passport: 0.7.0
|
passport: 0.6.0
|
||||||
passport-github2: 0.1.12
|
passport-github2: 0.1.12
|
||||||
passport-jwt: 4.0.1
|
passport-jwt: 4.0.1
|
||||||
passport-local: 1.0.0
|
passport-local: 1.0.0
|
||||||
|
|
@ -91,7 +91,7 @@ devDependencies:
|
||||||
'@types/express': 4.17.21
|
'@types/express': 4.17.21
|
||||||
'@types/jest': 29.5.14
|
'@types/jest': 29.5.14
|
||||||
'@types/node': 20.17.12
|
'@types/node': 20.17.12
|
||||||
'@types/passport-jwt': 4.0.1
|
'@types/passport-jwt': 3.0.13
|
||||||
'@types/supertest': 6.0.2
|
'@types/supertest': 6.0.2
|
||||||
'@typescript-eslint/eslint-plugin': 6.21.0_wj7xg5aijo4gzz5szz6d7vhele
|
'@typescript-eslint/eslint-plugin': 6.21.0_wj7xg5aijo4gzz5szz6d7vhele
|
||||||
'@typescript-eslint/parser': 6.21.0_6txzh3afdjfsavlpa2fczfkiua
|
'@typescript-eslint/parser': 6.21.0_6txzh3afdjfsavlpa2fczfkiua
|
||||||
|
|
@ -1056,14 +1056,14 @@ packages:
|
||||||
reflect-metadata: 0.1.14
|
reflect-metadata: 0.1.14
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@nestjs/passport/10.0.3_zkygu43hvfc54d4x4wrnlrmh5q:
|
/@nestjs/passport/10.0.3_kyxbe6v4wnr5hwxeohqnnyhglu:
|
||||||
resolution: {integrity: sha512-znJ9Y4S8ZDVY+j4doWAJ8EuuVO7SkQN3yOBmzxbGaXbvcSwFDAdGJ+OMCg52NdzIO4tQoN4pYKx8W6M0ArfFRQ==}
|
resolution: {integrity: sha512-znJ9Y4S8ZDVY+j4doWAJ8EuuVO7SkQN3yOBmzxbGaXbvcSwFDAdGJ+OMCg52NdzIO4tQoN4pYKx8W6M0ArfFRQ==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@nestjs/common': ^8.0.0 || ^9.0.0 || ^10.0.0
|
'@nestjs/common': ^8.0.0 || ^9.0.0 || ^10.0.0
|
||||||
passport: ^0.4.0 || ^0.5.0 || ^0.6.0 || ^0.7.0
|
passport: ^0.4.0 || ^0.5.0 || ^0.6.0 || ^0.7.0
|
||||||
dependencies:
|
dependencies:
|
||||||
'@nestjs/common': 10.4.15_rcbhqa4so6dtcde4mhxkgzk6je
|
'@nestjs/common': 10.4.15_rcbhqa4so6dtcde4mhxkgzk6je
|
||||||
passport: 0.7.0
|
passport: 0.6.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@nestjs/platform-express/10.4.15_5u4hn6whjn5aawl2edmluzh4i4:
|
/@nestjs/platform-express/10.4.15_5u4hn6whjn5aawl2edmluzh4i4:
|
||||||
|
|
@ -1407,9 +1407,10 @@ packages:
|
||||||
'@types/passport-oauth2': 1.4.17
|
'@types/passport-oauth2': 1.4.17
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@types/passport-jwt/4.0.1:
|
/@types/passport-jwt/3.0.13:
|
||||||
resolution: {integrity: sha512-Y0Ykz6nWP4jpxgEUYq8NoVZeCQPo1ZndJLfapI249g1jHChvRfZRO/LS3tqu26YgAS/laI1qx98sYGz0IalRXQ==}
|
resolution: {integrity: sha512-fjHaC6Bv8EpMMqzTnHP32SXlZGaNfBPC/Po5dmRGYi2Ky7ljXPbGnOy+SxZqa6iZvFgVhoJ1915Re3m93zmcfA==}
|
||||||
dependencies:
|
dependencies:
|
||||||
|
'@types/express': 4.17.21
|
||||||
'@types/jsonwebtoken': 9.0.5
|
'@types/jsonwebtoken': 9.0.5
|
||||||
'@types/passport-strategy': 0.2.38
|
'@types/passport-strategy': 0.2.38
|
||||||
dev: true
|
dev: true
|
||||||
|
|
@ -4740,8 +4741,8 @@ packages:
|
||||||
engines: {node: '>= 0.4.0'}
|
engines: {node: '>= 0.4.0'}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/passport/0.7.0:
|
/passport/0.6.0:
|
||||||
resolution: {integrity: sha512-cPLl+qZpSc+ireUvt+IzqbED1cHHkDoVYMo30jbJIdOOjQ1MQYZBPiNvmi8UM6lJuOpTPXJGZQk0DtC4y61MYQ==}
|
resolution: {integrity: sha512-0fe+p3ZnrWRW74fe8+SvCyf4a3Pb2/h7gFkQ8yTJpAO50gDzlfjZUZTO1k5Eg9kUct22OxHLqDZoKUWRHOh9ug==}
|
||||||
engines: {node: '>= 0.4.0'}
|
engines: {node: '>= 0.4.0'}
|
||||||
dependencies:
|
dependencies:
|
||||||
passport-strategy: 1.0.0
|
passport-strategy: 1.0.0
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,28 @@
|
||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
|
import { AuthService } from './auth.service';
|
||||||
|
import { UserModule } from '../user/user.module';
|
||||||
import { PassportModule } from '@nestjs/passport';
|
import { PassportModule } from '@nestjs/passport';
|
||||||
import { AuthController } from './auth.controller';
|
import { JwtModule } from '@nestjs/jwt';
|
||||||
import { GitHubStrategy } from './github.strategy';
|
import { JwtStrategy } from './strategies/jwt.strategy';
|
||||||
|
import { APP_GUARD } from '@nestjs/core';
|
||||||
|
import { JwtAuthGuard } from './guards/jwt-auth.guard';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [PassportModule],
|
imports: [
|
||||||
controllers: [AuthController],
|
UserModule,
|
||||||
providers: [GitHubStrategy],
|
PassportModule.register({ defaultStrategy: 'jwt' }),
|
||||||
|
JwtModule.register({
|
||||||
|
secret: 'your-secret-key', // 使用与 JwtStrategy 相同的密钥
|
||||||
|
signOptions: { expiresIn: '1h' },
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
AuthService,
|
||||||
|
JwtStrategy,
|
||||||
|
{
|
||||||
|
provide: APP_GUARD,
|
||||||
|
useClass: JwtAuthGuard,
|
||||||
|
},
|
||||||
|
],
|
||||||
})
|
})
|
||||||
export class AuthModule {}
|
export class AuthModule {}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
import { SetMetadata } from '@nestjs/common';
|
||||||
|
|
||||||
|
export const IS_PUBLIC_KEY = 'isPublic';
|
||||||
|
export const Public = () => SetMetadata(IS_PUBLIC_KEY, true);
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
import {
|
||||||
|
Injectable,
|
||||||
|
ExecutionContext,
|
||||||
|
UnauthorizedException,
|
||||||
|
} from '@nestjs/common';
|
||||||
|
import { AuthGuard } from '@nestjs/passport';
|
||||||
|
import { Reflector } from '@nestjs/core';
|
||||||
|
import { IS_PUBLIC_KEY } from '../decorators/public.decorator';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class JwtAuthGuard extends AuthGuard('jwt') {
|
||||||
|
constructor(private reflector: Reflector) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
canActivate(
|
||||||
|
context: ExecutionContext,
|
||||||
|
): boolean | Promise<boolean> | Observable<boolean> {
|
||||||
|
// 检查是否有Public装饰器
|
||||||
|
const isPublic = this.reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [
|
||||||
|
context.getHandler(),
|
||||||
|
context.getClass(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (isPublic) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.canActivate(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleRequest(err: any, user: any) {
|
||||||
|
if (err || !user) {
|
||||||
|
throw new UnauthorizedException('请先登录');
|
||||||
|
}
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
import { ExtractJwt, Strategy } from 'passport-jwt';
|
||||||
|
import { PassportStrategy } from '@nestjs/passport';
|
||||||
|
import { Injectable, UnauthorizedException } from '@nestjs/common';
|
||||||
|
import { UserService } from '../../user/user.service';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class JwtStrategy extends PassportStrategy(Strategy) {
|
||||||
|
constructor(private userService: UserService) {
|
||||||
|
super({
|
||||||
|
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
|
||||||
|
ignoreExpiration: false,
|
||||||
|
secretOrKey: 'your-secret-key', // 必须与 JwtModule 中的密钥相同
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async validate(payload: any) {
|
||||||
|
const user = await this.userService.findUserByUsername(payload.username);
|
||||||
|
if (!user) {
|
||||||
|
throw new UnauthorizedException();
|
||||||
|
}
|
||||||
|
if (!user.isActive) {
|
||||||
|
throw new UnauthorizedException('用户已被禁用');
|
||||||
|
}
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
import {
|
||||||
|
ExceptionFilter,
|
||||||
|
Catch,
|
||||||
|
ArgumentsHost,
|
||||||
|
HttpException,
|
||||||
|
} from '@nestjs/common';
|
||||||
|
import { Response } from 'express';
|
||||||
|
|
||||||
|
@Catch(HttpException)
|
||||||
|
export class HttpExceptionFilter implements ExceptionFilter {
|
||||||
|
catch(exception: HttpException, host: ArgumentsHost) {
|
||||||
|
const ctx = host.switchToHttp();
|
||||||
|
const response = ctx.getResponse<Response>();
|
||||||
|
const status = exception.getStatus();
|
||||||
|
const exceptionResponse = exception.getResponse();
|
||||||
|
|
||||||
|
let message = '请求失败';
|
||||||
|
if (typeof exceptionResponse === 'string') {
|
||||||
|
message = exceptionResponse;
|
||||||
|
} else if (
|
||||||
|
typeof exceptionResponse === 'object' &&
|
||||||
|
'message' in exceptionResponse
|
||||||
|
) {
|
||||||
|
message = (exceptionResponse as { message: string }).message;
|
||||||
|
}
|
||||||
|
|
||||||
|
response.status(status).json({
|
||||||
|
code: status,
|
||||||
|
success: false,
|
||||||
|
message,
|
||||||
|
data: null,
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
import {
|
||||||
|
Injectable,
|
||||||
|
NestInterceptor,
|
||||||
|
ExecutionContext,
|
||||||
|
CallHandler,
|
||||||
|
} from '@nestjs/common';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
import { map } from 'rxjs/operators';
|
||||||
|
|
||||||
|
export interface Response<T> {
|
||||||
|
code: number;
|
||||||
|
success: boolean;
|
||||||
|
data: T;
|
||||||
|
message: string;
|
||||||
|
timestamp: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class TransformInterceptor<T>
|
||||||
|
implements NestInterceptor<T, Response<T>>
|
||||||
|
{
|
||||||
|
intercept(
|
||||||
|
context: ExecutionContext,
|
||||||
|
next: CallHandler,
|
||||||
|
): Observable<Response<T>> {
|
||||||
|
return next.handle().pipe(
|
||||||
|
map((data) => ({
|
||||||
|
code: context.switchToHttp().getResponse().statusCode,
|
||||||
|
success: true,
|
||||||
|
data,
|
||||||
|
message: '请求成功',
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,6 +3,8 @@ import { AppModule } from './app.module';
|
||||||
import * as mysql from 'mysql2/promise';
|
import * as mysql from 'mysql2/promise';
|
||||||
import { ConfigService } from '@nestjs/config';
|
import { ConfigService } from '@nestjs/config';
|
||||||
import * as dotenv from 'dotenv';
|
import * as dotenv from 'dotenv';
|
||||||
|
import { HttpExceptionFilter } from './common/filters/http-exception.filter';
|
||||||
|
import { TransformInterceptor } from './common/interceptors/transform.interceptor';
|
||||||
|
|
||||||
dotenv.config();
|
dotenv.config();
|
||||||
|
|
||||||
|
|
@ -26,6 +28,12 @@ async function bootstrap() {
|
||||||
credentials: true, // 允许带有凭证(cookies)的请求
|
credentials: true, // 允许带有凭证(cookies)的请求
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 注册全局异常过滤器
|
||||||
|
app.useGlobalFilters(new HttpExceptionFilter());
|
||||||
|
|
||||||
|
// 注册全局响应转换拦截器
|
||||||
|
app.useGlobalInterceptors(new TransformInterceptor());
|
||||||
|
|
||||||
await app.listen(3030);
|
await app.listen(3030);
|
||||||
}
|
}
|
||||||
bootstrap();
|
bootstrap();
|
||||||
|
|
|
||||||
|
|
@ -16,4 +16,10 @@ export class User {
|
||||||
|
|
||||||
@Column({ default: true })
|
@Column({ default: true })
|
||||||
isActive: boolean;
|
isActive: boolean;
|
||||||
|
|
||||||
|
@Column({ nullable: true })
|
||||||
|
refreshToken: string;
|
||||||
|
|
||||||
|
@Column({ nullable: true })
|
||||||
|
refreshTokenExpires: Date;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,26 @@
|
||||||
import { Controller, Post, Body, UnauthorizedException } from '@nestjs/common';
|
import {
|
||||||
|
Controller,
|
||||||
|
Post,
|
||||||
|
Body,
|
||||||
|
UnauthorizedException,
|
||||||
|
BadRequestException,
|
||||||
|
Get,
|
||||||
|
} from '@nestjs/common';
|
||||||
import { UserService } from '../user/user.service';
|
import { UserService } from '../user/user.service';
|
||||||
|
import { Public } from '../auth/decorators/public.decorator';
|
||||||
|
|
||||||
@Controller('user')
|
@Controller('user')
|
||||||
export class UserController {
|
export class UserController {
|
||||||
constructor(private readonly userService: UserService) {}
|
constructor(private readonly userService: UserService) {}
|
||||||
|
|
||||||
|
@Public()
|
||||||
@Post('register')
|
@Post('register')
|
||||||
async register(
|
async register(
|
||||||
@Body() body: { username: string; password: string; email: string },
|
@Body() body: { username: string; password: string; email: string },
|
||||||
) {
|
) {
|
||||||
|
if (!body.password) {
|
||||||
|
throw new BadRequestException('Password is required');
|
||||||
|
}
|
||||||
const { username, password, email } = body;
|
const { username, password, email } = body;
|
||||||
const user = await this.userService.createUser(username, password, email);
|
const user = await this.userService.createUser(username, password, email);
|
||||||
const token = await this.userService.generateToken(user);
|
const token = await this.userService.generateToken(user);
|
||||||
|
|
@ -19,6 +31,7 @@ export class UserController {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Public()
|
||||||
@Post('login')
|
@Post('login')
|
||||||
async login(@Body() body: { username: string; password: string }) {
|
async login(@Body() body: { username: string; password: string }) {
|
||||||
const { username, password } = body;
|
const { username, password } = body;
|
||||||
|
|
@ -40,4 +53,10 @@ export class UserController {
|
||||||
...token,
|
...token,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Public()
|
||||||
|
@Post('refresh-token')
|
||||||
|
async refreshToken(@Body() body: { refresh_token: string }) {
|
||||||
|
return this.userService.refreshAccessToken(body.refresh_token);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ import { UserController } from './user.controller';
|
||||||
imports: [
|
imports: [
|
||||||
TypeOrmModule.forFeature([User]),
|
TypeOrmModule.forFeature([User]),
|
||||||
JwtModule.register({
|
JwtModule.register({
|
||||||
secret: 'your-secret-key', // 建议从环境变量中读取
|
secret: 'your-secret-key', // 必须与 JwtStrategy 中的密钥相同
|
||||||
signOptions: { expiresIn: '24h' },
|
signOptions: { expiresIn: '24h' },
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable, UnauthorizedException } from '@nestjs/common';
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
import { Repository } from 'typeorm';
|
import { Repository } from 'typeorm';
|
||||||
import { JwtService } from '@nestjs/jwt';
|
import { JwtService } from '@nestjs/jwt';
|
||||||
import { User } from './entities/user.entity';
|
import { User } from './entities/user.entity';
|
||||||
import * as bcrypt from 'bcrypt';
|
import * as bcrypt from 'bcrypt';
|
||||||
|
import { MoreThan } from 'typeorm';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class UserService {
|
export class UserService {
|
||||||
|
|
@ -14,7 +15,6 @@ export class UserService {
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async createUser(username: string, password: string, email: string) {
|
async createUser(username: string, password: string, email: string) {
|
||||||
console.log(username, password, email);
|
|
||||||
if (!password) {
|
if (!password) {
|
||||||
throw new Error('Password is required');
|
throw new Error('Password is required');
|
||||||
}
|
}
|
||||||
|
|
@ -43,12 +43,79 @@ export class UserService {
|
||||||
}
|
}
|
||||||
|
|
||||||
async generateToken(user: User) {
|
async generateToken(user: User) {
|
||||||
const payload = {
|
const accessTokenExpiresIn = 3600; // 1小时,单位:秒
|
||||||
sub: user.id,
|
const refreshTokenExpiresIn = 7 * 24 * 3600; // 7天,单位:秒
|
||||||
username: user.username,
|
|
||||||
};
|
const accessToken = this.jwtService.sign(
|
||||||
|
{
|
||||||
|
sub: user.id,
|
||||||
|
username: user.username,
|
||||||
|
},
|
||||||
|
{ expiresIn: `${accessTokenExpiresIn}s` },
|
||||||
|
);
|
||||||
|
|
||||||
|
const refreshToken = this.jwtService.sign(
|
||||||
|
{
|
||||||
|
sub: user.id,
|
||||||
|
},
|
||||||
|
{ expiresIn: `${refreshTokenExpiresIn}s` },
|
||||||
|
);
|
||||||
|
|
||||||
|
// 保存 refresh token 到数据库
|
||||||
|
const refreshTokenExpires = new Date();
|
||||||
|
refreshTokenExpires.setSeconds(
|
||||||
|
refreshTokenExpires.getSeconds() + refreshTokenExpiresIn,
|
||||||
|
);
|
||||||
|
|
||||||
|
await this.userRepository.update(user.id, {
|
||||||
|
refreshToken,
|
||||||
|
refreshTokenExpires,
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
access_token: this.jwtService.sign(payload),
|
access_token: accessToken,
|
||||||
|
refresh_token: refreshToken,
|
||||||
|
expires_in: accessTokenExpiresIn,
|
||||||
|
expires_at: Date.now() + accessTokenExpiresIn * 1000,
|
||||||
|
refresh_token_expires_in: refreshTokenExpiresIn,
|
||||||
|
refresh_token_expires_at: refreshTokenExpires.getTime(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async refreshAccessToken(refreshToken: string) {
|
||||||
|
try {
|
||||||
|
// 验证 refresh token
|
||||||
|
const payload = this.jwtService.verify(refreshToken);
|
||||||
|
const user = await this.userRepository.findOne({
|
||||||
|
where: {
|
||||||
|
id: payload.sub,
|
||||||
|
refreshToken,
|
||||||
|
refreshTokenExpires: MoreThan(new Date()),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
throw new UnauthorizedException('Invalid refresh token');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成新的 access token
|
||||||
|
const accessTokenExpiresIn = 3600; // 1小时,单位:秒
|
||||||
|
|
||||||
|
const accessToken = this.jwtService.sign(
|
||||||
|
{
|
||||||
|
sub: user.id,
|
||||||
|
username: user.username,
|
||||||
|
},
|
||||||
|
{ expiresIn: `${accessTokenExpiresIn}s` },
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
access_token: accessToken,
|
||||||
|
expires_in: accessTokenExpiresIn,
|
||||||
|
expires_at: Date.now() + accessTokenExpiresIn * 1000,
|
||||||
|
};
|
||||||
|
} catch {
|
||||||
|
throw new UnauthorizedException('Invalid refresh token');
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue