Redis를 이용한 로그아웃?

·2022년 4월 21일
0

TIL

목록 보기
19/36
post-thumbnail

오늘 수업 제법 재밌었는데, 다 포스팅을 하고 잘 수 있을련지 모르겠다. 떼잉

오늘의 과제는 Redis를 이용하여 로그아웃을 구현하는 것이였다.

아마 이 글을 보고 있는 사람이라면, 딱 나와 비슷한 레벨의 수준일 것이기에
코드를 올려놓고 리뷰하는 식으로 진행을 하려고 한다.

auth.resolver.ts

/// 임포트 한 것들 ////
import { Cache } from 'cache-manager';
import { CACHE_MANAGER, Inject } from '@nestjs/common';
import * as jwt from 'jsonwebtoken';
///////////////////////////////////////////
 constructor(
    @Inject(CACHE_MANAGER)
    private readonly cacheManager: Cache,
  ) {}
  // 생성자 주입, Redis는 임시저장해주는 곳으로 사용하기 떄문에 흔히 캐시라고 부르는 것이라
  // 이름도 캐시로 만들어져있다. (결국은 캐시가 레디스고 레디스가 캐시인 건가 싶기도 하고)
@Mutation(() => String)
  async logout(
    //
    @Context() context: any,
    //
    // 생성자로 컨텍스트 받아오기, 이렇게 해놔야 프론트에서 쏘는 것들을 잡아올 수 있다.
    // rest api라면 req로 받아왔을 듯?
    //
  ) {
    const refresh = context.req.rawHeaders
      .filter((ele) => {
        return ele.match(/refresh/);
      })[0]
      .split('=')[1];

    const access = context.req.rawHeaders
      .filter((ele) => {
        return ele.match(/Bearer/);
      })[0]
      .split(' ')[1];
      
      // 위에 토큰을 가져오는 것은 사람마다 차이가 있을 것 같다.
      // headers에도 담겨있고
      // rawHeaders에도 담겨있었는데
      // headers가 객체의 형식으로 되어있어서 가져오기는 더 쉬울 것 같았는데
      // 그냥 한번 남들과 다르게 해볼라고 써봤다(?) 
      // rawHeaders는 배열의 형식을 가지고 있고
      // 데이터를 주고 받을 때 순서대로 쌓아서 보내준다고 한다

    try {
      jwt.verify(access, 'myAccessKey');
      jwt.verify(refresh, 'myRefreshKey');
    } catch {
      throw new UnauthorizedException();
      // 이것은 인증되지 않음을 뜻하는 용어(?)다 
      // 인증이 안되면 너 인증 안댔다. 하고 아래 있는 코드를 실행하지 않고 에러뱉고 멈춘다.
    }
    
    // 과제 조건이 try catch문을 쓰라는 것이여서 썼다.
    // jwt.verify는 알아서 토큰을 검증을 해주는 작업을 해준다.
    // ...가드를 왜만들었나 싶기도 해서 뭔가 어....음....
    //
    // 결국 jwt.sign은 토큰을 만들어주는 것
    // jwt.verify는 토큰을 인증하는 역할을 한다.
    //
    // 아무튼 앞에는 해당하는 값을 넣고 뒤에는 비밀번호를 넣으면 된다.
    // 나는 액세스토큰의 비밀번호는 myAccessKey
    // 리프레시토큰의 비밀번호는 myRefreshKey의 형식으로 되어있어서 저렇게 넣었다.
    // 
    //
    await this.cacheManager.set(access, 'accessToken', { ttl: 120 });
    await this.cacheManager.set(refresh, 'refreshToken', { ttl: 120 });
    //
    // 요것은 Redis의 문법이다.
    // 형태는 앞에는 key, 뒤에는 value 한칸 더는 객체의 형태로 ttl 데이터가 생존해있는 시간을 적어넣을 수 있다.
    // 만약에 0으로 쓸 경우 서버를 내리기 전까지 유지가 된다.
    // 일단 저건 120초라는 의미다. s나 h같은거 붙여볼까...?
    //
    return '로그아웃에 성공했습니다';
    //
    // 확인해봤는데, 레디스에 저장되어있는 시간동안 재인증이 불가능해진다.
    // ....왜? verify의 구조를 조금 더 알아봐야할 것 같다.
    //
  }


throw new UnauthorizedException(); 를 사용하면 발생하는 에러


jwt-access.strategy.ts

 constructor(
    @InjectRepository(User)
    private readonly userRepository: Repository<User>,

    @Inject(CACHE_MANAGER)
    private readonly cacheManager: Cache,
    // 여기는 검증을 하기 위하여 Redis를 불러올 것이기 때문에 캐시를 불러온다.
    // 임포트는 똑같이 해주면 된다.
  ) {
    super({
      //
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      secretOrKey: 'myAccessKey',
      passReqToCallback: true,
      //
      // 이것을 사용하지 않으면, 토큰을 복호화했을 경우에 들어있는 값만 나온다.
      // 만약 이것을 true로 해놓으면 일반적으로 컨텍스트 중 req가 포함되어있는 것을 받아볼 수 있다.
      //
    });
  }

  async validate(req, payload) {
  // 위에 passReqTocallback을 설정해놨기때문에
  // validate의 함수에 req 값을 받아올 수 있다.
    const rawHeadersInAccessToken = req.rawHeaders
      .filter((ele) => {
        return ele.match(/Bearer/);
      })[0]
      .split(' ')[1];
      // 이건 위에서 쓴 형식과 동일하다
    //
    const check = await this.cacheManager.get(rawHeadersInAccessToken);
    //
    // 로그아웃 당시 해당하는 액세스 토큰은 키가 되었고, 'accessToken은 값이 된 상태다.
    // 그래서 로그아웃할 때 사용한 액세스 토큰이라면 값이 존재하여 true고
    // 새로운 액세스 토큰으로 찾아오면 null이라는 값을 받게 된다.
    //
    if (check) throw new UnauthorizedException();
    //
    // 값이 존재하면 로그아웃 했던 것과 같은 토큰이기에 너 인증 안댓음! 에러 발송
    // 아니면 코드는 아래로 흘러간다.

    const user = await this.userRepository
      .createQueryBuilder()
      .where({ user_email: payload.user_email })
      .getOne();
      
      // 이것은 그냥 내 욕심에 넣어놓은 코드인데
      // 나는 유저 id는 난수인 uuid로 생성되는 id고
      // 실질적으로 쓰는 아이디는 email인데 쿼리문을 돌릴 때는 고유값인 PK를 가져와서 
      // 사용하는 것이 좋은 편이라
      // 굳이 찾아와서 토큰 정보에 PK를 꽂아놓은 상태다.

    return {
      user_email: payload.user_email,
      user_id: user.user_id,
    };
    // 아무튼 토큰정보에 이런거 담아서 넘겨줌 복호화하면 이메일이랑 id를 볼 수 있다.
  }

설정파일 속으로 들어왔을 때, StrategyOptions에 있는 것들

잘 보면 passReqToCallback을 사용하지 않을 경우인 VerifyCallback에는
req라는 값이 없지만

딱봐도 쓰는 것 같은
VerifyCallbackWithRequest의 인터페이스 속에는 req 타입이 존재하는 것을 확인할 수 있다.
그리고 저기 노란색으로 대문자인 Request는 express에서 땡겨쓰는 것이라
우리가 생각하는 웹 상의 리퀘스트가 된다.


리프레시 토큰도 똑같이 만들어놔서 굳---이 안올려도 될 것 같아서 그냥 패스한다!
Redis 쓰러갈거다.

profile
물류 서비스 Backend Software Developer

0개의 댓글