미니 프로젝트 - JWT 로그아웃 구현

Zyoon·2025년 5월 31일

미니프로젝트

목록 보기
16/36
post-thumbnail

📘JWT 사용하여 로그아웃 기능 구현


JWT 로그인 기능 구현

📗JWT 로그인 기능 구현

서버에서의 JWT 로그아웃 처리

  • Refresh 토큰은 DB 에서도 관리하기 때문에 처리하기가 쉽다.
  • 하지만 Access 토큰은 서버에서 관리 할 수가 없기 때문에 처리가 어렵다.
  • 그래서 로그아웃시 Refresh 토큰은 삭제, Access 토큰은 블랙리스트 DB 에 넣어 무효화 시킨다.

로그아웃 기능 흐름

[로그아웃 흐름]

1. 헤더와 쿠키에서 토큰 가져옴

2. Access Token 블랙리스트 DB 등록

3. 블랙리스트 등록된 Access Token 은 유효성 소멸

4. Refresh Token DB 삭제 및 쿠키에서 리셋

BlackListEntity

@Getter
@Entity
@Table(name = "tokenBlackList")
public class TokenBlackList{
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false, unique = true)
    private String accessToken;

    public TokenBlackList() {
    }

    public TokenBlackList(String accessToken) {
        this.accessToken = accessToken;
    }
}
  • 유효성 소멸시킬 Access Token 을 담을 블랙리스트 Entity 생성

Controller

@PostMapping("/logout")
public ResponseEntity<Void> logout(HttpServletRequest request,
                                   HttpServletResponse response){

    //Cookie와 Header 에서 Token 가져오기
    String accessToken = tokenExtractor.extractAccessTokenFromHeader(request).orElse(null);
    String refreshToken = tokenExtractor.extractRefreshTokenFromCookie(request).orElse(null);

    authService.logout(new TokenDto(accessToken,refreshToken));

    //쿠키에서 refreshToken 리셋
    tokenCookieUtils.deleteRefreshTokenCookie(response);

    return new ResponseEntity<>(HttpStatus.OK);
}
  • 쿠키와 헤더에서 토큰을 받아온다.
  • 비즈니스 로직 수행 후 쿠키에서 Refresh 토큰을 삭제한다.

TokenExtractor

// Header 에서 access token 값을 가져온다.
public Optional<String> extractAccessTokenFromHeader(HttpServletRequest request) {
    return Optional.ofNullable(request.getHeader("Authorization")) // Authorization 헤더의 값을 Optional로 감싼다.
            .filter(header -> header.startsWith("Bearer ")) // 값이 "Bearer "로 시작하는지 확인 - Bearer : 토큰 종류
            .map(header -> header.substring(7)); // "Bearer " 길이만큼 잘라서 리턴
}

// Cookie 에서 refresh token 값을 가져온다.
public Optional<String> extractRefreshTokenFromCookie(HttpServletRequest request) {
    Cookie[] cookies = request.getCookies();
    if (cookies == null) return Optional.empty();

    //쿠키에서 토큰 찾아서 유효성 검증 후 리턴
    return getRefreshTokenFromCookie(cookies);
}

//쿠키에서 refresh_token 이름으로 토큰 받기
private Optional<String> getRefreshTokenFromCookie(Cookie[] cookies){
    for (Cookie cookie : cookies) {
        if ("refresh_token".equals(cookie.getName())) {
            String refreshToken = cookie.getValue();
            if(jwtTokenProvider.validateToken(refreshToken)){
                return Optional.ofNullable(cookie.getValue());
            }
        }
    }
    return Optional.empty();
}

//토큰 유효성 검증(key, 만료, 유효 시작, 형식)
//보안상 예외 처리는 최소화
public boolean validateToken(String token) {
    try {
        Jwts.parserBuilder()
                .setSigningKey(key)
                .build()
                .parseClaimsJws(token);
        return true;
    } catch (JwtException | IllegalArgumentException e) {
        return false;
    }
}
  • 쿠키와 헤더에 있는 토큰을 추출한다.
  • Null 방지를 위해 Optional 타입으로 리턴해준다.

AuthService

//로그아웃 기능
public void logout(TokenDto tokenDto) {

    //accessToken 블랙리스트 추가 -> 이미 추가되어 있거나 null 일 경우 예외 처리(비정상 접근)
    if(!tokenBlacklistService.isAccessTokenBlackListOrSave(tokenDto.getAccessToken())) {
        throw new CustomException(ErrorCode.INVALID_ACCESS);
    }

    //토큰 있을 시 실행
    if(tokenDto.getRefreshToken() == null) {
		    throw new CustomException(ErrorCode.INVALID_ACCESS);
    }

    //DB 에서 refreshToken 삭제
    tokenService.deleteRefreshTokenDB(tokenDto.getRefreshToken());

}

//Access 토큰 블랙리스트 등록
public boolean isAccessTokenBlackListOrSave(String accessToken) {

    if(accessToken == null || tokenBlackListRepository.existsByAccessToken(accessToken)) return false;

    tokenBlackListRepository.save(new TokenBlackList(accessToken));
    return true;
}
  • Access 토큰이 BlackList 에 이미 있을 시 해당 요청은 비정상적인 접근으로 판단하여 예외처리한다.
  • Refresh 토큰이 쿠키에 없을 시 예외처리한다.
  • 해당 조건 통과 시 DB 에서 Refresh 토큰을 삭제한다.

TokenCookieUtils

//클라이언트 쿠키에서 삭제 (MaxAge = 0)
public void deleteRefreshTokenCookie(HttpServletResponse response) {
    Cookie cookie = new Cookie("refresh_token", null);
    cookie.setPath("/");
    cookie.setHttpOnly(true);
    cookie.setMaxAge(0);
    response.addCookie(cookie);
}
  • 비즈니스 로직 수행 후 마지막으로 Refresh 토큰을 쿠키에서 삭제한다.

Client

  • Postman 실행으로 로직 성공 시 200 OK 가 나오게 된다.
profile
기어 올라가는 백엔드 개발

0개의 댓글