환경
- SpringBoot 3.2.0
- Refresh Token은 Redis가 아닌 DB에 저장, 관리 + 쿠키(HTTP ONLY) 저장
- Access Token은 클라이언트 자바스크립트 변수로 관리 (휘발성)
Refresh Token을 DB에 계속 저장한다면, 기간이 만료된 토큰들이 계속 쌓일 것이고, BaseEntity를 이용하여 생성한 시간과 만료 시간을 계산하여 해당 유저의 것이어도 만료됐다면 새로 업데이트하는 로직을 만들어도, 결국에는 매번 검증할 때마다 DB를 전부 돌아야 하기 때문에 성능적으로 고민이 됐다.
이전 방식 중 일부 코드 (토큰 엔티티, 로그인 서비스 코드)
public class RefreshToken {
...
@ManyToOne
@JoinColumn(name = "member_id", nullable = false)
private Member member;
...
}
@Transactional
public LoginResponse login(LoginRequest loginRequest) {
UsernamePasswordAuthenticationToken authenticationToken = loginRequest.toAuthentication();
Authentication authenticate = authenticationManagerBuilder.getObject().authenticate(authenticationToken);
TokenDTO.TokenInfoDTO tokenInfoDTO = jwtTokenProvider.generateTokenDto(authenticate);
log.info("로그인 API 중 토큰 생성 로직 실행");
String userEmail = authenticate.getName();
Member user = getMemberByEmail(userEmail);
String refreshToken = tokenInfoDTO.getRefreshToken();
refreshTokenService.saveOrUpdate(user, refreshToken);
return LoginResponse.builder()
.memberDto(MemberDto.fromEntity(user))
.tokenInfo(tokenInfoDTO.toTokenIssueDTO())
.build();
}
-> 내가 해결한 방식
Member와 Refresh Token을 1대1 매핑을 이용해 Redis의 키 밸류 처럼 하나의 Member에 하나의 Refresh Token을 갖게 하였다. 그렇게 되면 토큰이 만료됐다면 만료됐는지만 검사하고 바로 업데이트를 해주면 되는 방식이라서, DB를 싹 훑는 불필요한 작업을 없앨 수 있었다.
그리고 회원가입할 때 애초에 Refresh Token이 null값으로 들어있는 껍데기 토큰과 멤버를 같이 DB에 저장한다.
그렇게 되면, 매번 로그인할 때마다 DB에 불필요한 Refresh Token을 생성하는 것을 막을 수 있고, 매번 업데이트만 하기 때문에 깔끔하게 관리할 수 있다!!
멤버와 토큰 엔티티
public class RefreshToken {
...
@OneToOne
@JoinColumn(name = "member_id", nullable = false)
private Member member;
...
}
public class Member extends BaseTimeEntity {
...
@OneToOne(mappedBy = "member", cascade = REMOVE)
private RefreshToken refreshToken;
}
로그인, 회원가입 서비스 코드
@Transactional
public LoginResponse login(LoginRequest loginRequest) {
UsernamePasswordAuthenticationToken authenticationToken = loginRequest.toAuthentication();
Authentication authenticate = authenticationManagerBuilder.getObject().authenticate(authenticationToken);
TokenDTO.TokenInfoDTO tokenInfoDTO = jwtTokenProvider.generateTokenDto(authenticate);
log.info("로그인 API 중 토큰 생성 로직 실행");
String userEmail = authenticate.getName();
Member member = memberRepository.findByEmail(userEmail).orElseThrow(RuntimeException::new);
String refreshToken = tokenInfoDTO.getRefreshToken();
member.getRefreshToken().updateToken(refreshToken);
return LoginResponse.builder()
.memberDto(MemberDto.fromEntity(member))
.tokenInfo(tokenInfoDTO.toTokenIssueDTO())
.build();
}
@Transactional
public void signUp(SignUpRequest signUpRequest) {
...
String encodedPassword = passwordEncoder.encode(signUpRequest.password());
Member newMember = signUpRequest.toEntity(encodedPassword, EMAIL, ROLE_USER);
RefreshToken refreshToken = RefreshToken.builder()
.member(newMember)
.build();
memberRepository.save(newMember);
refreshTokenRepository.save(refreshToken);
}
추가로 프론트분들과 리프레쉬 토큰, 액세스 토큰의 관리 방법에 대해 논의하면서 정보를 얻은 블로그 출처 남김
'대충 넘어가지 않는 습관을 위한 기록' 카테고리의 다른 글
동시성 처리 기록 (1) | 2024.02.25 |
---|---|
엘라스틱 캐시를 이용한 redis 적용 (0) | 2024.01.24 |
AWS 도메인 구매 후, https 관련 (0) | 2023.12.26 |
@RequestParam 사용법 (0) | 2023.12.05 |
ArrayList와 LinkedList의 차이 (1) | 2023.12.04 |