본문 바로가기
Project/깃터디 (gitudy)

[깃터디/Auth] 로그아웃 요청 구현

by J-rain 2024. 5. 10.

 

 

💡 .java를 클릭시 관련 커밋으로 이동💡

 

 

AuthController.java

private final static int REFRESH_TOKEN_INDEX = 2;

@ApiResponse(responseCode = "200", description = "로그아웃 성공")
@PostMapping("/logout")
    public JsonResult<?> logout(@RequestHeader(name = "Authorization") String token) {
        List<String> tokens = Arrays.asList(token.split(" "));

        if (tokens.size() == 3) {
            authService.logout(tokens.get(REFRESH_TOKEN_INDEX));

            return JsonResult.successOf("로그아웃 되었습니다.");
        } else {
            log.warn(">>>> Invalid Header Access : {}", ExceptionMessage.JWT_INVALID_HEADER.getText());
            return JsonResult.failOf(ExceptionMessage.JWT_INVALID_HEADER.getText());
        }

    }

 

  • HTTP 요청 헤더 중 "Authorization" 헤더를 String 타입의 token 파라미터로 받음
  • Authorization 헤더의 값을 공백으로 분리하여 배열로 만든다
  • token.size()==3 인 이유는 → BEARER + "  " + accessToken + "  " + refreshToken

 

AuthService.java

    @Transactional
    public void logout(String refreshToken) {
        refreshTokenService.logout(refreshToken);
    }

logout Service 구현

 

 

RefreshTokenService.java

 // Logout 시 Redis에 저장된 RefreshToken 삭제
    public void logout(String refreshToken) {

        String sub = jwtService.extractAllClaims(refreshToken).getSubject();

        RefreshToken rtk = refreshTokenRepository.findById(refreshToken).orElseThrow(() -> {
            log.warn(">>>> Token Not Exist : {}", ExceptionMessage.REFRESHTOKEN_NOT_EXIST.getText());
            throw new JwtException(ExceptionMessage.REFRESHTOKEN_NOT_EXIST);
        });

        // refreshToken 유효성 검사
        if (!jwtService.isTokenValid(refreshToken, rtk.getRefreshToken())) {
            log.warn(">>>> Token Validation Fail : {}", ExceptionMessage.REFRESHTOKEN_INVALID.getText());
            throw new JwtException(ExceptionMessage.REFRESHTOKEN_INVALID);
        }

        refreshTokenRepository.delete(rtk);
        log.info(">>>> {}'s RefreshToken id deleted.", sub);
    }

로그아웃 하면 Redis에 저장된 RefreshToken을 삭제한다.

 


테스트

 

TestConfig.java

@AutoConfigureMockMvc
public class TestConfig {

    public static final String AUTHORIZATION = "Authorization";
    public static final String BEARER = "Bearer";

 public static String createAuthorizationHeader(String accessToken, String refreshToken) {
        return BEARER + " " + accessToken + " " + refreshToken;
    }

TestConfig 추가

 

AuthControllerTest.java

    @Test
    @DisplayName("로그아웃 실패 테스트 - 잘못된 토큰으로 요청시 예외 발생")
    void logoutTestWhenInvalidToken() throws Exception {
        String accessToken = "strangeToken";
        String refreshToken = "strangeToken";

        // when
        mockMvc.perform(
                        get("/auth/logout")
                                .header(AUTHORIZATION, createAuthorizationHeader(accessToken, refreshToken)))


                // then
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.res_code").value(400))
                .andExpect(jsonPath("$.res_msg").value(ExceptionMessage.JWT_MALFORMED.getText()));
    }

    @Test
    @DisplayName("로그아웃 성공 테스트")
    void logoutSuccessTest() throws Exception {
        // given
        User user = User.builder()
                .name(expectedName)
                .role(UserRole.USER)
                .platformType(UserPlatformType.GOOGLE)
                .platformId(expectedPlatformId)
                .profileImageUrl(expectedProfileImageUrl)
                .build();
        User savedUser = userRepository.save(user);

        HashMap<String, String> map = new HashMap<>();
        map.put("role", savedUser.getRole().name());
        map.put("name", savedUser.getName());
        map.put("profileImageUrl", savedUser.getProfileImageUrl());

        String accessToken = jwtService.generateAccessToken(map, user);
        String refreshToken = jwtService.generateRefreshToken(map, user);


        // when
        mockMvc.perform(get("/auth/logout")
                        .contentType(MediaType.APPLICATION_JSON)
                        .header(AUTHORIZATION, createAuthorizationHeader(accessToken, refreshToken)))
                // then
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.res_code").value(200))
                .andExpect(jsonPath("$.res_obj").value("로그아웃 되었습니다."));
    }


    @Test
    @DisplayName("로그아웃 실패 테스트 - 잘못된 Header로 요청시 에러 발생")
    void logoutWhenInvalidHeader() throws Exception {
        mockMvc.perform(get("/auth/logout")
                        .contentType(MediaType.APPLICATION_JSON)
                        .header(AUTHORIZATION, "INVALID HEADER"))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.res_code").value(400))
                .andExpect(jsonPath("$.res_msg").value(ExceptionMessage.JWT_INVALID_HEADER.getText()));
    }

로그아웃 Controller 테스트 동작 검증

잘못된 토큰으로 요청 → 로그아웃 실패 테스트

로그아웃 성공 테스트

잘못된 Header로 요청 → 로그아웃 실패 테스트

댓글