Kuma's Curious Paradise
[데브 캠프] 2일차 예시 코드 공부하기 - 궁금한 부분 정리 본문
1. jwtUtil이 아니라 jwtProvider라는 이름의 클래스를 사용한다. 이 둘은 어떤 차이가 있을까?
Util 클래스는 어디서나 가져다 쓸 만한 메서드들을 주제에 따라 하나로 모아 놓은 것으로, 직접 호출할 수 있는 static 메서드로 구성하는 경우가 많다. 현재 작성된 로직은 static 필드들을 쓰지만, 메서드는 static이 아니다. 하지만 여전히 JWT와 관련된 서비스를 제공하는 역할을 하므로 provider라는 클래스명을 채택한 것이 아닐까 싶다.
2. AccessLog 클래스는 무엇이며 어떻게 사용할까?
사용자의 AccessLog를 관리하기 위한 클래스다. 어디서, 어떤 ip로, 몇 시에 접속했는지 등의 기록을 user와 매핑하여 관리한다. 이는 사용자가 수상한 ip로 접근했을 때 바로 알아채 접근을 차단하여 보안을 높일 수 있는 방법이다. 예시 코드에는 해당 내역을 저장만 할 뿐, 적극적으로 활용하고 있지는 않아서 어떻게 구현해 볼지 고민해 볼 예정이다.
3. TokenPayload 클래스는 왜 생겼을까?
TokenPayload 클래스를 만들고 이를 생성하는 로직을 작성하면, 다양한 정보를 담아 페이로드를 구성할 수 있어 확장성이 높아진다. 하지만 예시 코드에서는 이를 적절히 활용하고 있는 것 같지 않다. 코드를 살펴보자.
public TokenPayload createTokenPayload(String email, TokenType tokenType) {
Date date = new Date();
long tokenTime = TokenType.ACCESS.equals(tokenType) ? ACCESS_TOKEN_TIME : REFRESH_TOKEN_TIME;
return new TokenPayload(
email,
UUID.randomUUID().toString(),
date,
new Date(date.getTime() + tokenTime)
);
}
public String createToken(TokenPayload payload) {
return BEARER_PREFIX +
Jwts.builder()
.subject(payload.getSub()) // 사용자 식별자값(ID)
.expiration(payload.getExpiresAt()) // 만료 시간
.issuedAt(payload.getIat()) // 발급일
.id(payload.getJti()) // JWT ID
.signWith(key, algorithm) // 암호화 Key & 알고리즘
.compact();
}
create access token, create refresh token 이렇게 두 개의 토큰 생성 로직을 쓰는 대신 이 방식을 채택한 것 같다.
하지만 이 경우 access 토큰과 refresh 토큰에 들어가는 내용이 획일화되기 때문에, 이 부분에서는 오히려 확장성이 줄어든다고 생각된다. refresh 토큰은 access token 처럼 사용자 정보를 많이 담을 필요가 없기 때문에 차라리 토큰 생성 로직을 두 개로 작성하는 것이 좋아 보인다. 또한 payload로 감쌌다가 다시 풀어서 토큰을 만드는 것이 불필요한 작업처럼 보이기도 한다.
4. security.headers(headers -> headers.frameOptions(HeadersConfigurer.FrameOptionsConfig::sameOrigin));
이는 쿠키의 same site 설정과 비슷한 것으로, “동일 출처” 개념을 사용한다. 하지만 쿠키의 same site 설정은 CSRF 공격을, 헤더의 X-Frame-Options 설정은 페이지 렌더링에서 클릭재킹 공격을 방지하는 데 사용된다는 차이점이 있다.
5. TokenBlackList는 왜 만든걸까?
수상한 토큰이 들어오면 블랙리스트에 있는지 확인이 가능하기 때문에 사용하는 것으로 보인다. 하지만 유지관리가 어려워 보이기도 하고, 해당 토큰을 언제까지 저장해야 할지도 불분명해서 token repository를 만들어 관리하는 것이 나은지, tokenBlackListRepository를 관리하는 것이 나은지 알 수 없었다.
먼저 token repository를 hashmap으로 구성하여 key는 access token을, value는 refresh token을 넣어 저장하는 방안을 생각 중이다.
6. deleteAllInBatch 메서드는 무엇일까?
@Override
public void removeExpiredTokens() {
List<TokenBlackList> expiredList = tokenBlackListRepository.findAllByExpiresAtLessThan(new Date());
tokenBlackListRepository.deleteAllInBatch(expiredList);
}
InBatch는 여러 작업을 한 번에 모아서 처리하는 것을 말한다. deleteAll()을 호출할 경우, 각 엔티티마다 delete 쿼리가 하나씩 날아가지만, deleteAllInBatch()를 호출할 경우 쿼리를 길게 만들어서 한 번에 날려 엔티티들을 삭제한다. 쿼리가 훨씬 줄어들기 때문에 대량의 데이터를 한 번에 삭제할 때 매우 유용하지만, 영속성 컨텍스트를 무시한다는 단점이 있다. 영속성 컨텍스트는 개별 엔티티의 생명주기를 관리하기 때문이다. 이 문제는 삭제 이후 entityManager.flush()를 해주면 간단히 해결 가능하다.