完成jwt鉴权
This commit is contained in:
@@ -31,6 +31,8 @@
|
|||||||
"class-transformer": "^0.5.1",
|
"class-transformer": "^0.5.1",
|
||||||
"class-validator": "^0.14.2",
|
"class-validator": "^0.14.2",
|
||||||
"jsonwebtoken": "^9.0.2",
|
"jsonwebtoken": "^9.0.2",
|
||||||
|
"passport": "^0.7.0",
|
||||||
|
"passport-jwt": "^4.0.1",
|
||||||
"pg": "^8.15.6",
|
"pg": "^8.15.6",
|
||||||
"reflect-metadata": "^0.2.0",
|
"reflect-metadata": "^0.2.0",
|
||||||
"rxjs": "^7.8.1",
|
"rxjs": "^7.8.1",
|
||||||
@@ -44,6 +46,7 @@
|
|||||||
"@types/express": "^5.0.0",
|
"@types/express": "^5.0.0",
|
||||||
"@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/supertest": "^6.0.0",
|
"@types/supertest": "^6.0.0",
|
||||||
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
||||||
"@typescript-eslint/parser": "^8.0.0",
|
"@typescript-eslint/parser": "^8.0.0",
|
||||||
|
|||||||
40
tone-page-server/pnpm-lock.yaml
generated
40
tone-page-server/pnpm-lock.yaml
generated
@@ -41,6 +41,12 @@ importers:
|
|||||||
jsonwebtoken:
|
jsonwebtoken:
|
||||||
specifier: ^9.0.2
|
specifier: ^9.0.2
|
||||||
version: 9.0.2
|
version: 9.0.2
|
||||||
|
passport:
|
||||||
|
specifier: ^0.7.0
|
||||||
|
version: 0.7.0
|
||||||
|
passport-jwt:
|
||||||
|
specifier: ^4.0.1
|
||||||
|
version: 4.0.1
|
||||||
pg:
|
pg:
|
||||||
specifier: ^8.15.6
|
specifier: ^8.15.6
|
||||||
version: 8.15.6
|
version: 8.15.6
|
||||||
@@ -75,6 +81,9 @@ importers:
|
|||||||
'@types/node':
|
'@types/node':
|
||||||
specifier: ^20.3.1
|
specifier: ^20.3.1
|
||||||
version: 20.17.31
|
version: 20.17.31
|
||||||
|
'@types/passport-jwt':
|
||||||
|
specifier: ^4.0.1
|
||||||
|
version: 4.0.1
|
||||||
'@types/supertest':
|
'@types/supertest':
|
||||||
specifier: ^6.0.0
|
specifier: ^6.0.0
|
||||||
version: 6.0.3
|
version: 6.0.3
|
||||||
@@ -693,6 +702,15 @@ packages:
|
|||||||
'@types/node@20.17.31':
|
'@types/node@20.17.31':
|
||||||
resolution: {integrity: sha512-quODOCNXQAbNf1Q7V+fI8WyErOCh0D5Yd31vHnKu4GkSztGQ7rlltAaqXhHhLl33tlVyUXs2386MkANSwgDn6A==}
|
resolution: {integrity: sha512-quODOCNXQAbNf1Q7V+fI8WyErOCh0D5Yd31vHnKu4GkSztGQ7rlltAaqXhHhLl33tlVyUXs2386MkANSwgDn6A==}
|
||||||
|
|
||||||
|
'@types/passport-jwt@4.0.1':
|
||||||
|
resolution: {integrity: sha512-Y0Ykz6nWP4jpxgEUYq8NoVZeCQPo1ZndJLfapI249g1jHChvRfZRO/LS3tqu26YgAS/laI1qx98sYGz0IalRXQ==}
|
||||||
|
|
||||||
|
'@types/passport-strategy@0.2.38':
|
||||||
|
resolution: {integrity: sha512-GC6eMqqojOooq993Tmnmp7AUTbbQSgilyvpCYQjT+H6JfG/g6RGc7nXEniZlp0zyKJ0WUdOiZWLBZft9Yug1uA==}
|
||||||
|
|
||||||
|
'@types/passport@1.0.17':
|
||||||
|
resolution: {integrity: sha512-aciLyx+wDwT2t2/kJGJR2AEeBz0nJU4WuRX04Wu9Dqc5lSUtwu0WERPHYsLhF9PtseiAMPBGNUOtFjxZ56prsg==}
|
||||||
|
|
||||||
'@types/qs@6.9.18':
|
'@types/qs@6.9.18':
|
||||||
resolution: {integrity: sha512-kK7dgTYDyGqS+e2Q4aK9X3D7q234CIZ1Bv0q/7Z5IwRDoADNU81xXJK/YVyLbLTZCoIwUoDoffFeF+p/eIklAA==}
|
resolution: {integrity: sha512-kK7dgTYDyGqS+e2Q4aK9X3D7q234CIZ1Bv0q/7Z5IwRDoADNU81xXJK/YVyLbLTZCoIwUoDoffFeF+p/eIklAA==}
|
||||||
|
|
||||||
@@ -2278,6 +2296,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==}
|
resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==}
|
||||||
engines: {node: '>= 0.8'}
|
engines: {node: '>= 0.8'}
|
||||||
|
|
||||||
|
passport-jwt@4.0.1:
|
||||||
|
resolution: {integrity: sha512-UCKMDYhNuGOBE9/9Ycuoyh7vP6jpeTp/+sfMJl7nLff/t6dps+iaeE0hhNkKN8/HZHcJ7lCdOyDxHdDoxoSvdQ==}
|
||||||
|
|
||||||
passport-strategy@1.0.0:
|
passport-strategy@1.0.0:
|
||||||
resolution: {integrity: sha512-CB97UUvDKJde2V0KDWWB3lyf6PC3FaZP7YxZ2G8OAtn9p4HI9j9JLP9qjOGZFvyl8uwNT8qM+hGnz/n16NI7oA==}
|
resolution: {integrity: sha512-CB97UUvDKJde2V0KDWWB3lyf6PC3FaZP7YxZ2G8OAtn9p4HI9j9JLP9qjOGZFvyl8uwNT8qM+hGnz/n16NI7oA==}
|
||||||
engines: {node: '>= 0.4.0'}
|
engines: {node: '>= 0.4.0'}
|
||||||
@@ -3859,6 +3880,20 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
undici-types: 6.19.8
|
undici-types: 6.19.8
|
||||||
|
|
||||||
|
'@types/passport-jwt@4.0.1':
|
||||||
|
dependencies:
|
||||||
|
'@types/jsonwebtoken': 9.0.7
|
||||||
|
'@types/passport-strategy': 0.2.38
|
||||||
|
|
||||||
|
'@types/passport-strategy@0.2.38':
|
||||||
|
dependencies:
|
||||||
|
'@types/express': 5.0.1
|
||||||
|
'@types/passport': 1.0.17
|
||||||
|
|
||||||
|
'@types/passport@1.0.17':
|
||||||
|
dependencies:
|
||||||
|
'@types/express': 5.0.1
|
||||||
|
|
||||||
'@types/qs@6.9.18': {}
|
'@types/qs@6.9.18': {}
|
||||||
|
|
||||||
'@types/range-parser@1.2.7': {}
|
'@types/range-parser@1.2.7': {}
|
||||||
@@ -5750,6 +5785,11 @@ snapshots:
|
|||||||
|
|
||||||
parseurl@1.3.3: {}
|
parseurl@1.3.3: {}
|
||||||
|
|
||||||
|
passport-jwt@4.0.1:
|
||||||
|
dependencies:
|
||||||
|
jsonwebtoken: 9.0.2
|
||||||
|
passport-strategy: 1.0.0
|
||||||
|
|
||||||
passport-strategy@1.0.0: {}
|
passport-strategy@1.0.0: {}
|
||||||
|
|
||||||
passport@0.7.0:
|
passport@0.7.0:
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import { PassportModule } from '@nestjs/passport';
|
|||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
ConfigModule.forRoot(),
|
ConfigModule.forRoot({ isGlobal: true }),
|
||||||
TypeOrmModule.forRoot({
|
TypeOrmModule.forRoot({
|
||||||
type: 'postgres',
|
type: 'postgres',
|
||||||
host: process.env.DATABASE_HOST,
|
host: process.env.DATABASE_HOST,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { BadRequestException, Body, Controller, Post } from '@nestjs/common';
|
import { BadRequestException, Body, Controller, Get, Post, Request } from '@nestjs/common';
|
||||||
import { LoginDto } from './dto/login.dto';
|
import { LoginDto } from './dto/login.dto';
|
||||||
import { AuthService } from './auth.service';
|
import { AuthService } from './auth.service';
|
||||||
|
|
||||||
|
|||||||
@@ -1,24 +1,40 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { forwardRef, Module } from '@nestjs/common';
|
||||||
import { AuthController } from './auth.controller';
|
import { AuthController } from './auth.controller';
|
||||||
import { AuthService } from './auth.service';
|
import { AuthService } from './auth.service';
|
||||||
import { UserModule } from 'src/user/user.module';
|
import { UserModule } from 'src/user/user.module';
|
||||||
import { JwtModule } from '@nestjs/jwt';
|
import { JwtModule } from '@nestjs/jwt';
|
||||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
import { UserSession } from 'src/user/entities/user-session.entity';
|
import { UserSession } from 'src/user/entities/user-session.entity';
|
||||||
import { UserSessionService } from 'src/user/services/user-session.service';
|
import { PassportModule } from '@nestjs/passport';
|
||||||
|
import { JwtStrategy } from './strategies/jwt.strategy';
|
||||||
|
import { ConfigModule, ConfigService } from '@nestjs/config';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
UserModule,
|
ConfigModule,
|
||||||
|
forwardRef(() => UserModule),
|
||||||
TypeOrmModule.forFeature([UserSession]),
|
TypeOrmModule.forFeature([UserSession]),
|
||||||
JwtModule.register({
|
PassportModule.register({ defaultStrategy: 'jwt' }),
|
||||||
secret: process.env.JWT_SECRET || 'tone-page',
|
JwtModule.registerAsync({
|
||||||
signOptions: {
|
imports: [ConfigModule],
|
||||||
expiresIn: process.env.EXPIRES_IN || '1d',
|
inject: [ConfigService],
|
||||||
}
|
useFactory: (configService: ConfigService) => ({
|
||||||
|
secret: configService.get<string>('JWT_SECRET', 'tone-page'),
|
||||||
|
signOptions: {
|
||||||
|
expiresIn: configService.get<string>('JWT_EXPIRES_IN', '1d'),
|
||||||
|
},
|
||||||
|
})
|
||||||
})
|
})
|
||||||
],
|
],
|
||||||
controllers: [AuthController],
|
controllers: [AuthController],
|
||||||
providers: [AuthService, UserSessionService],
|
providers: [
|
||||||
|
AuthService,
|
||||||
|
JwtStrategy,
|
||||||
|
],
|
||||||
|
exports: [
|
||||||
|
PassportModule,
|
||||||
|
JwtStrategy,
|
||||||
|
AuthService,
|
||||||
|
]
|
||||||
})
|
})
|
||||||
export class AuthModule { }
|
export class AuthModule { }
|
||||||
|
|||||||
33
tone-page-server/src/auth/strategies/jwt.strategy.ts
Normal file
33
tone-page-server/src/auth/strategies/jwt.strategy.ts
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import { Injectable, UnauthorizedException } from "@nestjs/common";
|
||||||
|
import { ConfigService } from "@nestjs/config";
|
||||||
|
import { PassportStrategy } from "@nestjs/passport";
|
||||||
|
import { ExtractJwt, Strategy } from "passport-jwt";
|
||||||
|
import { UserSessionService } from "src/user/services/user-session.service";
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class JwtStrategy extends PassportStrategy(Strategy, 'jwt') {
|
||||||
|
constructor(
|
||||||
|
private readonly userSessionService: UserSessionService,
|
||||||
|
private readonly configService: ConfigService,
|
||||||
|
) {
|
||||||
|
super({
|
||||||
|
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
|
||||||
|
ignoreExpiration: false,
|
||||||
|
secretOrKey: configService.get<string>('JWT_SECRET', 'tone-page'),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async validate(payload: any) {
|
||||||
|
const { userId, sessionId } = payload ?? {};
|
||||||
|
|
||||||
|
const isValidSession = await this.userSessionService.isSessionValid(userId, sessionId);
|
||||||
|
if (!isValidSession) {
|
||||||
|
throw new UnauthorizedException('登录凭证已过期,请重新登录');
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
userId,
|
||||||
|
sessionId,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,4 +18,16 @@ export class UserSessionService {
|
|||||||
});
|
});
|
||||||
return await this.userSessionRepository.save(session);
|
return await this.userSessionRepository.save(session);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async isSessionValid(userId: string, sessionId: string): Promise<boolean> {
|
||||||
|
const session = await this.userSessionRepository.findOne({
|
||||||
|
where: {
|
||||||
|
userId,
|
||||||
|
sessionId,
|
||||||
|
deletedAt: null,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return !!session;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,17 @@
|
|||||||
import { Controller } from '@nestjs/common';
|
import { Controller, Get, Injectable, UseGuards } from '@nestjs/common';
|
||||||
|
import { UserService } from './user.service';
|
||||||
|
import { AuthGuard } from '@nestjs/passport';
|
||||||
|
|
||||||
@Controller('user')
|
@Controller('user')
|
||||||
export class UserController {}
|
export class UserController {
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private readonly userService: UserService
|
||||||
|
) { }
|
||||||
|
|
||||||
|
@UseGuards(AuthGuard('jwt'))
|
||||||
|
@Get('me')
|
||||||
|
async getMe() {
|
||||||
|
return 'ok';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,14 +1,19 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { forwardRef, Module } from '@nestjs/common';
|
||||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
import { User } from './entities/user.entity';
|
import { User } from './entities/user.entity';
|
||||||
import { UserController } from './user.controller';
|
import { UserController } from './user.controller';
|
||||||
import { UserService } from './user.service';
|
import { UserService } from './user.service';
|
||||||
import { UserSession } from './entities/user-session.entity';
|
import { UserSession } from './entities/user-session.entity';
|
||||||
|
import { AuthModule } from 'src/auth/auth.module';
|
||||||
|
import { UserSessionService } from './services/user-session.service';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [TypeOrmModule.forFeature([User, UserSession])],
|
imports: [
|
||||||
|
TypeOrmModule.forFeature([User, UserSession]),
|
||||||
|
forwardRef(() => AuthModule),// 解决循环依赖问题
|
||||||
|
],
|
||||||
controllers: [UserController],
|
controllers: [UserController],
|
||||||
providers: [UserService],
|
providers: [UserService, UserSessionService],
|
||||||
exports: [UserService],
|
exports: [UserService, UserSessionService],
|
||||||
})
|
})
|
||||||
export class UserModule { }
|
export class UserModule { }
|
||||||
|
|||||||
Reference in New Issue
Block a user