From f97057577354382eda8c2947691230ea1f9ba13c Mon Sep 17 00:00:00 2001 From: Seokmyung Ham Date: Sun, 4 Aug 2024 20:50:08 +0900 Subject: [PATCH] =?UTF-8?q?[BE]=20=EB=A1=9C=EA=B7=B8=EC=95=84=EC=9B=83=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80=20(#151)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor(jwtManager): jwt 토큰에 만료 시간을 추가 * refactor: 로그인 시 참가자의 이름을 같이 반환하도록 변경 * feat: path 경로 수정 및 로그아웃 api 추가 * chore: 토큰 만료시간 추가 * chore: 서브모듈 업데이트 반영 - JWT 토큰 만료 시간 추가 * refactor(JwtCookieManager): 쿠키 생성의 책임을 컨트롤러에서 분리 * refactor(MeetingController): 약속 최초 생성시 주최자에 대한 JWT 토큰을 쿠키로 전송하도록 변경 * style(JwtManager): 생성자 매개변수 팀 컨벤션 개행 적용 * refactor: 쿠키 관련 상수 명 설정 * refactor(JwtCookieManager): 클래스 이름을 CookieManager 로 변경 * refactor(CookieManager): SAME SITE 상수 명을 변경하여 의미를 개선 * refactor(JwtProperties): JWT 관련 환경 변수들을 POJO로 관리하도록 개선 * chore: 서브모듈 업데이트 반영 * refactor(JwtProperties): expirationPeriod 필드 NotNull 검증 추가 * refactor: ConfigurationPropertiesScan을 사용하여 전역으로 관리하도록 변경 * refactor(CookieManager): 클래스의 책임에 맞도록 상수 및 변수 이동 --- .../main/java/kr/momo/MomoApplication.java | 2 ++ .../kr/momo/controller/CookieManager.java | 32 +++++++++++++++++ .../attendee/AttendeeController.java | 36 ++++++++++--------- .../controller/meeting/MeetingController.java | 12 +++++-- .../kr/momo/exception/code/AuthErrorCode.java | 3 +- .../service/attendee/AttendeeService.java | 11 +++--- .../attendee/dto/AttendeeLoginResponse.java | 10 ++++++ .../java/kr/momo/service/auth/JwtManager.java | 24 ++++++------- .../kr/momo/service/auth/JwtProperties.java | 28 +++++++++++++++ .../momo/service/meeting/MeetingService.java | 16 ++++++--- .../meeting/dto/MeetingCreateResponse.java | 11 ++++++ .../src/main/resources/application-local.yml | 3 +- backend/src/main/resources/security | 2 +- .../attendee/AttendeeControllerTest.java | 20 ++++++----- .../kr/momo/service/auth/JwtManagerTest.java | 36 ++++++++++++------- backend/src/test/resources/application.yml | 3 +- 16 files changed, 183 insertions(+), 66 deletions(-) create mode 100644 backend/src/main/java/kr/momo/controller/CookieManager.java create mode 100644 backend/src/main/java/kr/momo/service/attendee/dto/AttendeeLoginResponse.java create mode 100644 backend/src/main/java/kr/momo/service/auth/JwtProperties.java create mode 100644 backend/src/main/java/kr/momo/service/meeting/dto/MeetingCreateResponse.java diff --git a/backend/src/main/java/kr/momo/MomoApplication.java b/backend/src/main/java/kr/momo/MomoApplication.java index 2754e1442..2e78454d2 100644 --- a/backend/src/main/java/kr/momo/MomoApplication.java +++ b/backend/src/main/java/kr/momo/MomoApplication.java @@ -2,9 +2,11 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.properties.ConfigurationPropertiesScan; import org.springframework.data.jpa.repository.config.EnableJpaAuditing; @EnableJpaAuditing +@ConfigurationPropertiesScan @SpringBootApplication public class MomoApplication { diff --git a/backend/src/main/java/kr/momo/controller/CookieManager.java b/backend/src/main/java/kr/momo/controller/CookieManager.java new file mode 100644 index 000000000..7720fad45 --- /dev/null +++ b/backend/src/main/java/kr/momo/controller/CookieManager.java @@ -0,0 +1,32 @@ +package kr.momo.controller; + +import org.springframework.http.ResponseCookie; +import org.springframework.stereotype.Component; + +@Component +public class CookieManager { + + private static final String ACCESS_TOKEN = "ACCESS_TOKEN"; + private static final String SAME_SITE_OPTION = "None"; + private static final long SESSION_COOKIE_AGE = -1; + private static final long EXPIRED_COOKIE_AGE = 0; + + public String createNewCookie(String value, String path) { + return createCookie(value, path, SESSION_COOKIE_AGE); + } + + public String createExpiredCookie(String path) { + return createCookie("", path, EXPIRED_COOKIE_AGE); + } + + private String createCookie(String value, String path, long maxAge) { + return ResponseCookie.from(ACCESS_TOKEN, value) + .httpOnly(true) + .secure(true) + .path(path) + .sameSite(SAME_SITE_OPTION) + .maxAge(maxAge) + .build() + .toString(); + } +} diff --git a/backend/src/main/java/kr/momo/controller/attendee/AttendeeController.java b/backend/src/main/java/kr/momo/controller/attendee/AttendeeController.java index a34a69391..8280eeb4d 100644 --- a/backend/src/main/java/kr/momo/controller/attendee/AttendeeController.java +++ b/backend/src/main/java/kr/momo/controller/attendee/AttendeeController.java @@ -1,11 +1,13 @@ package kr.momo.controller.attendee; import jakarta.validation.Valid; +import kr.momo.controller.CookieManager; +import kr.momo.controller.MomoApiResponse; import kr.momo.service.attendee.AttendeeService; import kr.momo.service.attendee.dto.AttendeeLoginRequest; +import kr.momo.service.attendee.dto.AttendeeLoginResponse; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpHeaders; -import org.springframework.http.ResponseCookie; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; @@ -16,28 +18,28 @@ @RequiredArgsConstructor public class AttendeeController { - private static final String ACCESS_TOKEN = "ACCESS_TOKEN"; - private static final String SAME_SITE_SETTING = "None"; - private final AttendeeService attendeeService; + private final CookieManager cookieManager; @PostMapping("/api/v1/meetings/{uuid}/login") - public ResponseEntity login(@PathVariable String uuid, @RequestBody @Valid AttendeeLoginRequest request) { - String token = attendeeService.login(uuid, request); - String path = String.format("/api/v1/meetings/%s/", uuid); + public ResponseEntity> login( + @PathVariable String uuid, @RequestBody @Valid AttendeeLoginRequest request + ) { + AttendeeLoginResponse response = attendeeService.login(uuid, request); + String path = String.format("/meeting/%s", uuid); + String cookie = cookieManager.createNewCookie(response.token(), path); return ResponseEntity.ok() - .header(HttpHeaders.SET_COOKIE, createCookie(token, path)) - .build(); + .header(HttpHeaders.SET_COOKIE, cookie) + .body(new MomoApiResponse<>(response.name())); } - private String createCookie(String value, String path) { - return ResponseCookie.from(ACCESS_TOKEN, value) - .httpOnly(true) - .secure(true) - .path(path) - .sameSite(SAME_SITE_SETTING) - .build() - .toString(); + @PostMapping("/api/v1/meetings/{uuid}/logout") + public ResponseEntity logout(@PathVariable String uuid) { + String cookie = cookieManager.createExpiredCookie(uuid); + + return ResponseEntity.ok() + .header(HttpHeaders.SET_COOKIE, cookie) + .build(); } } diff --git a/backend/src/main/java/kr/momo/controller/meeting/MeetingController.java b/backend/src/main/java/kr/momo/controller/meeting/MeetingController.java index 760ff562d..cd6723375 100644 --- a/backend/src/main/java/kr/momo/controller/meeting/MeetingController.java +++ b/backend/src/main/java/kr/momo/controller/meeting/MeetingController.java @@ -2,13 +2,16 @@ import jakarta.validation.Valid; import java.net.URI; +import kr.momo.controller.CookieManager; import kr.momo.controller.MomoApiResponse; import kr.momo.controller.auth.AuthAttendee; import kr.momo.service.meeting.MeetingService; import kr.momo.service.meeting.dto.MeetingCreateRequest; +import kr.momo.service.meeting.dto.MeetingCreateResponse; import kr.momo.service.meeting.dto.MeetingResponse; import kr.momo.service.meeting.dto.MeetingSharingResponse; import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpHeaders; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PatchMapping; @@ -22,13 +25,18 @@ public class MeetingController { private final MeetingService meetingService; + private final CookieManager cookieManager; @PostMapping("/api/v1/meetings") - public ResponseEntity> create( + public ResponseEntity> create( @RequestBody @Valid MeetingCreateRequest request ) { - MeetingSharingResponse response = meetingService.create(request); + MeetingCreateResponse response = meetingService.create(request); + String path = String.format("/meeting/%s", response.uuid()); + String cookie = cookieManager.createNewCookie(response.token(), path); + return ResponseEntity.created(URI.create("/meeting/" + response.uuid())) + .header(HttpHeaders.SET_COOKIE, cookie) .body(new MomoApiResponse<>(response)); } diff --git a/backend/src/main/java/kr/momo/exception/code/AuthErrorCode.java b/backend/src/main/java/kr/momo/exception/code/AuthErrorCode.java index d78bd3392..54d6777fe 100644 --- a/backend/src/main/java/kr/momo/exception/code/AuthErrorCode.java +++ b/backend/src/main/java/kr/momo/exception/code/AuthErrorCode.java @@ -4,8 +4,7 @@ public enum AuthErrorCode implements ErrorCodeType { - INVALID_TOKEN(HttpStatus.BAD_REQUEST, "유효하지 않은 토큰입니다."), - NOT_FOUND_TOKEN(HttpStatus.UNAUTHORIZED, "토큰이 존재하지 않습니다."); + UNAUTHORIZED_TOKEN(HttpStatus.UNAUTHORIZED, "유효하지 않은 토큰입니다."); private final HttpStatus httpStatus; private final String message; diff --git a/backend/src/main/java/kr/momo/service/attendee/AttendeeService.java b/backend/src/main/java/kr/momo/service/attendee/AttendeeService.java index 731052eea..f3f675b1e 100644 --- a/backend/src/main/java/kr/momo/service/attendee/AttendeeService.java +++ b/backend/src/main/java/kr/momo/service/attendee/AttendeeService.java @@ -10,6 +10,7 @@ import kr.momo.exception.MomoException; import kr.momo.exception.code.MeetingErrorCode; import kr.momo.service.attendee.dto.AttendeeLoginRequest; +import kr.momo.service.attendee.dto.AttendeeLoginResponse; import kr.momo.service.auth.JwtManager; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -24,7 +25,7 @@ public class AttendeeService { private final JwtManager jwtManager; @Transactional - public String login(String uuid, AttendeeLoginRequest request) { + public AttendeeLoginResponse login(String uuid, AttendeeLoginRequest request) { Meeting meeting = meetingRepository.findByUuid(uuid) .orElseThrow(() -> new MomoException(MeetingErrorCode.INVALID_UUID)); @@ -36,14 +37,14 @@ public String login(String uuid, AttendeeLoginRequest request) { .orElseGet(() -> signup(meeting, name, password)); } - private String verifyPassword(Attendee attendee, AttendeePassword password) { + private AttendeeLoginResponse verifyPassword(Attendee attendee, AttendeePassword password) { attendee.verifyPassword(password); - return jwtManager.generate(attendee.getId()); + return AttendeeLoginResponse.from(jwtManager.generate(attendee.getId()), attendee); } - private String signup(Meeting meeting, AttendeeName name, AttendeePassword password) { + private AttendeeLoginResponse signup(Meeting meeting, AttendeeName name, AttendeePassword password) { Attendee attendee = new Attendee(meeting, name, password, Role.GUEST); attendeeRepository.save(attendee); - return jwtManager.generate(attendee.getId()); + return AttendeeLoginResponse.from(jwtManager.generate(attendee.getId()), attendee); } } diff --git a/backend/src/main/java/kr/momo/service/attendee/dto/AttendeeLoginResponse.java b/backend/src/main/java/kr/momo/service/attendee/dto/AttendeeLoginResponse.java new file mode 100644 index 000000000..6a7448cdc --- /dev/null +++ b/backend/src/main/java/kr/momo/service/attendee/dto/AttendeeLoginResponse.java @@ -0,0 +1,10 @@ +package kr.momo.service.attendee.dto; + +import kr.momo.domain.attendee.Attendee; + +public record AttendeeLoginResponse(String token, String name) { + + public static AttendeeLoginResponse from(String token, Attendee attendee) { + return new AttendeeLoginResponse(token, attendee.name()); + } +} diff --git a/backend/src/main/java/kr/momo/service/auth/JwtManager.java b/backend/src/main/java/kr/momo/service/auth/JwtManager.java index c7ce8afc2..6a802d06b 100644 --- a/backend/src/main/java/kr/momo/service/auth/JwtManager.java +++ b/backend/src/main/java/kr/momo/service/auth/JwtManager.java @@ -8,28 +8,28 @@ import com.auth0.jwt.exceptions.JWTVerificationException; import com.auth0.jwt.exceptions.SignatureVerificationException; import com.auth0.jwt.interfaces.DecodedJWT; +import java.util.Date; import kr.momo.exception.MomoException; import kr.momo.exception.code.AuthErrorCode; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; @Slf4j @Component +@RequiredArgsConstructor public class JwtManager { private static final String CLAIM_ID = "id"; - private final String secretKey; - - public JwtManager(@Value("${security.jwt.secret_key}") String secretKey) { - this.secretKey = secretKey; - } + private final JwtProperties jwtProperties; public String generate(long id) { + Date expirationDate = new Date(System.currentTimeMillis() + jwtProperties.getExpirationPeriodInMillis()); return JWT.create() .withClaim(CLAIM_ID, id) - .sign(Algorithm.HMAC256(secretKey)); + .withExpiresAt(expirationDate) + .sign(Algorithm.HMAC256(jwtProperties.getSecretKey())); } public long extract(String token) { @@ -39,21 +39,21 @@ public long extract(String token) { private DecodedJWT verifyToken(String token) { try { - Algorithm algorithm = Algorithm.HMAC256(secretKey); + Algorithm algorithm = Algorithm.HMAC256(jwtProperties.getSecretKey()); JWTVerifier verifier = JWT.require(algorithm).build(); return verifier.verify(token); } catch (InvalidClaimException e) { log.warn("토큰 내 클레임이 유효하지 않습니다. {} ", e.getMessage()); - throw new MomoException(AuthErrorCode.INVALID_TOKEN); + throw new MomoException(AuthErrorCode.UNAUTHORIZED_TOKEN); } catch (JWTDecodeException e) { log.warn("JWT 토큰 구성이 올바르지 않습니다. {} ", e.getMessage()); - throw new MomoException(AuthErrorCode.INVALID_TOKEN); + throw new MomoException(AuthErrorCode.UNAUTHORIZED_TOKEN); } catch (SignatureVerificationException e) { log.warn("토큰의 서명이 알고리즘을 사용하여 검증했을 때 유효하지 않습니다. {} ", e.getMessage()); - throw new MomoException(AuthErrorCode.INVALID_TOKEN); + throw new MomoException(AuthErrorCode.UNAUTHORIZED_TOKEN); } catch (JWTVerificationException e) { log.warn("토큰 검증 과정에서 예기치 못한 에러가 발생하였습니다. {} ", e.getMessage()); - throw new MomoException(AuthErrorCode.INVALID_TOKEN); + throw new MomoException(AuthErrorCode.UNAUTHORIZED_TOKEN); } } } diff --git a/backend/src/main/java/kr/momo/service/auth/JwtProperties.java b/backend/src/main/java/kr/momo/service/auth/JwtProperties.java new file mode 100644 index 000000000..9c6a17eb3 --- /dev/null +++ b/backend/src/main/java/kr/momo/service/auth/JwtProperties.java @@ -0,0 +1,28 @@ +package kr.momo.service.auth; + +import jakarta.validation.constraints.NotNull; +import java.time.Duration; +import java.time.temporal.ChronoUnit; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.convert.DurationUnit; +import org.springframework.validation.annotation.Validated; + +@Getter +@Validated +@RequiredArgsConstructor +@ConfigurationProperties("security.jwt") +public class JwtProperties { + + @NotNull + private final String secretKey; + + @NotNull + @DurationUnit(ChronoUnit.HOURS) + private final Duration expirationPeriod; + + public long getExpirationPeriodInMillis() { + return getExpirationPeriod().toMillis(); + } +} diff --git a/backend/src/main/java/kr/momo/service/meeting/MeetingService.java b/backend/src/main/java/kr/momo/service/meeting/MeetingService.java index 1a7f5413d..6c6d5ac53 100644 --- a/backend/src/main/java/kr/momo/service/meeting/MeetingService.java +++ b/backend/src/main/java/kr/momo/service/meeting/MeetingService.java @@ -14,7 +14,9 @@ import kr.momo.exception.MomoException; import kr.momo.exception.code.AttendeeErrorCode; import kr.momo.exception.code.MeetingErrorCode; +import kr.momo.service.auth.JwtManager; import kr.momo.service.meeting.dto.MeetingCreateRequest; +import kr.momo.service.meeting.dto.MeetingCreateResponse; import kr.momo.service.meeting.dto.MeetingResponse; import kr.momo.service.meeting.dto.MeetingSharingResponse; import lombok.RequiredArgsConstructor; @@ -28,13 +30,17 @@ public class MeetingService { private final MeetingRepository meetingRepository; private final AvailableDateRepository availableDateRepository; private final AttendeeRepository attendeeRepository; + private final JwtManager jwtManager; @Transactional - public MeetingSharingResponse create(MeetingCreateRequest request) { + public MeetingCreateResponse create(MeetingCreateRequest request) { Meeting meeting = saveMeeting(request.meetingName(), request.meetingStartTime(), request.meetingEndTime()); saveAvailableDates(request.meetingAvailableDates(), meeting); - saveHostAttendee(meeting, request.hostName(), request.hostPassword()); - return MeetingSharingResponse.from(meeting); + + Attendee attendee = saveHostAttendee(meeting, request.hostName(), request.hostPassword()); + String token = jwtManager.generate(attendee.getId()); + + return MeetingCreateResponse.from(meeting, attendee, token); } private Meeting saveMeeting(String meetingName, LocalTime startTime, LocalTime endTime) { @@ -47,9 +53,9 @@ private void saveAvailableDates(List dates, Meeting meeting) { availableDateRepository.saveAll(availableDates.getAvailableDates()); } - private void saveHostAttendee(Meeting meeting, String hostName, String hostPassword) { + private Attendee saveHostAttendee(Meeting meeting, String hostName, String hostPassword) { Attendee attendee = new Attendee(meeting, hostName, hostPassword, Role.HOST); - attendeeRepository.save(attendee); + return attendeeRepository.save(attendee); } @Transactional(readOnly = true) diff --git a/backend/src/main/java/kr/momo/service/meeting/dto/MeetingCreateResponse.java b/backend/src/main/java/kr/momo/service/meeting/dto/MeetingCreateResponse.java new file mode 100644 index 000000000..0cd27582a --- /dev/null +++ b/backend/src/main/java/kr/momo/service/meeting/dto/MeetingCreateResponse.java @@ -0,0 +1,11 @@ +package kr.momo.service.meeting.dto; + +import kr.momo.domain.attendee.Attendee; +import kr.momo.domain.meeting.Meeting; + +public record MeetingCreateResponse(String uuid, String name, String token) { + + public static MeetingCreateResponse from(Meeting meeting, Attendee attendee, String token) { + return new MeetingCreateResponse(meeting.getUuid(), attendee.name(), token); + } +} diff --git a/backend/src/main/resources/application-local.yml b/backend/src/main/resources/application-local.yml index 9a3b1f5e3..2d1335ccd 100644 --- a/backend/src/main/resources/application-local.yml +++ b/backend/src/main/resources/application-local.yml @@ -21,4 +21,5 @@ spring: security: jwt: - secret_key: ${random.value}" + secret-key: ${random.value}" + expiration-period: 1h diff --git a/backend/src/main/resources/security b/backend/src/main/resources/security index 67202e4d5..61d224f8a 160000 --- a/backend/src/main/resources/security +++ b/backend/src/main/resources/security @@ -1 +1 @@ -Subproject commit 67202e4d5263396fc317a0e77eba5ffdf79c7654 +Subproject commit 61d224f8a2279a4897c2c3a3a3a2e5786ce6fd50 diff --git a/backend/src/test/java/kr/momo/controller/attendee/AttendeeControllerTest.java b/backend/src/test/java/kr/momo/controller/attendee/AttendeeControllerTest.java index 8613c405f..764d90b79 100644 --- a/backend/src/test/java/kr/momo/controller/attendee/AttendeeControllerTest.java +++ b/backend/src/test/java/kr/momo/controller/attendee/AttendeeControllerTest.java @@ -1,9 +1,11 @@ package kr.momo.controller.attendee; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; import io.restassured.RestAssured; import io.restassured.http.ContentType; +import io.restassured.response.Response; import kr.momo.domain.attendee.Attendee; import kr.momo.domain.attendee.AttendeeRepository; import kr.momo.domain.meeting.Meeting; @@ -20,7 +22,6 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; import org.springframework.boot.test.web.server.LocalServerPort; -import org.springframework.http.HttpStatus; @IsolateDatabase @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) @@ -43,7 +44,7 @@ void setUp() { RestAssured.port = port; } - @DisplayName("로그인에 성공하면 200 상태 코드와 토큰을 응답한다.") + @DisplayName("로그인에 성공하면 200 상태 코드를 응답한다.") @Test void login() { Meeting meeting = meetingRepository.save(MeetingFixture.COFFEE.create()); @@ -51,14 +52,17 @@ void login() { AttendeeLoginRequest request = new AttendeeLoginRequest(attendee.name(), attendee.password()); - String token = RestAssured.given().log().all() + Response response = RestAssured.given().log().all() .contentType(ContentType.JSON) .body(request) - .when().post("/api/v1/meetings/{uuid}/login", meeting.getUuid()) - .then().log().all() - .statusCode(HttpStatus.OK.value()) - .extract().cookie("ACCESS_TOKEN"); + .when().post("/api/v1/meetings/{uuid}/login", meeting.getUuid()); - assertThat(jwtManager.extract(token)).isEqualTo(attendee.getId()); + String token = response.cookie("ACCESS_TOKEN"); + String hostName = response.jsonPath().getString("data"); + + assertAll( + () -> assertThat(jwtManager.extract(token)).isEqualTo(attendee.getId()), + () -> assertThat(hostName).isEqualTo(request.name()) + ); } } diff --git a/backend/src/test/java/kr/momo/service/auth/JwtManagerTest.java b/backend/src/test/java/kr/momo/service/auth/JwtManagerTest.java index 0ed9ce158..1f3dfeeb4 100644 --- a/backend/src/test/java/kr/momo/service/auth/JwtManagerTest.java +++ b/backend/src/test/java/kr/momo/service/auth/JwtManagerTest.java @@ -3,13 +3,10 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import kr.momo.domain.attendee.Attendee; -import kr.momo.domain.attendee.AttendeeName; -import kr.momo.domain.attendee.AttendeePassword; -import kr.momo.domain.attendee.Role; +import java.time.Duration; import kr.momo.exception.MomoException; import kr.momo.exception.code.AuthErrorCode; -import kr.momo.fixture.MeetingFixture; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; @@ -18,19 +15,22 @@ class JwtManagerTest { - private final JwtManager jwtManager = new JwtManager("secretKey"); + private JwtManager jwtManager; + + @BeforeEach + void setUp() { + JwtProperties jwtProperties = new JwtProperties("secretKey", Duration.ofMinutes(10)); + jwtManager = new JwtManager(jwtProperties); + } @DisplayName("참가자의 정보를 이용하여 jwt 토큰을 발행한다.") @Test void generate() { - AttendeeName name = new AttendeeName("attendee"); - AttendeePassword password = new AttendeePassword("password"); - Attendee attendee = new Attendee(1L, MeetingFixture.DINNER.create(), name, password, Role.GUEST); + String token = jwtManager.generate(1L); - String token = jwtManager.generate(attendee.getId()); long attendeeId = jwtManager.extract(token); - assertThat(attendeeId).isEqualTo(attendee.getId()); + assertThat(attendeeId).isEqualTo(1L); } @DisplayName("토큰이 null이거나 올바르지 않을 경우 예외를 발생시킨다.") @@ -40,6 +40,18 @@ void generate() { void throwExceptionForInvalidToken(String token) { assertThatThrownBy(() -> jwtManager.extract(token)) .isInstanceOf(MomoException.class) - .hasMessage(AuthErrorCode.INVALID_TOKEN.message()); + .hasMessage(AuthErrorCode.UNAUTHORIZED_TOKEN.message()); + } + + @DisplayName("만료된 토큰일 경우 예외를 발생시킨다.") + @Test + void throwExceptionWhenTokenIsExpired() { + JwtProperties jwtProperties = new JwtProperties("secretKey", Duration.ofMillis(1)); + JwtManager jwtManager = new JwtManager(jwtProperties); + String token = jwtManager.generate(1L); + + assertThatThrownBy(() -> jwtManager.extract(token)) + .isInstanceOf(MomoException.class) + .hasMessage(AuthErrorCode.UNAUTHORIZED_TOKEN.message()); } } diff --git a/backend/src/test/resources/application.yml b/backend/src/test/resources/application.yml index dc689e293..8caa9789d 100644 --- a/backend/src/test/resources/application.yml +++ b/backend/src/test/resources/application.yml @@ -21,4 +21,5 @@ logging: security: jwt: - secret_key: ${random.value}" + secret-key: ${random.value}" + expiration-period: 1h