캡스톤 설계 [건물별 소통 플랫폼 BBC]

[백엔드] 회원가입,로그인(JWT토큰&리프레시 토큰&가드&커스텀데코레이터) [2]

softmoca__ 2024. 3. 7. 21:39
목차

https://softmoca.tistory.com/309

 

[백엔드] 회원가입,로그인(JWT토큰&리프레시 토큰) [1]

https://softmoca.tistory.com/294 세션, 쿠키, JWT 토큰 및 인증과 인가 개념 정리 로그인기능을 구현한는 것도 어렵지만 무엇보다 로그인 상태를 '유지'하는거도 만만치 않게 어려운 일이다. 예로 들어 nav

softmoca.tistory.com

해당 포스팅의 연장 내용입니다 ~

 

 

 

1) 요청객체 (request)를 불러오고  authorization header로부터 토큰을 가져온다.

2) authService.extractTokenFromHeader를 이용해서 사용할 수 있는 형태의 토큰을 추출한다.

3) authService.decodeBasicToken을 실행해서 email과 password를 추출한다.

4) email과 password를 이용해서 사용자를 가져온다.( authService.authenticateWithEmailAndPassword)

5) 찾아낸 사용자를 (1) 요청 객체에 붙여준다.   ( req.user = user)

 

 

 

basic-token.guard.ts

@Injectable()
export class BasicTokenGuard implements CanActivate{
    constructor(private readonly authService: AuthService){}

    async canActivate(context: ExecutionContext): Promise<boolean> {
        const req = context.switchToHttp().getRequest();//요청 객체 가져오기
        const rawToken = req.headers['authorization']; //요청 헤더에 있는 토큰 가져오기

        if(!rawToken){
            throw new UnauthorizedException('토큰이 없습니다!');
        }

        const token = this.authService.extractTokenFromHeader(rawToken, false);
        const {email, password} = this.authService.decodeBasicToken(token);
        const user = await this.authService.authenticateWithEmailAndPassword({
            email,
            password,
        });

        req.user = user;
        return true;
    }
}

basic토큰이 있는지 확인 하는 가드로서 유효한 토큰으로 요청을 보낸경우 요청 객체에 사용자의 정보를 담은채 응답하기 전까지 쭉 아래와 같은 백엔드 플로우 내내 사용자의 정보를 가지고 있을 수 있다.

 

요청에 사용자의 정보가 잘 실리는지 확인

  @Post("login/email")
  @UseGuards(BasicTokenGuard)
  loginEmail(@Headers("authorization") rawToken: string, @Request() req) {
    const token = this.authService.extractTokenFromHeader(rawToken, false);
    const email_password = this.authService.decodeBasicToken(token);
    console.log(req);
    return this.authService.loginWithEmail(email_password);
  }

 

많은 req의 속성중 user라는 key로 사용자의 정보가 잘 담겨 있다.

 

+ 물론 대부분 커스텀 데코레이터로 간편히 사용자의 정보를 요청 내내 실을 수 있어 @Requset는 안쓴다.(차후 로직 추가 예정)

 

 

 

 

 

 

bearer-token.guard.ts

request에 넣을 정보

1. 사용자의 정보 -user

2. token -token

3. tokenType -access 또는 refesh

@Injectable()
export class BearerTokenGuard implements CanActivate {
    constructor(private readonly authService: AuthService,
        private readonly usersService: UserService
      ) { }

    async canActivate(context: ExecutionContext): Promise<boolean> {
  
        const req = context.switchToHttp().getRequest();
        const rawToken = req.headers['authorization'];

        if (!rawToken) {
            throw new UnauthorizedException('토큰이 없습니다!');
        }

        const token = this.authService.extractTokenFromHeader(rawToken, true);
        const decoded = await this.authService.verifyToken(token);
        const user = await this.usersService.getUserByEmail(decoded.email);

        req.user = user;
        req.token = token;
        req.tokenType = decoded.type;

        return true;
    }
}

 

 

 

 

 

Access-token.guard.ts

@Injectable()
export class AccessTokenGuard extends BearerTokenGuard {
    async canActivate(context: ExecutionContext): Promise<boolean> {
        await super.canActivate(context);
        const req = context.switchToHttp().getRequest();

        if (req.tokenType !== 'access') {
            throw new UnauthorizedException('Access Token이 아닙니다.');
        }

        return true;
    }
}

 

Refresh-token.guard.ts

@Injectable()
export class RefreshTokenGuard extends BearerTokenGuard {
    async canActivate(context: ExecutionContext): Promise<boolean> {
        await super.canActivate(context);
        const req = context.switchToHttp().getRequest();

        if (req.tokenType !== 'refresh') {
            throw new UnauthorizedException('Refresh Token이 아닙니다.');
        }

        return true;
    }
}

super를 사용해서  Bearer-token 가드에 있는 공통된 로직을 상속받아 구현.

 

유저 정보를 요청에 주입하는 데코레이터

유저 데코레이터는 엑세스토큰가드를 사용해야만 사용할 수 있다.

export const CurrentUser = createParamDecorator(
    (data, context: ExecutionContext) => {
    const req = context.switchToHttp().getRequest();
    const user = req.user 
    if (!user) {
        throw new InternalServerErrorException('Request에 user 프로퍼티가 존재하지 않습니다!');
    }
    return user;
});

Access-token가드로 인해 이미 req.user의 값이 들어가 있다.

하지만 가드를 실수로 걸어주지 않았다면 intertal에러를 던저 프론트단 문제가 아닌 서버쪽 문제임을 알수 있게 한다.

 

 

게시물 생성시 엑세스토큰 가드와 유저데코레이터로 얻은 사용자의 id가 authorId로 잘 들어와 새로운 게시물이 잘 저장되는것을 확인.