웅글웅글: Nest 게시판 + 채팅 7. 로그인

메밀·2024년 3월 21일
0

웅글웅글: NestJS

목록 보기
7/9
post-thumbnail

1. 로컬 로그인과 소셜 로그인

dependency 부분에서는 Spring Boot가 훨씬 편했고,
JWT 토큰 생성 부분은 NestJS가 편했다.

소셜 로그인은 딱히 누구의 손을 들어주기 애매하다 ㅋㅋㅋ

2. 로컬 로그인

1) 플로우

  • 유저가 ID, PW를 담은 request를 보내면, DB 저장값과 일치 여부를 검사한다.
    • 불일치 시 UnauthorizedException를 던져준다
  • 일치 시, Access Token(JWT)를 돌려준다.

2) 유저 입력값 DB랑 비교하기: Spring 승리🎉

	// DB 검증 로직
    const user = await this.userService.findByUsername(loginDto.username);
    const isPasswordMatch = await bcrypt.compare(loginDto.password, user.password);

    // ID, PW 검사
    if (!user || !isPasswordMatch) {
      console.log(!user ? "그런 사용자 없음" : "비번 틀림");
      throw new UnauthorizedException(!user ? "그런 사용자 없음" : "비번 틀림");
    }

DB의 암호화된 비밀번호와의 일치 여부를 확인하기 위해서 bcrypt를 사용했다.

이게 NestJS를 사용하면서 줄곧 느낀 귀찮음 중 하난데,
뭘 하나 만들려면 너무 많은 디펜던시가 필요한 느낌이다.

이 부분도 Spring Boot였다면
Spring Security를 추가하고 BCryptPasswordEncoder를 Bean으로 등록하기만 했으면 될 일이다.

요약하자면,

  1. 디펜던시 너무 많아서 NestJS가 귀찮은 감이 있다.
    1.1 Spring Security처럼 하나로 만들어줘!
  2. bcrypt 임포트도 잘 안된다.
  3. 다른 방법이 있는데 내가 모르는 걸 수도 있다😂

3) JWT Access Token 생성: NestJS 압승🎉

Access Token을 생성하는 건 NestJS가 압도적으로 편리하다.

아래와 같은 코드를 auth.module.ts에 추가하고,

JwtModule.register({
      secret: process.env.SECRET_KEY, // 토큰 시크릿 키
      signOptions: { expiresIn: "1h" } // 토큰 만료 시간
}),

⭐️ 토큰 발급은 JwtModule.register(payload) 메소드 한 줄로 해결할 수 있다

- Spring이었다면?

Spring이었으면 토큰 클래스 만들고, claim도 하나하나 가공하고, 기간도 가공해서 넣어줘야할 것이다.
이후 Jwt 라이브러리를 통해 JWT 토큰을 생성하고 리턴해야 한다.

// AccessToken.java
	public AccessToken(Long id, Key key, Map<String, String> claims) {
        LocalDateTime expiredAt = LocalDateTime.now().plusMinutes(EXPIRED_AFTER);
        Date expiredDate = Date.from(expiredAt.atZone(ZoneId.systemDefault()).toInstant());

        this.key = key;
        this.expiredAt = expiredAt;
        this.token = createJwtAuthToken(id, claims, expiredDate).get();
    }

    public Optional<String> createJwtAuthToken(Long id, Map<String, String> claimMap, Date expiredDate) {
        Claims claims = new DefaultClaims(claimMap);

        return Optional.ofNullable(
                Jwts.builder()
                        .setSubject(String.valueOf(id))
                        .addClaims(claims)
                        .signWith(key, SignatureAlgorithm.HS256)
                        .setExpiration(expiredDate)
                        .compact()
        );
    }

AccessToken 클래스의 일부만 발췌했는데도 이 정도다.

2. 소셜 로그인

1) 구글 로그인만 구현

NestJS 찍먹 과정이라 구글 로그인만 간단히 구현했다.
개발자 콘솔 설정은 스킵할 것이다.

- 구현 상세

프론트에서 소셜 로그인 provider가 준 oauthCode를 보내면,

    const queryString = window.location.search;
    const urlParams = new URLSearchParams(queryString);
    const oauthCode = urlParams.get("code"); // ⭐️ 얘를 보낸다

Google Guard, Google Strategy로 어플리케이션 인증 로직을 거친 후 Access Token을 돌려주는 방식이다.

해당 소셜 서비스에서 받은 데이터를 내 어플리케이션에 맞게 가공하고,
이미 해당 회원이 존재하면 UPDATE, 신규 회원일시 INSERT 하는 로직 자체는 스프링에서 구현했던 방식과 똑같다.

사실 당연하다. 똑같은 response를 받는 거니까 ㅋㅋㅋ

2) auth.controller.ts

  @Post("/auth/google/callback")
  @UseGuards(AuthGuard("google"))
  async googleAuthRedirect(@Req() req) {
    const user = req.user;
    return user;
  }

다른 Service 레이어를 타지 않고 컨트롤러에서 바로 리턴한다.

로그인 로직은 strategy에 있다는 의미다.

3) google.strategy.ts

Strategy는 Guard의 짝꿍같은 것이다.
Guard가 요청 처리 전 작동하는 미들웨어라면, Strategy는 그걸 처리하는 방식이다.

@Injectable()
export class GoogleStrategy extends PassportStrategy(Strategy, "google") {
  constructor(
    private readonly authService: AuthService
  ) {
    super({
      clientID: process.env.GOOGLE_CLIENT_ID,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET,
      callbackURL: "http://localhost:3000/auth/google/callback",
      passReqToCallback: true,
      scope: ["profile", "email"]
    });
  }

  async validate(request: any, accessToken: string, refreshToken: string, profile: any, done: VerifyCallback): Promise<any> {
    try {
      const user = await this.authService.validateOAuthLogin(profile); // ⭐️
      done(null, user);
    } catch (e) {
      done(e, false);
    }
  }
}

authService.validateOAuthLogin(profile)에 해당하는 코드는
해당 소셜 서비스에서 받은 데이터를 내 어플리케이션에 맞게 가공하고,
INSERT/UPDATE 후 Access token을 포함한 사용자 정보를 리턴한다.

크게 뭐가 나은 지점이 느껴지지 않아서 이번엔 무승부!

3. 남은 의문들...🧐

Spring Security에서 인증된 사용자 정보는 Security Context에 저장된다.
NestJS에서 인증된 사용자 정보는 어디에 저장되는 걸까?

사실 이미 공부했다.
다음 포스트에서 알아볼 것이다🤣

0개의 댓글