nest g module auth
nest g controller auth --no-spec
nest g service auth --no-spec
npm install --save bcrypt @types/bcrypt
비밀번호를 그대로 db에 저장하게 되면 관리자는 누구나 암호를 확인할 수 있다.
따라서 db에 저장하기 전에 암호화를 하고 이 값을 db에 담아줘야 한다.
보통 hash를 통해 암호화를 하는데, 같은 비밀번호를 사용할 경우 동일한 암호가 나오게 된다.
이렇게 되면 레인보우 테이블이라는 것을 이용해 비밀번호를 유추하게 될 위험이 생긴다.
따라서 salt라는 특정 키값을 이용해 이 값과 비밀번호를 더해 hash를 생성하고, 이 값을 db에 저장하면 된다.
async createUser(authCredentialDto: AuthCredentialDto): Promise<void>{
const{username,password} = authCredentialDto;
// 암호화 로직
const salt = await bcrypt.genSalt(); // salt 생성
const hashedPassword = await bcrypt.hash(password, salt); // hash암호 생성
const user = this.userRepository.create({username, password: hashedPassword});
try {
await this.userRepository.save(user);
} catch (error) {
if(error.code === '23505'){
throw new ConflictException('이미 존재하는 id입니다.');
}else{
throw new InternalServerErrorException();
}
}
}
json web token을 뜻한다. session의 경우 session에 유저 정보를 저장하고, session id를 쿠키로 내려주는 식으로 진행되는데 이렇게 할 경우 서버에 데이터가 누적되기 때문에 jwt를 사용하는게 로드밸런싱이나, 서버 스토리지에 유용하다.
npm install --save @nestjs/jwt @nestjs/passport passport passport-jwt --save
imports: [
// passport와 jwt모듈 추가
PassportModule.register({ defaultStrategy: 'jwt'}),
JwtModule.register({
secret: 'Secret1234',
signOptions: {
expiresIn: 60 * 60
}
}),
TypeOrmModule.forFeature([User])
],
이제 모듈을 활용해 로그인 성공 시에 JWT를 발급해주자.
async signIn(authCredentialDto: AuthCredentialDto): Promise<{accessToken: string}> {
const { username, password } = authCredentialDto;
const user = await this.userRepository.findOneBy({username});
// 입력받은 password와 db에 저장된 password를 비교하여 일치한지 체크
if(user && (await bcrypt.compare(password, user.password))){
// 유저 토큰 생성 (secret + payload)
const payload = { username };
const accessToken = await this.jwtService.sign(payload);
return { accessToken };
}else{
throw new UnauthorizedException('login Falid');
}
}
npm install @types/passport-jwt --save
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy){
constructor(
@InjectRepository(User)
private userRepository:Repository<User>
){
console.log("strategy ",Strategy);
super({
secretOrKey: 'Secret1234',
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken()
})
}
async validate(payload){
const {username} = payload;
const user:User = await this.userRepository.findOneBy({username});
if(!user){
throw new UnauthorizedException();
}
return user;
}
}
이제 jwtstrategy와 passportmodule을 모듈에 등록해줘야 하는데, jwtstrategy와 passportmodule의 경우 인증이 필요한 다른 모듈에서도 사용해야 하므로 exports에도 등록해주자.
@Module({
// 생략
providers: [AuthService, UserRepository, JwtStrategy], // JwtStrategy 등록
// exports에 등록
exports: [JwtStrategy, PassportModule]
})
JwtStrategy에서 반환하는 user정보를 활용하기 위해 UserGuards를 사용하면 된다.
@Post('/test')
// 토큰이 유효한 토큰인지 확인하고, request에 사용자 정보를 담아주는 역할을 함
@UseGuards(AuthGuard())
test(@Req() req){
console.log('Req', req);
}
import { ExecutionContext, createParamDecorator } from "@nestjs/common";
import { User } from "./user.entity";
export const GetUser = createParamDecorator((data, ctx: ExecutionContext): User => {
const req = ctx.switchToHttp().getRequest();
return req.user;
})
@Post('/test')
@UseGuards(AuthGuard())
// test(@Req() req){
test(@GetUser() user:User){
console.log('user', user);
}
}
이제 게시물을 가져올 때 토큰을 확인해 권한을 체크하도록 변경해주자.
@Controller('boards')
@UseGuards(AuthGuard()) // 컨트롤러 레벨로 가드 적용
export class BoardsController {
// 생략
}
컨트롤러 단위로 가드를 적용해주면 해당 컨트롤러의 모든 핸들러에 가드가 적용되어 권한 체크를 해준다.
게시물에는 게시물 관련 내용 뿐만 아니라 게시물을 작성한 사용자의 정보와도 관계를 맺게 된다. TypeORM을 이용해 관계를 맺어준다.
@Entity()
@Unique(['username'])
export class User{
// 생략
// 게시물과 관계 형성하기
@OneToMany(type => Board, board => board.user, {eager: true})
boards: Board[];
}
eager: true는 해당 엔티티를 조회할 때 참조중인 엔티티(board)도 함께 가져오는 것이다
@Entity()
// export class Board extends BaseEntity{
export class Board{
// 생략
@ManyToOne(type => User, user=> user.boards, {eager: false})
user: User;
}
eager를 false로 줬으므로 user정보 가져오지는 않는다.
guards로 user정보를 받아와 board생성시 담아주면 된다.
// 컨트롤러
@Post()
@UsePipes(ValidationPipe)
createBoard(
@Body() createBoard: CreateBoardDto,
@GetUser() user: User) : Promise<Board>{
return this.boardService.creatBoard(createBoard,user);
}
// service 생략
// 리포지토리
async createBoard(createBoardDto: CreateBoardDto, user:User): Promise<Board>{
const{title, description} =createBoardDto;
const board = this.boardRepository.create({
title,
description,
status: BoardStatus.PUBLIC,
user // user정보 담아주기
})
await this.boardRepository.save(board);
return board;
}
미들웨어 동작 순서
middleware - guard - interceptor - pipe - controller - service - controller - interceptor - filter - client