Skip to content

Commit 912164f

Browse files
committed
[#7] JWT인증구현
1 parent 28e2518 commit 912164f

File tree

10 files changed

+403
-35
lines changed

10 files changed

+403
-35
lines changed

package-lock.json

Lines changed: 232 additions & 12 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,20 @@
2424
"@nestjs/common": "^10.0.0",
2525
"@nestjs/config": "^3.3.0",
2626
"@nestjs/core": "^10.0.0",
27+
"@nestjs/jwt": "^10.2.0",
2728
"@nestjs/mapped-types": "*",
29+
"@nestjs/passport": "^10.0.3",
2830
"@nestjs/platform-express": "^10.0.0",
2931
"@nestjs/typeorm": "^10.0.2",
32+
"@types/passport-jwt": "^4.0.1",
3033
"bcrypt": "^5.1.1",
3134
"bcrypto": "^5.5.2",
3235
"class-validator": "^0.14.1",
3336
"dotenv": "^16.4.5",
3437
"mysql2": "^3.11.4",
38+
"passport": "^0.7.0",
39+
"passport-jwt": "^4.0.1",
40+
"passport-local": "^1.0.0",
3541
"reflect-metadata": "^0.2.0",
3642
"rxjs": "^7.8.1",
3743
"typeorm": "^0.3.20"
@@ -44,6 +50,7 @@
4450
"@types/express": "^4.17.17",
4551
"@types/jest": "^29.5.2",
4652
"@types/node": "^20.3.1",
53+
"@types/passport-local": "^1.0.38",
4754
"@types/supertest": "^6.0.0",
4855
"@typescript-eslint/eslint-plugin": "^8.0.0",
4956
"@typescript-eslint/parser": "^8.0.0",

src/auth/auth.controller.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
1-
import { Controller } from '@nestjs/common';
1+
import { Controller, Post, UseGuards, Request } from '@nestjs/common';
22
import { AuthService } from './auth.service';
3+
import { LocalAuthGuard } from './security/passport.jwt';
34

4-
@Controller('auth')
5+
@Controller()
56
export class AuthController {
67
constructor(private readonly authService: AuthService) {}
8+
9+
@Post('/signin')
10+
@UseGuards(LocalAuthGuard)
11+
async singin(@Request() req){
12+
return this.authService.signin(req.user);
13+
}
714
}

src/auth/auth.module.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,22 @@
11
import { Module } from '@nestjs/common';
22
import { AuthService } from './auth.service';
33
import { AuthController } from './auth.controller';
4+
import { UsersModule } from 'src/users/users.module';
5+
import { JwtModule } from '@nestjs/jwt';
6+
import { PassportModule } from '@nestjs/passport';
7+
import { JwtStrategy, JwtLocalStrategy } from './security/passport.jwt';
8+
import { jwtConstants } from './constants';
49

510
@Module({
11+
imports:[
12+
UsersModule,
13+
JwtModule.register({
14+
secret:jwtConstants.secret,
15+
signOptions: {expiresIn:jwtConstants.expiresIn}
16+
}),
17+
PassportModule
18+
],
619
controllers: [AuthController],
7-
providers: [AuthService],
20+
providers: [AuthService, JwtStrategy, JwtLocalStrategy],
821
})
922
export class AuthModule {}

src/auth/auth.service.ts

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,56 @@
1-
import { Injectable } from '@nestjs/common';
1+
import { Injectable, UnauthorizedException } from '@nestjs/common';
2+
import * as bcrypt from 'bcrypt';
3+
import { UserEntity } from 'src/users/entities/user.entity';
4+
import { UsersService } from 'src/users/users.service';
5+
import { JwtService } from '@nestjs/jwt';
6+
import { JwtTokenDto } from './dto/token.dto';
7+
import { jwtConstants } from './constants';
28

39
@Injectable()
4-
export class AuthService {}
10+
export class AuthService {
11+
constructor(private readonly userService: UsersService,
12+
private readonly jwtService: JwtService) {}
13+
14+
async validateUser(userId: string, password:string): Promise<any>{
15+
const user = await this.userService.findbyId(userId);
16+
if(!user){
17+
throw new UnauthorizedException('아이디 혹은 비밀번호를 확인하세요.');
18+
}
19+
20+
const pwdCheck = bcrypt.compareSync(password, user.password);
21+
if(!pwdCheck){
22+
throw new UnauthorizedException('아이디 혹은 비밀번호를 확인히세요.');
23+
} else {
24+
delete user.password
25+
}
26+
27+
return user;
28+
}
29+
30+
async signin(user: UserEntity){
31+
return this.createToken(user);
32+
}
33+
34+
async createToken(user: UserEntity) {
35+
const tokenUserNo = user.userNo;
36+
const tokenDto = new JwtTokenDto();
37+
tokenDto.accessToken = await this.createAccessToken(user);
38+
tokenDto.refreshToken = await this.createRefreshToken(tokenUserNo);
39+
40+
return tokenDto;
41+
}
42+
43+
async createAccessToken(user: UserEntity): Promise<string>{
44+
const payload = {userNo: user.userNo, userId:user.userId}
45+
return this.jwtService.sign({payload},
46+
{secret: jwtConstants.secret,
47+
expiresIn: jwtConstants.expiresIn})
48+
}
49+
50+
async createRefreshToken(userNo: number): Promise<string>{
51+
return this.jwtService.sign({userNo},
52+
{secret:jwtConstants.secret,
53+
expiresIn:jwtConstants.expiresInRefresh})
54+
}
55+
56+
}

src/auth/constants.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export const jwtConstants = {
2+
secret: 'frogthedev1635@^#$',
3+
expiresIn:'5m',
4+
expiresInRefresh:'2h'
5+
};

src/auth/dto/auth.dto.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,22 @@
1-
import { IsEmpty, IsString, IsEmail, Length, IsAlphanumeric, Matches} from "class-validator";
1+
import {IsString, IsEmpty, IsEmail, Length, IsAlphanumeric, Matches} from "class-validator";
22
export namespace AuthDTO{
33
export class SignUp{
44
//TODO : guard랑 JWT토큰 적용
5-
@IsEmpty()
6-
@IsAlphanumeric()
5+
@IsEmpty({message: '사용자 ID는 공백일 수 없습니다.'})
6+
@IsAlphanumeric("en-US", {message:'사용자 ID는 영어와 숫자만 가능합니다.'})
77
userId: string;
88

9-
@IsEmpty()
10-
@IsString()
11-
@Length(2,10)
9+
@IsEmpty({message: '사용자 이름은 공백일 수 없습니다.'})
10+
@IsString({message: '사용자 이름은 문자만 가능합니다.'})
11+
@Length(2,10,{message:'사용자 이름은 최소 2글자에서 10글자 까지 가능합니다.'})
1212
userName: string;
1313

14-
@IsEmpty()
14+
@IsEmpty({message: '사용자 email은 공백일 수 없습니다.'})
1515
@IsEmail()
1616
email:string;
1717

18-
@IsEmpty()
19-
@IsString()
18+
@IsEmpty({message: '사용자 비밀번호는 공백일 수 없습니다.'})
19+
@IsString({message: '사용자 비밀번호는 문자만 가능합니다.'})
2020
@Matches(/^(?=.*[a-zA-Z])(?=.*[0-9])(?=.*[!@#$%^&*?_]).{8,20}$/, {
2121
message:
2222
'패스워드는 8~20자리이며 최소 하나 이상의 영문자, 최소 하나 이상의 숫자, 최소 하나 이상의 특수문자를 입력해야 합니다.',

src/auth/dto/token.dto.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export class JwtTokenDto {
2+
accessToken;
3+
refreshToken;
4+
}

src/auth/security/passport.jwt.ts

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { Injectable, UnauthorizedException } from "@nestjs/common";
2+
import { PassportStrategy } from "@nestjs/passport";
3+
import { ExtractJwt, Strategy } from "passport-jwt";
4+
import { Strategy as LocalStrategy } from "passport-local";
5+
import { AuthGuard } from "@nestjs/passport";
6+
7+
import { AuthService } from "../auth.service";
8+
import { jwtConstants } from "../constants";
9+
10+
@Injectable()
11+
export class JwtLocalStrategy extends PassportStrategy(LocalStrategy,'local'){
12+
constructor(private readonly authService: AuthService){
13+
super({
14+
usernameField: 'userId',
15+
passwordField: 'password'
16+
})
17+
}
18+
19+
async validate(userId:string, password:string):Promise<any>{
20+
const user = await this.authService.validateUser(userId, password);
21+
// 사용자가 뭔가 입력하면 무조건 아이디 혹은 비밀번호를 확인하세요 라고 뜨는거 아닌가?
22+
if(!user) {
23+
return new UnauthorizedException({message: '회원 정보가 존재하지 않습니다.'});
24+
}
25+
return user;
26+
27+
}
28+
}
29+
30+
@Injectable()
31+
export class JwtStrategy extends PassportStrategy(Strategy,'jwt'){
32+
constructor(){
33+
super({
34+
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
35+
secretOrKey: jwtConstants.secret,
36+
ignoreException: false,
37+
algorithms: ['HS256'],
38+
})
39+
}
40+
41+
async validate(payload: any) {
42+
return { ...payload };
43+
}
44+
}
45+
46+
47+
@Injectable()
48+
export class JwtAuthGuard extends AuthGuard('jwt'){}
49+
50+
@Injectable()
51+
export class LocalAuthGuard extends AuthGuard('local'){}

src/users/users.controller.ts

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
import { Controller, Get, Post, Body, Patch, Param, Delete, ConflictException } from '@nestjs/common';
1+
import { Controller, Get, Post, Body, Patch, Param, Delete,
2+
ConflictException, UseGuards, Req,ParseIntPipe } from '@nestjs/common';
23
import { UsersService } from './users.service';
34
import { AuthDTO } from 'src/auth/dto/auth.dto';
4-
import { userDto } from './dto/user.dto';
55
import { UpdateUserDto } from './dto/update-user.dto';
6+
import { JwtAuthGuard } from 'src/auth/security/passport.jwt';
67

78
@Controller('users')
89
export class UsersController {
@@ -24,22 +25,30 @@ export class UsersController {
2425
return '회원가입성공';
2526
}
2627

28+
@UseGuards(JwtAuthGuard)
29+
@Get('/')
30+
async getProfile(@Req() req: any){
31+
const user = req.user;
32+
return user;
33+
}
34+
35+
2736
@Get()
2837
findAll() {
2938
return this.usersService.findAll();
3039
}
3140

32-
@Get(':id')
33-
findOne(@Param('id') id: string) {
34-
return this.usersService.findOne(+id);
41+
@Get('/userNo')
42+
findOne(@Param('userNo', ParseIntPipe) userNo: number) {
43+
return this.usersService.findOne(userNo);
3544
}
3645

37-
@Patch(':id')
38-
update(@Param('id') id: string, @Body() updateUserDto: UpdateUserDto) {
39-
return this.usersService.update(+id, updateUserDto);
46+
@Patch('/userNo')
47+
update(@Param('userNo', ParseIntPipe) userNo: number, @Body() updateUserDto: UpdateUserDto) {
48+
return this.usersService.update(userNo, updateUserDto);
4049
}
4150

42-
@Delete(':id')
51+
@Delete('/id')
4352
remove(@Param('id') id: string) {
4453
return this.usersService.remove(+id);
4554
}

0 commit comments

Comments
 (0)