개발 낙서장

[TIL] 내일배움캠프 46일차 - JWT 활용 본문

Java/Sparta

[TIL] 내일배움캠프 46일차 - JWT 활용

권승준 2024. 2. 29. 19:22

오늘의 학습 키워드📚

JWT 활용

유저 인증을 인가 필터에서 JWT를 발급받고 검증을 통해 진행했다.
JWT에는 유저의 EMAIL, NAME, ADDRESS, ROLE 정보가 포함돼있다.

이전에는 JWT를 그냥 단순히 열쇠라고만 생각했다. 인증과 인가를 통과하기 위한 열쇠.
그 안에 담긴 정보들을 활용할 방법은 구상하지 않고 그냥 강의에서 나온 코드들을 복사-붙여넣기 했었다.

    public String createToken(User user) {
        Date date = new Date();

        long TOKEN_TIME = 60 * 60 * 1000;

        Claims claims = Jwts.claims().setSubject(user.getEmail());
        claims.put("userId", user.getUserId());
        claims.put("name", user.getName());
        claims.put("role", user.getRole());
        claims.put("address", user.getAddress());

        return BEARER_PREFIX + Jwts.builder()
            .setExpiration(new Date(date.getTime() + TOKEN_TIME))
            .setIssuedAt(date)
            .signWith(key, signatureAlgorithm)
            .setClaims(claims)
            .compact();
    }

이런 식으로 JWT의 Claims에 정보들을 넣어서 토큰을 만들어주었고

        String token = jwtUtil.resolveToken(httpServletRequest);

        if (Objects.nonNull(token)) {
            if (jwtUtil.validateToken(token)) {
                Claims info = jwtUtil.getUserInfoFromToken(token);

                String email = info.getSubject();

                SecurityContext context = SecurityContextHolder.createEmptyContext();
                
                UserDetails userDetails = userDetailsServiceImpl.getUserDetails(info);
                
                Authentication authentication = new UsernamePasswordAuthenticationToken(userDetails,
                    null, userDetails.getAuthorities());
                
                context.setAuthentication(authentication);
                
                SecurityContextHolder.setContext(context);
            }
        ...

인가 필터에서 토큰을 가져와 유저 정보를 뽑아내 검증을 해주었다.
문제는 UserDetailsServiceImpl에 있는 getUserDetails라는 메소드이다.

    public UserDetails getUserDetails(String email) {
        User user = userRepository.findByEmail(email)
            .orElseThrow(() -> new UsernameNotFoundException("Not Found " + email));

        return new UserDetailsImpl(user);
    }

결국 토큰을 사용하는 이유는 서버에 무상태를 위함이다.
즉, DB에 접근을 하지 않아야 한다. 하지만 UserRepository에 접근해 탐색 쿼리문을 날리게 된다.
이 경우 유저가 많아질 수록, 작업의 수가 증가할 수록 쿼리 문이 많아져 N+1 문제가 발생한다.
또한 토큰의 이점을 하나도 활용하지 못하고 있다. 결국 저 User를 활용하는 다른 로직들에서는 몇몇의 정보(이름, 주소, Id, Email 등등)만 필요하다. 유저의 모든 정보가 필요한 것이 아니다.
그럼 토큰에 저장돼있는 정보들만 가져와 유저 객체를 넣어 반환해주면 되는 것이다!

    public UserDetails getUserDetails(Claims info) {
        User user = makeUser(info);
        
        return new UserDetailsImpl(user);
    }
    
    private User makeUser(Claims info) {
        User user = new User(info.get("name").toString(), "", info.getSubject(),
            info.get("address").toString(), UserRoleEnum.valueOf(info.get("role").toString()), 0L);
        user.setUserId((long) (int) info.get("userId"));

        return user;
    }

이런 식으로 유저에 필요한 정보들만  JWT에서 꺼내 넣어주면 불필요한 쿼리문을 날릴 필요 없이 간단하게 정보를 추가해줄 수 있다.
(주의해야 할 점은 Enum을 사용할 경우 valueOf로 변환을 해줘야 하고 id같은 경우 Integer에서 long으로 바로 변환이 안 되므로 int로 변환한 다음 long으로 변환해야 에러가 발생하지 않는다.)

이렇게 불필요한 쿼리문을 날리지 않더라도 유저 정보를 올바르게 갖고 오고 인가 처리도 되어 문제 없이 사용할 수 있다.


오늘의 회고💬

팀 프로젝트를 최종적으로 회고하면서 역시 또 많이 배운 것 같다. 특히 JWT에 관한 진정한 활용법을 약간이나마 깨닫게 된 것 같아서 즐겁다.

 

내일의 계획📜

다음주부터 새로운 주차가 시작된다. 두렵기도 하면서도 기대가 된다.

Comments