Skip to content

Commit

Permalink
feat: 提供JWT验证及鉴权
Browse files Browse the repository at this point in the history
  • Loading branch information
MZ-Dlovely committed Oct 29, 2024
1 parent d13a625 commit adb6337
Show file tree
Hide file tree
Showing 20 changed files with 845 additions and 12 deletions.
3 changes: 3 additions & 0 deletions apps/backend-nest/.env.development
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,6 @@ VITE_PORT=5777

# 是否开启 SWC
VITE_SWC=true

# JWT密钥
VITE_JWT_SECRET=your_jwt_secret
3 changes: 3 additions & 0 deletions apps/backend-nest/.env.production
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,6 @@ VITE_PORT=3000

# 是否开启 SWC
VITE_SWC=false

# JWT密钥
VITE_JWT_SECRET=your_jwt_secret
14 changes: 13 additions & 1 deletion apps/backend-nest/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,25 @@
"dependencies": {
"@nestjs/common": "^10.0.0",
"@nestjs/core": "^10.0.0",
"@nestjs/jwt": "^10.2.0",
"@nestjs/passport": "^10.0.3",
"@nestjs/platform-express": "^10.0.0",
"@nestjs/swagger": "^8.0.0",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.1",
"cookie-parser": "^1.4.7",
"passport": "^0.7.0",
"passport-jwt": "^4.0.1",
"passport-local": "^1.0.0",
"reflect-metadata": "^0.2.0",
"rxjs": "^7.8.1"
},
"devDependencies": {
"@nestjs/cli": "^10.0.0",
"@types/cookie-parser": "^1.4.7",
"@types/express": "^4.17.17",
"@types/node": "catalog:"
"@types/node": "catalog:",
"@types/passport-jwt": "^4.0.1",
"@types/passport-local": "^1.0.38"
}
}
8 changes: 7 additions & 1 deletion apps/backend-nest/src/app/index.module.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import { Module } from '@nestjs/common';

@Module({})
import guards from '#/guards';
import { RoutesModule } from '#/routes';

@Module({
imports: [RoutesModule],
providers: [...guards],
})
// eslint-disable-next-line @typescript-eslint/no-extraneous-class
export class AppModule {}
6 changes: 6 additions & 0 deletions apps/backend-nest/src/app/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import type { INestApplication } from '@nestjs/common';

import { NestFactory } from '@nestjs/core';

import plugins from '#/plugins';

import { AppModule } from './index.module';

let app: INestApplication;
Expand All @@ -11,6 +13,10 @@ async function createApp() {

app.enableCors();

for (const plugin of plugins) {
app.use(plugin);
}

return app;
}

Expand Down
19 changes: 19 additions & 0 deletions apps/backend-nest/src/auth/index.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { Module } from '@nestjs/common';
import { JwtModule } from '@nestjs/jwt';
import { PassportModule } from '@nestjs/passport';

import { AuthService } from './index.service';
import { JwtStrategy } from './jwt.strategy';
import { LocalStrategy } from './local.strategy';

@Module({
imports: [
PassportModule,
JwtModule.register({
secret: import.meta.env.VITE_JWT_SECRET,
}),
],
providers: [AuthService, LocalStrategy, JwtStrategy],
})
// eslint-disable-next-line @typescript-eslint/no-extraneous-class
export class AuthModule {}
90 changes: 90 additions & 0 deletions apps/backend-nest/src/auth/index.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { Injectable } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';

export interface UserInfo {
id: number;
password: string;
realName: string;
roles: string[];
username: string;
}

