(Spring) 취향 기반 향수 추천 서비스 - 7. 세번째 기능 (로그아웃)

김준석·2023년 3월 26일
0

향수 추천 서비스

목록 보기
8/21

블랙리스트란?

Jwt는 특성상, 한번 발급된 토큰은 그 유효기간이 만료되거나, 강제로 만료시키지 않는 한 계속 유효하다.
예를 들어, AccessToken의 만료시간을 30분으로 설정했다고 가정하자.
클라이언트에서 로그아웃 처리를 하였는데, 만료시간 내에 제 3자가 AccessToken을 탈취하여 Api 요청을 보낸다면 보안적으로 큰 문제가 발생할 수 있다.
물론 AccessToken을 30분으로 짧게 설정하였지만 그래도 보안의 위험성은 존재하기 때문에, 한번 로그아웃 하면 해당 AccessToken으로는 Api에 접근을 할 수 없게 만들어야 한다.
장점 : 구현하기 쉽다. 만일 Redis와같은 휘발성 메모리 데이터베이스를 사용하면 빠르고 효율적인 관리가 가능하다.
단점 : 매번 요청 시 BlackList를 조회해야해서 서버에 부하가 발생할 수 있다. 또한 분산환경에서 공유된 BlackList를 유지하기 어려운 문제가 있다.

나는 트래픽이 많이 몰릴 서비스도, 분산 환경도 아니기 때문에 비교적 구현하기 쉬운 BlackList를 사용하기로 했다.

어떻게 쓸건데?

클라이언트에서 AccessToken과 함께 로그아웃 요청을 보내면 인증을 거친 뒤, 서버는 AccessToken과 memberId를 BlackList Database에 저장시킨다. 이후 해당 토큰으로 요청을 보낼 때, 서버는 BlackList를 조회하여 로그아웃한 유저인지 판별한다.

Domain

Blacklist.java

@Entity(name = "blacklist")
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Blacklist {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "blacklist_id", nullable = false)
    private Long id;

    @ManyToOne
    @NotNull
    private Member member;

    @NotNull
    private String accessToken;

    @Builder
    public Blacklist(Long id, Member member, String accessToken) {
        this.id = id;
        this.member = member;
        this.accessToken = accessToken;
    }
}
  • MemberId를 받아야하기 때문에 Member에 ManyToOne을 걸었다.

Repository

BlacklistRepository.java


public interface BlacklistRepository extends JpaRepository<Blacklist, Long> {

    boolean existsByAccessToken(String accessToken);
}

Service

LogoutService.java

@Service
public class LogoutService {
    private final TokenRepository tokenRepository;
    private final MemberService memberService;
    private final BlacklistRepository blacklistRepository;

    public LogoutService(TokenRepository tokenRepository, MemberService memberService, BlacklistRepository blacklistRepository) {
        this.tokenRepository = tokenRepository;
        this.memberService = memberService;
        this.blacklistRepository = blacklistRepository;
    }

    @Transactional
    public void permitLogout(LogoutRequestDto logoutRequestDto, String accessToken) {
        Blacklist blackList = Blacklist.builder()
                .member(memberService.findByMemberPk(logoutRequestDto.getMemberId()))
                .accessToken(accessToken)
                .build();
        blacklistRepository.save(blackList);
        tokenRepository.deleteByMemberId(logoutRequestDto.getMemberId());
    }

    public void isUserAlreadyLogout(String accessToken){
        if(blacklistRepository.existsByAccessToken(accessToken)){
            throw new MemberAlreadyLogoutException();
        }
    }
  • isUserAlreadyLogout()은 이미 유저가 로그아웃했을 경우, MemberAlreadyLogoutException()을 발생시킨다.
  • permitLogout()은 memberId와 AccessToken을 받아 BlackList에 저장하는 메서드이다. 또한 로그아웃을 완료하면 토큰을 갱신하지 못하게 RefreshToken도 지워주고 있다.

개선할 점

Mysql로 작성하였기 때문에, 로그아웃 요청 수에 비례해 서버의 부하가 커질 수 있다. 이를 위해 추후 Redis로 교체하거나, 토큰이 만료된 시점부터 자동으로 BlackList 데이터를 삭제하는 방법을 사용할 수 있겠다.

로그인 관련 글은 여기서 끝내고 다음 글에서 세번째 기능을 마치려고 한다!

profile
기록하면서 성장하기!

0개의 댓글