refactor: 重构后端鉴权方式

This commit is contained in:
2025-12-17 15:33:25 +08:00
parent fdc8da2308
commit 8c2a50127a
18 changed files with 97 additions and 264 deletions

View File

@@ -28,9 +28,7 @@
"@nestjs/common": "^10.0.0", "@nestjs/common": "^10.0.0",
"@nestjs/config": "^4.0.2", "@nestjs/config": "^4.0.2",
"@nestjs/core": "^10.0.0", "@nestjs/core": "^10.0.0",
"@nestjs/jwt": "^11.0.0",
"@nestjs/mapped-types": "*", "@nestjs/mapped-types": "*",
"@nestjs/passport": "^11.0.5",
"@nestjs/platform-express": "^10.0.0", "@nestjs/platform-express": "^10.0.0",
"@nestjs/throttler": "^6.4.0", "@nestjs/throttler": "^6.4.0",
"@nestjs/typeorm": "^11.0.0", "@nestjs/typeorm": "^11.0.0",
@@ -40,8 +38,6 @@
"class-validator": "^0.14.2", "class-validator": "^0.14.2",
"cookie-parser": "^1.4.7", "cookie-parser": "^1.4.7",
"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",
@@ -56,7 +52,6 @@
"@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",

View File

@@ -32,15 +32,9 @@ importers:
'@nestjs/core': '@nestjs/core':
specifier: ^10.0.0 specifier: ^10.0.0
version: 10.4.17(@nestjs/common@10.4.17(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.17)(reflect-metadata@0.2.2)(rxjs@7.8.2) version: 10.4.17(@nestjs/common@10.4.17(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.17)(reflect-metadata@0.2.2)(rxjs@7.8.2)
'@nestjs/jwt':
specifier: ^11.0.0
version: 11.0.0(@nestjs/common@10.4.17(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))
'@nestjs/mapped-types': '@nestjs/mapped-types':
specifier: '*' specifier: '*'
version: 2.1.0(@nestjs/common@10.4.17(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2) version: 2.1.0(@nestjs/common@10.4.17(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)
'@nestjs/passport':
specifier: ^11.0.5
version: 11.0.5(@nestjs/common@10.4.17(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(passport@0.7.0)
'@nestjs/platform-express': '@nestjs/platform-express':
specifier: ^10.0.0 specifier: ^10.0.0
version: 10.4.17(@nestjs/common@10.4.17(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.17) version: 10.4.17(@nestjs/common@10.4.17(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.17)
@@ -68,12 +62,6 @@ 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
@@ -111,9 +99,6 @@ 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
@@ -606,11 +591,6 @@ packages:
'@nestjs/websockets': '@nestjs/websockets':
optional: true optional: true
'@nestjs/jwt@11.0.0':
resolution: {integrity: sha512-v7YRsW3Xi8HNTsO+jeHSEEqelX37TVWgwt+BcxtkG/OfXJEOs6GZdbdza200d6KqId1pJQZ6UPj1F0M6E+mxaA==}
peerDependencies:
'@nestjs/common': ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0
'@nestjs/mapped-types@2.1.0': '@nestjs/mapped-types@2.1.0':
resolution: {integrity: sha512-W+n+rM69XsFdwORF11UqJahn4J3xi4g/ZEOlJNL6KoW5ygWSmBB2p0S2BZ4FQeS/NDH72e6xIcu35SfJnE8bXw==} resolution: {integrity: sha512-W+n+rM69XsFdwORF11UqJahn4J3xi4g/ZEOlJNL6KoW5ygWSmBB2p0S2BZ4FQeS/NDH72e6xIcu35SfJnE8bXw==}
peerDependencies: peerDependencies:
@@ -624,12 +604,6 @@ packages:
class-validator: class-validator:
optional: true optional: true
'@nestjs/passport@11.0.5':
resolution: {integrity: sha512-ulQX6mbjlws92PIM15Naes4F4p2JoxGnIJuUsdXQPT+Oo2sqQmENEZXM7eYuimocfHnKlcfZOuyzbA33LwUlOQ==}
peerDependencies:
'@nestjs/common': ^10.0.0 || ^11.0.0
passport: ^0.5.0 || ^0.6.0 || ^0.7.0
'@nestjs/platform-express@10.4.17': '@nestjs/platform-express@10.4.17':
resolution: {integrity: sha512-ovn4Wxney3QGBrqNPv0QLcCuH5QoAi6pb/GNWAz6B/NmBjZbs9/zl4a2beGDA2SaYre9w43YbfmHTm17PneP9w==} resolution: {integrity: sha512-ovn4Wxney3QGBrqNPv0QLcCuH5QoAi6pb/GNWAz6B/NmBjZbs9/zl4a2beGDA2SaYre9w43YbfmHTm17PneP9w==}
peerDependencies: peerDependencies:
@@ -798,9 +772,6 @@ packages:
'@types/json-schema@7.0.15': '@types/json-schema@7.0.15':
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
'@types/jsonwebtoken@9.0.7':
resolution: {integrity: sha512-ugo316mmTYBl2g81zDFnZ7cfxlut3o+/EQdaP7J8QN2kY6lJ22hmQYCK5EHcJHbrW+dkCGSCPgbG8JtYj6qSrg==}
'@types/methods@1.1.4': '@types/methods@1.1.4':
resolution: {integrity: sha512-ymXWVrDiCxTBE3+RIrrP533E70eA+9qu7zdWoHuOmGujkYtzf4HQF96b8nwHLqhuf4ykX61IGRIB38CC6/sImQ==} resolution: {integrity: sha512-ymXWVrDiCxTBE3+RIrrP533E70eA+9qu7zdWoHuOmGujkYtzf4HQF96b8nwHLqhuf4ykX61IGRIB38CC6/sImQ==}
@@ -816,15 +787,6 @@ packages:
'@types/node@22.15.14': '@types/node@22.15.14':
resolution: {integrity: sha512-BL1eyu/XWsFGTtDWOYULQEs4KR0qdtYfCxYAUYRoB7JP7h9ETYLgQTww6kH8Sj2C0pFGgrpM0XKv6/kbIzYJ1g==} resolution: {integrity: sha512-BL1eyu/XWsFGTtDWOYULQEs4KR0qdtYfCxYAUYRoB7JP7h9ETYLgQTww6kH8Sj2C0pFGgrpM0XKv6/kbIzYJ1g==}
'@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==}
@@ -2531,17 +2493,6 @@ 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:
resolution: {integrity: sha512-CB97UUvDKJde2V0KDWWB3lyf6PC3FaZP7YxZ2G8OAtn9p4HI9j9JLP9qjOGZFvyl8uwNT8qM+hGnz/n16NI7oA==}
engines: {node: '>= 0.4.0'}
passport@0.7.0:
resolution: {integrity: sha512-cPLl+qZpSc+ireUvt+IzqbED1cHHkDoVYMo30jbJIdOOjQ1MQYZBPiNvmi8UM6lJuOpTPXJGZQk0DtC4y61MYQ==}
engines: {node: '>= 0.4.0'}
path-exists@4.0.0: path-exists@4.0.0:
resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
engines: {node: '>=8'} engines: {node: '>=8'}
@@ -2574,9 +2525,6 @@ packages:
pause-stream@0.0.11: pause-stream@0.0.11:
resolution: {integrity: sha512-e3FBlXLmN/D1S+zHzanP4E/4Z60oFAa3O051qt1pxa7DEJWKAyil6upYVXCWadEnuoqa4Pkc9oUx9zsxYeRv8A==} resolution: {integrity: sha512-e3FBlXLmN/D1S+zHzanP4E/4Z60oFAa3O051qt1pxa7DEJWKAyil6upYVXCWadEnuoqa4Pkc9oUx9zsxYeRv8A==}
pause@0.0.1:
resolution: {integrity: sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg==}
peek-readable@7.0.0: peek-readable@7.0.0:
resolution: {integrity: sha512-nri2TO5JE3/mRryik9LlHFT53cgHfRK0Lt0BAZQXku/AW3E6XLt2GaY8siWi7dvW/m1z0ecn+J+bpDa9ZN3IsQ==} resolution: {integrity: sha512-nri2TO5JE3/mRryik9LlHFT53cgHfRK0Lt0BAZQXku/AW3E6XLt2GaY8siWi7dvW/m1z0ecn+J+bpDa9ZN3IsQ==}
engines: {node: '>=18'} engines: {node: '>=18'}
@@ -4135,12 +4083,6 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- encoding - encoding
'@nestjs/jwt@11.0.0(@nestjs/common@10.4.17(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))':
dependencies:
'@nestjs/common': 10.4.17(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2)
'@types/jsonwebtoken': 9.0.7
jsonwebtoken: 9.0.2
'@nestjs/mapped-types@2.1.0(@nestjs/common@10.4.17(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)': '@nestjs/mapped-types@2.1.0(@nestjs/common@10.4.17(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)':
dependencies: dependencies:
'@nestjs/common': 10.4.17(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2) '@nestjs/common': 10.4.17(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2)
@@ -4149,11 +4091,6 @@ snapshots:
class-transformer: 0.5.1 class-transformer: 0.5.1
class-validator: 0.14.2 class-validator: 0.14.2
'@nestjs/passport@11.0.5(@nestjs/common@10.4.17(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(passport@0.7.0)':
dependencies:
'@nestjs/common': 10.4.17(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2)
passport: 0.7.0
'@nestjs/platform-express@10.4.17(@nestjs/common@10.4.17(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.17)': '@nestjs/platform-express@10.4.17(@nestjs/common@10.4.17(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.17)':
dependencies: dependencies:
'@nestjs/common': 10.4.17(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2) '@nestjs/common': 10.4.17(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2)
@@ -4357,10 +4294,6 @@ snapshots:
'@types/json-schema@7.0.15': {} '@types/json-schema@7.0.15': {}
'@types/jsonwebtoken@9.0.7':
dependencies:
'@types/node': 20.17.31
'@types/methods@1.1.4': {} '@types/methods@1.1.4': {}
'@types/mime@1.3.5': {} '@types/mime@1.3.5': {}
@@ -4375,20 +4308,6 @@ snapshots:
dependencies: dependencies:
undici-types: 6.21.0 undici-types: 6.21.0
'@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': {}
@@ -6423,19 +6342,6 @@ 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@0.7.0:
dependencies:
passport-strategy: 1.0.0
pause: 0.0.1
utils-merge: 1.0.1
path-exists@4.0.0: {} path-exists@4.0.0: {}
path-is-absolute@1.0.1: {} path-is-absolute@1.0.1: {}
@@ -6459,8 +6365,6 @@ snapshots:
dependencies: dependencies:
through: 2.3.8 through: 2.3.8
pause@0.0.1: {}
peek-readable@7.0.0: {} peek-readable@7.0.0: {}
pg-cloudflare@1.2.5: pg-cloudflare@1.2.5:

View File

@@ -19,13 +19,13 @@ import { RemoveUserDto } from '../dto/admin-user/remove.dto';
import { RolesGuard } from 'src/common/guard/roles.guard'; import { RolesGuard } from 'src/common/guard/roles.guard';
import { Roles } from 'src/common/decorators/role.decorator'; import { Roles } from 'src/common/decorators/role.decorator';
import { Role } from 'src/auth/role.enum'; import { Role } from 'src/auth/role.enum';
import { AuthGuard } from '@nestjs/passport'; import { AuthGuard } from 'src/auth/guards/auth.guard';
@Controller('admin/user') @Controller('admin/user')
@UseGuards(AuthGuard('jwt'), RolesGuard) @UseGuards(AuthGuard, RolesGuard)
@Roles(Role.Admin) @Roles(Role.Admin)
export class AdminUserController { export class AdminUserController {
constructor(private readonly userService: UserService) {} constructor(private readonly userService: UserService) { }
@Get() @Get()
async list(@Query() listDto: ListDto) { async list(@Query() listDto: ListDto) {

View File

@@ -9,20 +9,20 @@ import {
Put, Put,
UseGuards, UseGuards,
} from '@nestjs/common'; } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { CreateBlogDto } from 'src/admin/dto/admin-web/create-blog.dto'; import { CreateBlogDto } from 'src/admin/dto/admin-web/create-blog.dto';
import { SetBlogPasswordDto } from 'src/admin/dto/admin-web/set-blog-password.dto'; import { SetBlogPasswordDto } from 'src/admin/dto/admin-web/set-blog-password.dto';
import { UpdateBlogDto } from 'src/admin/dto/admin-web/update-blog.dto'; import { UpdateBlogDto } from 'src/admin/dto/admin-web/update-blog.dto';
import { AuthGuard } from 'src/auth/guards/auth.guard';
import { Role } from 'src/auth/role.enum'; import { Role } from 'src/auth/role.enum';
import { BlogService } from 'src/blog/blog.service'; import { BlogService } from 'src/blog/blog.service';
import { Roles } from 'src/common/decorators/role.decorator'; import { Roles } from 'src/common/decorators/role.decorator';
import { RolesGuard } from 'src/common/guard/roles.guard'; import { RolesGuard } from 'src/common/guard/roles.guard';
@Controller('/admin/web/blog') @Controller('/admin/web/blog')
@UseGuards(AuthGuard('jwt'), RolesGuard) @UseGuards(AuthGuard, RolesGuard)
@Roles(Role.Admin) @Roles(Role.Admin)
export class AdminWebBlogController { export class AdminWebBlogController {
constructor(private readonly adminWebBlogService: BlogService) {} constructor(private readonly adminWebBlogService: BlogService) { }
@Get() @Get()
async list() { async list() {

View File

@@ -9,18 +9,18 @@ import {
Put, Put,
UseGuards, UseGuards,
} from '@nestjs/common'; } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { CreateResourceDto } from 'src/admin/dto/admin-web/create-resource.dto'; import { CreateResourceDto } from 'src/admin/dto/admin-web/create-resource.dto';
import { AuthGuard } from 'src/auth/guards/auth.guard';
import { Role } from 'src/auth/role.enum'; import { Role } from 'src/auth/role.enum';
import { Roles } from 'src/common/decorators/role.decorator'; import { Roles } from 'src/common/decorators/role.decorator';
import { RolesGuard } from 'src/common/guard/roles.guard'; import { RolesGuard } from 'src/common/guard/roles.guard';
import { ResourceService } from 'src/resource/resource.service'; import { ResourceService } from 'src/resource/resource.service';
@Controller('/admin/web/resource') @Controller('/admin/web/resource')
@UseGuards(AuthGuard('jwt'), RolesGuard) @UseGuards(AuthGuard, RolesGuard)
@Roles(Role.Admin) @Roles(Role.Admin)
export class AdminWebResourceController { export class AdminWebResourceController {
constructor(private readonly resourceService: ResourceService) {} constructor(private readonly resourceService: ResourceService) { }
@Get() @Get()
async list() { async list() {

View File

@@ -7,7 +7,6 @@ import { UserModule } from './user/user.module';
import { AuthModule } from './auth/auth.module'; import { AuthModule } from './auth/auth.module';
import { VerificationModule } from './verification/verification.module'; import { VerificationModule } from './verification/verification.module';
import { NotificationModule } from './notification/notification.module'; import { NotificationModule } from './notification/notification.module';
import { PassportModule } from '@nestjs/passport';
import { ResourceModule } from './resource/resource.module'; import { ResourceModule } from './resource/resource.module';
import { BlogModule } from './blog/blog.module'; import { BlogModule } from './blog/blog.module';
import { AdminModule } from './admin/admin.module'; import { AdminModule } from './admin/admin.module';
@@ -28,7 +27,6 @@ import { ThrottlerModule } from '@nestjs/throttler';
entities: [], entities: [],
synchronize: process.env.NODE_ENV !== 'production', // Set to false in production synchronize: process.env.NODE_ENV !== 'production', // Set to false in production
}), }),
PassportModule.register({ defaultStrategy: 'jwt' }),
ThrottlerModule.forRoot({ ThrottlerModule.forRoot({
throttlers: [ throttlers: [
{ {

View File

@@ -10,11 +10,11 @@ import {
} from '@nestjs/common'; } from '@nestjs/common';
import { LoginByPasswordDto } from './dto/login.dto'; import { LoginByPasswordDto } from './dto/login.dto';
import { AuthService } from './auth.service'; import { AuthService } from './auth.service';
import { AuthGuard } from '@nestjs/passport';
import { UserSessionService } from 'src/user/services/user-session.service'; import { UserSessionService } from 'src/user/services/user-session.service';
import { Throttle, ThrottlerGuard } from '@nestjs/throttler'; import { Throttle, ThrottlerGuard } from '@nestjs/throttler';
import { Response } from 'express'; import { Response } from 'express';
import { UserService } from 'src/user/user.service'; import { UserService } from 'src/user/user.service';
import { AuthGuard } from './guards/auth.guard';
@Controller('auth') @Controller('auth')
export class AuthController { export class AuthController {
@@ -40,8 +40,8 @@ export class AuthController {
// } // }
// } // }
private setUserToken(res: Response, token: string) { private setUserSession(res: Response, sessionId: string) {
res.cookie('token', token, { res.cookie('session', sessionId, {
httpOnly: true, httpOnly: true,
secure: process.env.NODE_ENV === 'production', secure: process.env.NODE_ENV === 'production',
sameSite: 'lax', sameSite: 'lax',
@@ -56,11 +56,10 @@ export class AuthController {
@Res({ passthrough: true }) res: Response, @Res({ passthrough: true }) res: Response,
) { ) {
const { identifier, password } = loginDto; const { identifier, password } = loginDto;
const loginRes = await this.authService.loginWithPassword(identifier, password); const session = await this.authService.loginWithPassword(identifier, password);
const { userId, token } = loginRes; this.setUserSession(res, session.sessionId);
this.setUserToken(res, token);
return { return {
user: await this.userService.findById(userId), user: await this.userService.findById(session.userId),
}; };
} }
@@ -84,7 +83,7 @@ export class AuthController {
throw new NotImplementedException(); throw new NotImplementedException();
} }
@UseGuards(AuthGuard('jwt')) @UseGuards(AuthGuard)
@Post('logout') @Post('logout')
async logout(@Request() req) { async logout(@Request() req) {
const { userId, sessionId } = req.user; const { userId, sessionId } = req.user;

View File

@@ -2,35 +2,22 @@ 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 { 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 { PassportModule } from '@nestjs/passport'; import { ConfigModule } from '@nestjs/config';
import { JwtStrategy } from './strategies/jwt.strategy';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { VerificationModule } from 'src/verification/verification.module'; import { VerificationModule } from 'src/verification/verification.module';
import { OptionalAuthGuard } from './strategies/OptionalAuthGuard'; import { AuthGuard } from './guards/auth.guard';
import { OptionalAuthGuard } from './guards/optional-auth.guard';
@Module({ @Module({
imports: [ imports: [
ConfigModule, ConfigModule,
forwardRef(() => UserModule), forwardRef(() => UserModule),
TypeOrmModule.forFeature([UserSession]), TypeOrmModule.forFeature([UserSession]),
PassportModule.register({ defaultStrategy: 'jwt' }),
JwtModule.registerAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: (configService: ConfigService) => ({
secret: configService.get<string>('JWT_SECRET', 'tone-page'),
signOptions: {
expiresIn: configService.get<string>('JWT_EXPIRES_IN', '1d'),
},
}),
}),
VerificationModule, VerificationModule,
], ],
controllers: [AuthController], controllers: [AuthController],
providers: [AuthService, JwtStrategy, OptionalAuthGuard], providers: [AuthService, AuthGuard, OptionalAuthGuard],
exports: [PassportModule, JwtStrategy, AuthService, OptionalAuthGuard], exports: [AuthService, AuthGuard, OptionalAuthGuard],
}) })
export class AuthModule {} export class AuthModule { }

View File

@@ -1,8 +1,6 @@
import { createHash } from 'crypto'; import { createHash } from 'crypto';
import { BadRequestException, Injectable } from '@nestjs/common'; import { BadRequestException, Injectable } from '@nestjs/common';
import { UserService } from 'src/user/user.service'; import { UserService } from 'src/user/user.service';
import { User } from 'src/user/entities/user.entity';
import { JwtService } from '@nestjs/jwt';
import { UserSessionService } from 'src/user/services/user-session.service'; import { UserSessionService } from 'src/user/services/user-session.service';
import { VerificationService } from 'src/verification/verification.service'; import { VerificationService } from 'src/verification/verification.service';
import { BusinessException } from 'src/common/exceptions/business.exception'; import { BusinessException } from 'src/common/exceptions/business.exception';
@@ -12,7 +10,6 @@ import { ErrorCode } from 'src/common/constants/error-codes';
export class AuthService { export class AuthService {
constructor( constructor(
private readonly userService: UserService, private readonly userService: UserService,
private readonly jwtService: JwtService,
private readonly userSessionService: UserSessionService, private readonly userSessionService: UserSessionService,
private readonly verificationService: VerificationService, private readonly verificationService: VerificationService,
) { } ) { }
@@ -49,11 +46,9 @@ export class AuthService {
}); });
} }
// 登录成功颁发token const { userId } = user;
return {
token: await this.generateToken(user), return this.userSessionService.createSession(userId);
userId: user.userId,
};
} }
async loginWithPhone(data: { phone: string; code: string; }) { async loginWithPhone(data: { phone: string; code: string; }) {
@@ -93,9 +88,8 @@ export class AuthService {
throw new BadRequestException('请求失败,请稍后再试'); throw new BadRequestException('请求失败,请稍后再试');
} }
// 登录颁发token
return { return {
token: await this.generateToken(user), userId: user.userId
}; };
} }
@@ -103,18 +97,4 @@ export class AuthService {
return createHash('sha256').update(`${password}${salt}`).digest('hex'); return createHash('sha256').update(`${password}${salt}`).digest('hex');
} }
private async generateToken(user: User) {
// 存储
const sessionRes = await this.userSessionService.createSession(
user.userId,
);
const payload = {
userId: user.userId,
sessionId: sessionRes.sessionId,
};
// 颁发token
return this.jwtService.sign(payload);
}
} }

View File

@@ -0,0 +1,38 @@
// auth.guard.ts
import { Injectable, CanActivate, ExecutionContext, UnauthorizedException } from '@nestjs/common';
import { Request } from 'express';
import { UserSessionService } from 'src/user/services/user-session.service';
import { UserService } from 'src/user/user.service';
@Injectable()
export class AuthGuard implements CanActivate {
constructor(
private userService: UserService,
private userSessionService: UserSessionService,
) { }
async canActivate(context: ExecutionContext): Promise<boolean> {
const request = context.switchToHttp().getRequest<Request>();
// 从 Cookie 读取 session
const sessionId = request.cookies?.['session'];
if (!sessionId) {
throw new UnauthorizedException('登陆凭证无效,请重新登陆');
}
// 验证 session
const session = await this.userSessionService.getSession(sessionId);
if (!session) {
throw new UnauthorizedException('登陆凭证无效,请重新登陆');
}
// 附加 user 到 req
const user = await this.userService.findOne({ userId: session.userId });
if (!user) {
throw new UnauthorizedException('用户不存在');
}
(request as any).user = { ...user, sessionId };
return true;
}
}

View File

@@ -0,0 +1,16 @@
import { ExecutionContext, Injectable } from "@nestjs/common";
import { AuthGuard } from "./auth.guard";
@Injectable()
export class OptionalAuthGuard extends AuthGuard {
async canActivate(context: ExecutionContext): Promise<boolean> {
try {
return await super.canActivate(context);
} catch (error) {
// 验证失败时req.user = null但允许继续
const request = context.switchToHttp().getRequest();
request.user = null;
return true;
}
}
}

View File

@@ -1,28 +0,0 @@
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
@Injectable()
export class OptionalAuthGuard extends AuthGuard('jwt') implements CanActivate {
async canActivate(context: ExecutionContext): Promise<boolean> {
try {
await super.canActivate(context);
return true;
} catch (error) {
console.error('OptionalAuthGuard error:', error);
return true; // 如果验证失败,仍然允许访问
}
}
handleRequest<TUser = any>(
err: any,
user: any,
// info: any,
// context: ExecutionContext,
// status?: any,
): TUser {
if (err || !user) {
return null; // 如果没有用户信息返回null
}
return user; // 如果有用户信息,返回用户对象
}
}

View File

@@ -1,46 +0,0 @@
import {
BadRequestException,
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';
import { UserService } from 'src/user/user.service';
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy, 'jwt') {
constructor(
private readonly userService: UserService,
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 ?? {};
await this.userSessionService.isSessionValid(
userId,
sessionId,
).catch((e) => {
throw new UnauthorizedException(`${e}`);
});
const user = await this.userService.findOne({ userId });
if (!user) {
throw new BadRequestException('用户不存在');
}
return {
...user,
sessionId,
};
}
}

View File

@@ -11,11 +11,11 @@ import {
UseGuards, UseGuards,
} from '@nestjs/common'; } from '@nestjs/common';
import { BlogService } from './blog.service'; import { BlogService } from './blog.service';
import { OptionalAuthGuard } from 'src/auth/strategies/OptionalAuthGuard';
import { UserService } from 'src/user/user.service'; import { UserService } from 'src/user/user.service';
import { createBlogCommentDto } from './dto/create.blogcomment.dto'; import { createBlogCommentDto } from './dto/create.blogcomment.dto';
import { Throttle, ThrottlerGuard } from '@nestjs/throttler'; import { Throttle, ThrottlerGuard } from '@nestjs/throttler';
import { BlogPermission } from './blog.permission.enum'; import { BlogPermission } from './blog.permission.enum';
import { OptionalAuthGuard } from 'src/auth/guards/optional-auth.guard';
@Controller('blog') @Controller('blog')
export class BlogController { export class BlogController {

View File

@@ -1,12 +1,12 @@
import { Controller, Get, Request, UseGuards } from '@nestjs/common'; import { Controller, Get, Request, UseGuards } from '@nestjs/common';
import { OssService } from './oss.service'; import { OssService } from './oss.service';
import { AuthGuard } from '@nestjs/passport'; import { AuthGuard } from 'src/auth/guards/auth.guard';
@Controller('oss') @Controller('oss')
export class OssController { export class OssController {
constructor(private readonly ossService: OssService) {} constructor(private readonly ossService: OssService) { }
@UseGuards(AuthGuard('jwt')) @UseGuards(AuthGuard)
@Get('sts') @Get('sts')
async getStsToken(@Request() req) { async getStsToken(@Request() req) {
const { userId } = req.user; const { userId } = req.user;

View File

@@ -1,9 +1,12 @@
import { Module } from '@nestjs/common'; import { Module } from '@nestjs/common';
import { OssService } from './oss.service'; import { OssService } from './oss.service';
import { OssController } from './oss.controller'; import { OssController } from './oss.controller';
import { AuthModule } from 'src/auth/auth.module';
import { UserModule } from 'src/user/user.module';
@Module({ @Module({
providers: [OssService], providers: [OssService],
controllers: [OssController], controllers: [OssController],
imports: [AuthModule, UserModule],
}) })
export class OssModule {} export class OssModule { }

View File

@@ -17,32 +17,19 @@ export class UserSessionService {
return this.userSessionRepository.save(session); return this.userSessionRepository.save(session);
} }
/** async getSession(sessionId: string) {
* @throws string 无效原因
*/
async isSessionValid(userId: string, sessionId: string): Promise<void> {
const session = await this.userSessionRepository.findOne({ const session = await this.userSessionRepository.findOne({
where: { where: {
userId,
sessionId, sessionId,
}, },
withDeleted: true,
}); });
if (session === null) { return session;
throw '登陆凭证无效';
} }
if (session.deletedAt !== null) { async invalidateSession(sessionId: string, reason?: string): Promise<void> {
throw session.disabledReason || '登陆凭证无效';
}
return null;
}
async invalidateSession(userId: string, sessionId: string, reason?: string): Promise<void> {
await this.userSessionRepository.update( await this.userSessionRepository.update(
{ userId, sessionId, deletedAt: null }, { sessionId, deletedAt: null },
{ {
deletedAt: new Date(), deletedAt: new Date(),
disabledReason: reason || null, disabledReason: reason || null,

View File

@@ -1,24 +1,24 @@
import { Body, Controller, Get, Put, Request, UseGuards } from '@nestjs/common'; import { Body, Controller, Get, Put, Request, UseGuards } from '@nestjs/common';
import { UserService } from './user.service'; import { UserService } from './user.service';
import { AuthGuard } from '@nestjs/passport';
import { UpdateUserPasswordDto } from './dto/update-user-password.dto'; import { UpdateUserPasswordDto } from './dto/update-user-password.dto';
import { AuthService } from 'src/auth/auth.service'; import { AuthService } from 'src/auth/auth.service';
import { AuthGuard } from 'src/auth/guards/auth.guard';
@Controller('user') @Controller('user')
export class UserController { export class UserController {
constructor( constructor(
private readonly userService: UserService, private readonly userService: UserService,
private readonly authService: AuthService, private readonly authService: AuthService,
) {} ) { }
@UseGuards(AuthGuard('jwt')) @UseGuards(AuthGuard)
@Get('me') @Get('me')
async getMe(@Request() req) { async getMe(@Request() req) {
const { user } = req; const { user } = req;
return this.userService.findOne({ userId: user.userId }); return this.userService.findById(user.userId);
} }
@UseGuards(AuthGuard('jwt')) @UseGuards(AuthGuard)
@Put('password') @Put('password')
async update(@Request() req, @Body() dto: UpdateUserPasswordDto) { async update(@Request() req, @Body() dto: UpdateUserPasswordDto) {
return this.userService.setPassword(req.user.userId, dto.password); return this.userService.setPassword(req.user.userId, dto.password);