@Injectable()
export class AuthService {
// TODO: Replace with your own secret key
static ACCESS_TOKEN_SECRET = 'access_token_secret';
static MOCK_CODES = [
// super
{
codes: ['AC_100100', 'AC_100110', 'AC_100120', 'AC_100010'],
username: 'vben',
},
{
// admin
codes: ['AC_100010', 'AC_100020', 'AC_100030'],
username: 'admin',
},
{
// user
codes: ['AC_1000001', 'AC_1000002'],
username: 'jack',
},
];
static MOCK_USERS: UserInfo[] = [
{
id: 0,
password: '123456',
realName: 'Vben',
roles: ['super'],
username: 'vben',
},
{
id: 1,
password: '123456',
realName: 'Admin',
roles: ['admin'],
username: 'admin',
},
{
id: 2,
password: '123456',
realName: 'Jack',
roles: ['user'],
username: 'jack',
},
];
static REFRESH_TOKEN_SECRET = 'refresh_token_secret';

constructor(private readonly JwtService: JwtService) {}

public getAccessToken(user: UserInfo) {
return this.JwtService.sign(user, {
expiresIn: '7d',
});
}

public getRefreshToken(user: UserInfo) {
return this.JwtService.sign(user, {
expiresIn: '30d',
});
}

public async validateUser(username: string, password: string) {
const findUser = AuthService.MOCK_USERS.find(
(item) => item.username === username && item.password === password,
);

if (!findUser) {
return;
}

return findUser;
}
}

declare global {
export namespace Express {
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
export interface User extends UserInfo {}
}
}
4 changes: 4 additions & 0 deletions apps/backend-nest/src/auth/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export { AuthModule } from './index.module';
export { AuthService } from './index.service';

export type { UserInfo } from './index.service';
18 changes: 18 additions & 0 deletions apps/backend-nest/src/auth/jwt.strategy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Injectable } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor() {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: import.meta.env.VITE_JWT_SECRET,
});
}

async validate(payload: any) {
return { ...payload };
}
}
22 changes: 22 additions & 0 deletions apps/backend-nest/src/auth/local.strategy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { Strategy } from 'passport-local';

import { AuthService } from './index.service';

@Injectable()
export class LocalStrategy extends PassportStrategy(Strategy) {
constructor(private readonly AuthService: AuthService) {
super();
}

async validate(username: string, password: string): Promise<any> {
const findUser = await this.AuthService.validateUser(username, password);

if (!findUser) {
throw new UnauthorizedException();
}

return findUser;
}
}
30 changes: 30 additions & 0 deletions apps/backend-nest/src/guards/auth.guard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import type { Reflector } from '@nestjs/core';

import { type ExecutionContext, Injectable, SetMetadata } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';

const IS_PUBLIC_KEY = 'isPublic';
export const Public = () => SetMetadata(IS_PUBLIC_KEY, true);

@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {
constructor(private readonly Reflector: Reflector) {
super();
}

override canActivate(context: ExecutionContext) {
const isPublic = this.Reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [
context.getHandler(),
context.getClass(),
]);

if (isPublic) {
return true;
}

return super.canActivate(context);
}
}

// 默认导出,便于glob导入
export default JwtAuthGuard;
15 changes: 15 additions & 0 deletions apps/backend-nest/src/guards/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import type { ClassProvider, Type } from '@nestjs/common';

import { APP_GUARD } from '@nestjs/core';

const glob_result = import.meta.glob<Type>('./*.guard.ts', {
import: 'default',
eager: true,
});

export default Object.values(glob_result).map<ClassProvider>((useClass) => ({
provide: APP_GUARD,
useClass,
}));

export { Public } from './auth.guard';
28 changes: 28 additions & 0 deletions apps/backend-nest/src/plugins/cookies.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { createParamDecorator, type ExecutionContext } from '@nestjs/common';
import cookieParser from 'cookie-parser';

export default cookieParser();

export const Cookies = createParamDecorator(
(data: string, ctx: ExecutionContext) => {
const request: Express.Request = ctx.switchToHttp().getRequest();
return data ? request.cookies?.[data] : request.cookies;
},
);

declare global {
export namespace Express {
export interface Request {
/**
* 请求密钥
* [可选] 如果向`cookie-parser`传递了密钥,则此属性将包含密钥。
* 可以被其他中间件使用
*/
secret?: string | undefined;
/** 解析尚未签名的cookie */
cookies: Record<string, any>;
/** 解析已签名的cookie */
signedCookies: Record<string, any>;
}
}
}
8 changes: 8 additions & 0 deletions apps/backend-nest/src/plugins/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
const glob_result = import.meta.glob<any>(['./*.ts', '!./index.ts'], {
import: 'default',
eager: true,
});

export default Object.values(glob_result);

export { Cookies } from './cookies';
Loading

0 comments on commit adb6337

Please sign in to comment.