당사자간에 정보를 JSON 개체로 안전하게 전송하기위한 컴팩트하고 독립적인 방식을 정의하는 개방적 표준
header :토큰에 대한 메타 데이터(타입,해싱알고리즘,SHA256,RSA 등등등)
payload :유저정보(issuser),만료 기간(expiration time),주제(subject) 등등
Verify Signature : 서명
필요한 모듈들을 설치하기
npm i @nestjs/jwt @nestjs/passport passport passport-jwt
그후 module에 jwt모듈을 추가한다.
PassportModule.register({ defaultStrategy: 'jwt' }),
JwtModule.register({
secret:process.env.JWTSECRET,
,
signOptions: {
expiresIn: 60 * 60, //유효시간
},
})
서비스에 주입한다.
constructor(
@InjectRepository(User)
private userRepository: Repository<User>,
private jwtService: JwtService,
)
그 후 로그인시에 토큰을 생성한다
async userLogin(userDTO: UserDTO): Promise<{ accessToken: string }> {
const { userName, password } = userDTO;
const user = await this.userRepository.findOneBy({ userName });
if (user && (await bcrypt.compare(password, user.password))) {
//유저 토큰 생성 (Secret + Payload)
const payload = { userName }; //payload에는 중요한 정보를 넣지 않는다.
const accessToken = await this.jwtService.sign(payload); //알아서 시크릿과 pay로드는 합쳐서 accessToken을 생성해줌
return { accessToken };
} else {
throw new UnauthorizedException('login failed');
}
}
npm i -D @types/passport-jwt //로 타입정의
jwt.guard.ts 파일 작성
import { Injectable} from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
@Injectable()
export class JwtAuthGurad extends AuthGuard('jwt') {
//스트레터지를 자동으로 실행
}
jwt.strategy.ts 파일 작성
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { InjectRepository } from '@nestjs/typeorm';
import { ExtractJwt, Strategy } from 'passport-jwt';
import { Repository } from 'typeorm';
import { User } from '../entity/user.entity';
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor(
@InjectRepository(User)
private userRepository: Repository<User>,
) {
super({
secretOrKey:process.env.JWTSECRET, //시크릿키가 유효한지 체크
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), //이거 대소문자 구분잘해야함
});
}
async validate(payload) {
//토큰이 유효할시 실행됨
const { userName } = payload;
const user: User = await this.userRepository.findOne({
where: {
userName,
},
select: ['id','userName'], //id랑 userName만 받아오기
});
if (!user) {
throw new UnauthorizedException();
}
return user;
}
}
작성한 JwtStrategy를 AuthModule Provider 항목에 넣어주고
다른곳에서도 사용해야하기때문에 export도 해준다
providers: [AuthService,JwtStrategy],
exports: [JwtStrategy,PassportModule]
그후 UseGuard()를 이용해서 req.user를 받아오면 된다.
@Post('/login/test')
@UseGuards(JwtAuthGuard)
test(@Req() req){
return req.user;
}
인증
인증은 크게 2가지로 나눠진다. 세션 기반 인증과 토큰 기반 인증
세션
세션 기반 인증 : 로그인시 서버는 따로 생성해둔 세션DB에 해당 사용자를 저장시키고 이후 사용자로부터 요청이 올 때마다 세션 DB를 체크해 저장되어있는지 확인한다.
세션 인증 장점 : DB에 계속 저장중이므로 특정 사용자를 벤하거나 로그인된 다른 기기에서 다른기기의 로그아웃 등의 작업이 가능하다.
세션 인증 단점 : 요청마다 DB에 체크해야하므로 느리다(그래서 Redis를 주로 이용함). 세션 DB를 따로 두어야함. DB에 저장해야하므로 특정 길이 제한을 둔다.
토큰
토큰 기반 인증 : 로그인시 서버는 토큰을 생성해 보내주고 토큰을 따로 DB에 저장하지 않는다. 이후 사용자로부터 요청이 올 때마다 토큰에 대한 검증만 수행한다. JWT 를 주로 이용한다.
토큰 인증 장점 : 빠르다. Facebook, Google 등 계정으로 다른 서비스 로그인이 가능한 OAuth 구현이 가능하다. 따로 DB를 둘 필요가 없다.
토큰 인증 단점 : 토큰 자체가 정보이므로 탈취시 취약하다. ex) 토큰 유효기간 30분이라면 해당 사용자가 악의적인 사용자여도 내가 해당 사용자를 30분동안 벤하지 못함.
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
import { User } from '../entity/user.entity';
export const getUser = createParamDecorator((data, ctx: ExecutionContext):User => {
const req = ctx.switchToHttp().getRequest();
return req.user;
});