From 7fc59e8d95e8f502d88d598cbd0a035d788cdfc6 Mon Sep 17 00:00:00 2001 From: Chuseok22 Date: Mon, 12 Jan 2026 15:11:51 +0900 Subject: [PATCH 01/13] =?UTF-8?q?=EB=A1=9C=EA=B7=B8=EC=9D=B8=5F=EB=B0=8F?= =?UTF-8?q?=5F=EA=B8=B0=ED=83=80=5F=EC=9D=B8=EC=A6=9D=5F=EA=B4=80=EB=A0=A8?= =?UTF-8?q?=5F=EB=A1=9C=EC=A7=81=5F=EA=B0=9C=EB=B0=9C=20:=20feat=20:=20mem?= =?UTF-8?q?ber=20=EC=97=94=ED=8B=B0=ED=8B=B0=20=ED=95=99=EB=B2=88:=20stude?= =?UTF-8?q?nt=5Fnumber=20=EB=B3=80=EA=B2=BD=20https://github.com/CampusTab?= =?UTF-8?q?le/campus-table-server/issues/5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ctmember/infrastructure/entity/Member.kt | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/CT-member/src/main/kotlin/com/chuseok22/ctmember/infrastructure/entity/Member.kt b/CT-member/src/main/kotlin/com/chuseok22/ctmember/infrastructure/entity/Member.kt index 36a93e5..1662b94 100644 --- a/CT-member/src/main/kotlin/com/chuseok22/ctmember/infrastructure/entity/Member.kt +++ b/CT-member/src/main/kotlin/com/chuseok22/ctmember/infrastructure/entity/Member.kt @@ -13,8 +13,8 @@ open class Member protected constructor() : BaseEntity() { var id: UUID? = null protected set - @field:Column(name = "student_name", nullable = false) - lateinit var studentName: String + @field:Column(name = "student_number", nullable = false) + lateinit var studentNumber: String protected set @field:Column(name = "name", nullable = false) @@ -26,18 +26,18 @@ open class Member protected constructor() : BaseEntity() { var role: Role = Role.ROLE_USER protected set - private constructor(studentName: String, name: String, role: Role) : this() { - this.studentName = normalizeStudentName(studentName) + private constructor(studentNumber: String, name: String, role: Role) : this() { + this.studentNumber = normalizeStudentNumber(studentNumber) this.name = normalizeName(name) this.role = role } companion object { - fun create(studentName: String, name: String): Member { - return Member(studentName, name, Role.ROLE_USER) + fun create(studentNumber: String, name: String): Member { + return Member(studentNumber, name, Role.ROLE_USER) } - private fun normalizeStudentName(raw: String): String { + private fun normalizeStudentNumber(raw: String): String { val normalized: String = raw.trim() require(normalized.isNotBlank()) { "학번은 필수로 입력되어야 합니다 " } return normalized From bff02e598e3ea9eacf67345d101ad1797a355364 Mon Sep 17 00:00:00 2001 From: Chuseok22 Date: Mon, 12 Jan 2026 15:12:05 +0900 Subject: [PATCH 02/13] =?UTF-8?q?=EB=A1=9C=EA=B7=B8=EC=9D=B8=5F=EB=B0=8F?= =?UTF-8?q?=5F=EA=B8=B0=ED=83=80=5F=EC=9D=B8=EC=A6=9D=5F=EA=B4=80=EB=A0=A8?= =?UTF-8?q?=5F=EB=A1=9C=EC=A7=81=5F=EA=B0=9C=EB=B0=9C=20:=20feat=20:=20mem?= =?UTF-8?q?ber=20=EC=97=94=ED=8B=B0=ED=8B=B0=20=ED=95=99=EB=B2=88:=20stude?= =?UTF-8?q?nt=5Fnumber=20=EB=B3=80=EA=B2=BD=20https://github.com/CampusTab?= =?UTF-8?q?le/campus-table-server/issues/5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../chuseok22/ctauth/infrastructure/user/CustomUserDetails.kt | 2 +- .../ctauth/infrastructure/user/CustomUserDetailsService.kt | 2 +- .../ctmember/infrastructure/repository/MemberRepository.kt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CT-auth/src/main/kotlin/com/chuseok22/ctauth/infrastructure/user/CustomUserDetails.kt b/CT-auth/src/main/kotlin/com/chuseok22/ctauth/infrastructure/user/CustomUserDetails.kt index 4e2fc82..d7f131e 100644 --- a/CT-auth/src/main/kotlin/com/chuseok22/ctauth/infrastructure/user/CustomUserDetails.kt +++ b/CT-auth/src/main/kotlin/com/chuseok22/ctauth/infrastructure/user/CustomUserDetails.kt @@ -24,7 +24,7 @@ class CustomUserDetails( } override fun getUsername(): String { - return member.studentName + return member.studentNumber } override fun getRoles(): List { diff --git a/CT-auth/src/main/kotlin/com/chuseok22/ctauth/infrastructure/user/CustomUserDetailsService.kt b/CT-auth/src/main/kotlin/com/chuseok22/ctauth/infrastructure/user/CustomUserDetailsService.kt index 5114c41..bbfb370 100644 --- a/CT-auth/src/main/kotlin/com/chuseok22/ctauth/infrastructure/user/CustomUserDetailsService.kt +++ b/CT-auth/src/main/kotlin/com/chuseok22/ctauth/infrastructure/user/CustomUserDetailsService.kt @@ -15,7 +15,7 @@ class CustomUserDetailsService( @Transactional(readOnly = true) override fun loadUserByUsername(username: String): UserDetails { - val member = memberRepository.findByStudentNameAndDeletedFalse(username) + val member = memberRepository.findByStudentNumberAndDeletedFalse(username) ?: throw CustomException(ErrorCode.MEMBER_NOT_FOUND) return CustomUserDetails(member) } diff --git a/CT-member/src/main/kotlin/com/chuseok22/ctmember/infrastructure/repository/MemberRepository.kt b/CT-member/src/main/kotlin/com/chuseok22/ctmember/infrastructure/repository/MemberRepository.kt index 4a3f569..f58d9ed 100644 --- a/CT-member/src/main/kotlin/com/chuseok22/ctmember/infrastructure/repository/MemberRepository.kt +++ b/CT-member/src/main/kotlin/com/chuseok22/ctmember/infrastructure/repository/MemberRepository.kt @@ -7,5 +7,5 @@ import java.util.* interface MemberRepository : JpaRepository { fun findByIdAndDeletedFalse(memberId: UUID): Member? - fun findByStudentNameAndDeletedFalse(studentName: String): Member? + fun findByStudentNumberAndDeletedFalse(studentNumber: String): Member? } \ No newline at end of file From b957750ac912cd3f21f05cee53b9a608df90addf Mon Sep 17 00:00:00 2001 From: Chuseok22 Date: Mon, 12 Jan 2026 15:12:10 +0900 Subject: [PATCH 03/13] =?UTF-8?q?=EB=A1=9C=EA=B7=B8=EC=9D=B8=5F=EB=B0=8F?= =?UTF-8?q?=5F=EA=B8=B0=ED=83=80=5F=EC=9D=B8=EC=A6=9D=5F=EA=B4=80=EB=A0=A8?= =?UTF-8?q?=5F=EB=A1=9C=EC=A7=81=5F=EA=B0=9C=EB=B0=9C=20:=20feat=20:=20mem?= =?UTF-8?q?ber=20=EC=97=94=ED=8B=B0=ED=8B=B0=20=ED=95=99=EB=B2=88:=20stude?= =?UTF-8?q?nt=5Fnumber=20=EB=B3=80=EA=B2=BD=20https://github.com/CampusTab?= =?UTF-8?q?le/campus-table-server/issues/5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../V20260112_132017__create_member_table.sql | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/CT-web/src/main/resources/db/migration/V20260112_132017__create_member_table.sql b/CT-web/src/main/resources/db/migration/V20260112_132017__create_member_table.sql index dde8e22..c7a72a2 100644 --- a/CT-web/src/main/resources/db/migration/V20260112_132017__create_member_table.sql +++ b/CT-web/src/main/resources/db/migration/V20260112_132017__create_member_table.sql @@ -2,7 +2,7 @@ CREATE TABLE member ( id UUID NOT NULL, - student_name varchar(255) NOT NULL, + student_number varchar(255) NOT NULL, name varchar(255) NOT NULL, role varchar(50) NOT NULL, @@ -16,11 +16,11 @@ CREATE TABLE member CONSTRAINT chk_member_role CHECK ( role IN ('ROLE_USER', 'ROLE_ADMIN')) ); --- 활성 회원에 대해서 student_name 유니크 적용 -CREATE UNIQUE INDEX uq_member_student_name_active - ON member (student_name) +-- 활성 회원에 대해서 student_number 유니크 적용 +CREATE UNIQUE INDEX uq_member_student_number_active + ON member (student_number) WHERE deleted = FALSE; -- 조회 인덱스 (학번 + 삭제여부) -CREATE INDEX idx_member_student_name_deleted - ON member (student_name, deleted); \ No newline at end of file +CREATE INDEX idx_member_student_number_deleted + ON member (student_number, deleted); \ No newline at end of file From 2b774f87784352a5789d5d725dc619035524f811 Mon Sep 17 00:00:00 2001 From: Chuseok22 Date: Mon, 12 Jan 2026 15:12:24 +0900 Subject: [PATCH 04/13] =?UTF-8?q?=EB=A1=9C=EA=B7=B8=EC=9D=B8=5F=EB=B0=8F?= =?UTF-8?q?=5F=EA=B8=B0=ED=83=80=5F=EC=9D=B8=EC=A6=9D=5F=EA=B4=80=EB=A0=A8?= =?UTF-8?q?=5F=EB=A1=9C=EC=A7=81=5F=EA=B0=9C=EB=B0=9C=20:=20feat=20:=20?= =?UTF-8?q?=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EB=A1=9C=EC=A7=81=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20https://github.com/CampusTable/campus-table-server/?= =?UTF-8?q?issues/5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/dto/request/LoginRequest.kt | 6 ++ .../application/dto/response/LoginResponse.kt | 8 +++ .../ctauth/application/service/AuthService.kt | 65 +++++++++++++++++++ .../infrastructure/constant/SecurityUrls.kt | 3 + .../application/exception/ErrorCode.kt | 3 + 5 files changed, 85 insertions(+) create mode 100644 CT-auth/src/main/kotlin/com/chuseok22/ctauth/application/dto/request/LoginRequest.kt create mode 100644 CT-auth/src/main/kotlin/com/chuseok22/ctauth/application/dto/response/LoginResponse.kt create mode 100644 CT-auth/src/main/kotlin/com/chuseok22/ctauth/application/service/AuthService.kt diff --git a/CT-auth/src/main/kotlin/com/chuseok22/ctauth/application/dto/request/LoginRequest.kt b/CT-auth/src/main/kotlin/com/chuseok22/ctauth/application/dto/request/LoginRequest.kt new file mode 100644 index 0000000..e76b5fd --- /dev/null +++ b/CT-auth/src/main/kotlin/com/chuseok22/ctauth/application/dto/request/LoginRequest.kt @@ -0,0 +1,6 @@ +package com.chuseok22.ctauth.application.dto.request + +data class LoginRequest( + val sejongPortalId: String, + val sejongPortalPw: String +) diff --git a/CT-auth/src/main/kotlin/com/chuseok22/ctauth/application/dto/response/LoginResponse.kt b/CT-auth/src/main/kotlin/com/chuseok22/ctauth/application/dto/response/LoginResponse.kt new file mode 100644 index 0000000..141ccf8 --- /dev/null +++ b/CT-auth/src/main/kotlin/com/chuseok22/ctauth/application/dto/response/LoginResponse.kt @@ -0,0 +1,8 @@ +package com.chuseok22.ctauth.application.dto.response + +data class LoginResponse( + val studentNumber: String, + val name: String, + val accessToken: String, + val refreshToken: String +) diff --git a/CT-auth/src/main/kotlin/com/chuseok22/ctauth/application/service/AuthService.kt b/CT-auth/src/main/kotlin/com/chuseok22/ctauth/application/service/AuthService.kt new file mode 100644 index 0000000..2d53526 --- /dev/null +++ b/CT-auth/src/main/kotlin/com/chuseok22/ctauth/application/service/AuthService.kt @@ -0,0 +1,65 @@ +package com.chuseok22.ctauth.application.service + +import com.chuseok22.ctauth.application.dto.request.LoginRequest +import com.chuseok22.ctauth.application.dto.response.LoginResponse +import com.chuseok22.ctauth.core.token.TokenProvider +import com.chuseok22.ctcommon.application.exception.CustomException +import com.chuseok22.ctcommon.application.exception.ErrorCode +import com.chuseok22.ctmember.infrastructure.entity.Member +import com.chuseok22.ctmember.infrastructure.repository.MemberRepository +import com.chuseok22.sejongportallogin.core.SejongMemberInfo +import com.chuseok22.sejongportallogin.infrastructure.SejongPortalLoginService +import io.github.oshai.kotlinlogging.KotlinLogging +import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional + +private val log = KotlinLogging.logger { } + +@Service +class AuthService( + private val sejongPortalLoginService: SejongPortalLoginService, + private val memberRepository: MemberRepository, + private val tokenProvider: TokenProvider +) { + + @Transactional + fun login(request: LoginRequest): LoginResponse { + val sejongMemberInfo = sejongPortalLogin(request.sejongPortalId, request.sejongPortalPw) + val studentNumber = sejongMemberInfo.studentId + val name = sejongMemberInfo.name + + + val member = memberRepository.findByStudentNumberAndDeletedFalse(studentNumber) + ?: run { + log.info { "신규 회원 로그인: 학번=$studentNumber, 이름=$name" } + val newMember = Member.create(studentNumber, name) + memberRepository.save(newMember) + } + + val memberId = member.id?.toString() + ?: throw IllegalStateException("memberId가 초기화 되지 않았습니다") + + // 토큰 발급 + val accessToken = tokenProvider.createAccessToken(memberId) + val refreshToken = tokenProvider.createRefreshToken(memberId) + + log.info { "로그인 성공: 학번=$studentNumber, 이름=$name" } + + return LoginResponse( + studentNumber = sejongMemberInfo.studentId, + name = sejongMemberInfo.name, + accessToken = accessToken, + refreshToken = refreshToken + ) + } + + private fun sejongPortalLogin(sejongPortalId: String, sejongPortalPw: String): SejongMemberInfo { + try { + log.debug { "세종대학교 포털 로그인을 시도합니다: $sejongPortalId" } + return sejongPortalLoginService.getMemberAuthInfos(sejongPortalId, sejongPortalPw) + } catch (e: Exception) { + log.error(e) { "세종대학교 포털 로그인 중 오류 발생: ${e.message}" } + throw CustomException(ErrorCode.SEJONG_PORTAL_LOGIN_FAILED) + } + } +} \ No newline at end of file diff --git a/CT-auth/src/main/kotlin/com/chuseok22/ctauth/infrastructure/constant/SecurityUrls.kt b/CT-auth/src/main/kotlin/com/chuseok22/ctauth/infrastructure/constant/SecurityUrls.kt index e2de9ce..ef2dd53 100644 --- a/CT-auth/src/main/kotlin/com/chuseok22/ctauth/infrastructure/constant/SecurityUrls.kt +++ b/CT-auth/src/main/kotlin/com/chuseok22/ctauth/infrastructure/constant/SecurityUrls.kt @@ -14,5 +14,8 @@ object SecurityUrls { // Swagger "/docs/swagger-ui/**", "/v3/api-docs/**", + + // Health Check + "/actuator/health", ) } \ No newline at end of file diff --git a/CT-common/src/main/kotlin/com/chuseok22/ctcommon/application/exception/ErrorCode.kt b/CT-common/src/main/kotlin/com/chuseok22/ctcommon/application/exception/ErrorCode.kt index 32f06fb..3afa20b 100644 --- a/CT-common/src/main/kotlin/com/chuseok22/ctcommon/application/exception/ErrorCode.kt +++ b/CT-common/src/main/kotlin/com/chuseok22/ctcommon/application/exception/ErrorCode.kt @@ -18,4 +18,7 @@ enum class ErrorCode( // Member MEMBER_NOT_FOUND(HttpStatus.NOT_FOUND, "회원을 찾을 수 없습니다"), + + // Auth + SEJONG_PORTAL_LOGIN_FAILED(HttpStatus.BAD_REQUEST, "세종대학교 포털 로그인 실패") } \ No newline at end of file From baf8e13f6fbfb055f939565f909b75ccd00ad9b1 Mon Sep 17 00:00:00 2001 From: Chuseok22 Date: Mon, 12 Jan 2026 15:12:31 +0900 Subject: [PATCH 05/13] =?UTF-8?q?=EB=A1=9C=EA=B7=B8=EC=9D=B8=5F=EB=B0=8F?= =?UTF-8?q?=5F=EA=B8=B0=ED=83=80=5F=EC=9D=B8=EC=A6=9D=5F=EA=B4=80=EB=A0=A8?= =?UTF-8?q?=5F=EB=A1=9C=EC=A7=81=5F=EA=B0=9C=EB=B0=9C=20:=20feat=20:=20?= =?UTF-8?q?=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EC=BB=A8=ED=8A=B8=EB=A1=A4?= =?UTF-8?q?=EB=9F=AC=20=EC=B6=94=EA=B0=80=20https://github.com/CampusTable?= =?UTF-8?q?/campus-table-server/issues/5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/auth/AuthController.kt | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 CT-web/src/main/kotlin/com/chuseok22/ctweb/application/controller/auth/AuthController.kt diff --git a/CT-web/src/main/kotlin/com/chuseok22/ctweb/application/controller/auth/AuthController.kt b/CT-web/src/main/kotlin/com/chuseok22/ctweb/application/controller/auth/AuthController.kt new file mode 100644 index 0000000..84676d7 --- /dev/null +++ b/CT-web/src/main/kotlin/com/chuseok22/ctweb/application/controller/auth/AuthController.kt @@ -0,0 +1,25 @@ +package com.chuseok22.ctweb.application.controller.auth + +import com.chuseok22.ctauth.application.dto.request.LoginRequest +import com.chuseok22.ctauth.application.dto.response.LoginResponse +import com.chuseok22.ctauth.application.service.AuthService +import com.chuseok22.logging.annotation.LogMonitoring +import io.swagger.v3.oas.annotations.tags.Tag +import org.springframework.http.ResponseEntity +import org.springframework.web.bind.annotation.PostMapping +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RestController + +@RestController +@RequestMapping("/api/auth") +@Tag(name = "인증 API") +class AuthController( + private val authService: AuthService +) { + + @LogMonitoring + @PostMapping("/login") + fun login(request: LoginRequest): ResponseEntity { + return ResponseEntity.ok(authService.login(request)) + } +} \ No newline at end of file From 43e886cbadadacff3951061241f780173361cd34 Mon Sep 17 00:00:00 2001 From: Chuseok22 Date: Mon, 12 Jan 2026 15:18:07 +0900 Subject: [PATCH 06/13] =?UTF-8?q?=EB=A1=9C=EA=B7=B8=EC=9D=B8=5F=EB=B0=8F?= =?UTF-8?q?=5F=EA=B8=B0=ED=83=80=5F=EC=9D=B8=EC=A6=9D=5F=EA=B4=80=EB=A0=A8?= =?UTF-8?q?=5F=EB=A1=9C=EC=A7=81=5F=EA=B0=9C=EB=B0=9C=20:=20feat=20:=20?= =?UTF-8?q?=EC=A7=80=EC=97=AD=EB=B3=80=EC=88=98=20=EC=9E=AC=EC=82=AC?= =?UTF-8?q?=EC=9A=A9=20https://github.com/CampusTable/campus-table-server/?= =?UTF-8?q?issues/5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ctauth/application/service/AuthService.kt | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/CT-auth/src/main/kotlin/com/chuseok22/ctauth/application/service/AuthService.kt b/CT-auth/src/main/kotlin/com/chuseok22/ctauth/application/service/AuthService.kt index 2d53526..fcc0f5b 100644 --- a/CT-auth/src/main/kotlin/com/chuseok22/ctauth/application/service/AuthService.kt +++ b/CT-auth/src/main/kotlin/com/chuseok22/ctauth/application/service/AuthService.kt @@ -36,18 +36,15 @@ class AuthService( memberRepository.save(newMember) } - val memberId = member.id?.toString() - ?: throw IllegalStateException("memberId가 초기화 되지 않았습니다") - // 토큰 발급 - val accessToken = tokenProvider.createAccessToken(memberId) - val refreshToken = tokenProvider.createRefreshToken(memberId) + val accessToken = tokenProvider.createAccessToken(member.id.toString()) + val refreshToken = tokenProvider.createRefreshToken(member.id.toString()) log.info { "로그인 성공: 학번=$studentNumber, 이름=$name" } return LoginResponse( - studentNumber = sejongMemberInfo.studentId, - name = sejongMemberInfo.name, + studentNumber = studentNumber, + name = name, accessToken = accessToken, refreshToken = refreshToken ) From 4c7feb9dc8509f34748d9b631204f330c315a775 Mon Sep 17 00:00:00 2001 From: Chuseok22 Date: Mon, 12 Jan 2026 15:47:01 +0900 Subject: [PATCH 07/13] =?UTF-8?q?=EB=A1=9C=EA=B7=B8=EC=9D=B8=5F=EB=B0=8F?= =?UTF-8?q?=5F=EA=B8=B0=ED=83=80=5F=EC=9D=B8=EC=A6=9D=5F=EA=B4=80=EB=A0=A8?= =?UTF-8?q?=5F=EB=A1=9C=EC=A7=81=5F=EA=B0=9C=EB=B0=9C=20:=20feat=20:=20Log?= =?UTF-8?q?inRequest.kt=20validation=20=EC=A0=81=EC=9A=A9=20https://github?= =?UTF-8?q?.com/CampusTable/campus-table-server/issues/5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../chuseok22/ctauth/application/dto/request/LoginRequest.kt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CT-auth/src/main/kotlin/com/chuseok22/ctauth/application/dto/request/LoginRequest.kt b/CT-auth/src/main/kotlin/com/chuseok22/ctauth/application/dto/request/LoginRequest.kt index e76b5fd..32435b9 100644 --- a/CT-auth/src/main/kotlin/com/chuseok22/ctauth/application/dto/request/LoginRequest.kt +++ b/CT-auth/src/main/kotlin/com/chuseok22/ctauth/application/dto/request/LoginRequest.kt @@ -1,6 +1,10 @@ package com.chuseok22.ctauth.application.dto.request +import jakarta.validation.constraints.NotBlank + data class LoginRequest( + @field:NotBlank val sejongPortalId: String, + @field:NotBlank val sejongPortalPw: String ) From 6d0f759c428f7b5ab96dd94d764575d4aaef08f6 Mon Sep 17 00:00:00 2001 From: Chuseok22 Date: Mon, 12 Jan 2026 15:48:16 +0900 Subject: [PATCH 08/13] =?UTF-8?q?=EB=A1=9C=EA=B7=B8=EC=9D=B8=5F=EB=B0=8F?= =?UTF-8?q?=5F=EA=B8=B0=ED=83=80=5F=EC=9D=B8=EC=A6=9D=5F=EA=B4=80=EB=A0=A8?= =?UTF-8?q?=5F=EB=A1=9C=EC=A7=81=5F=EA=B0=9C=EB=B0=9C=20:=20feat=20:=20JWT?= =?UTF-8?q?=20=EC=A0=80=EC=9E=A5=20=EB=B0=8F=20=EC=82=AD=EC=A0=9C=EB=A5=BC?= =?UTF-8?q?=20=EC=9C=84=ED=95=9C=20TokenManager.kt=20=EC=B6=94=EA=B0=80=20?= =?UTF-8?q?https://github.com/CampusTable/campus-table-server/issues/5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ctauth/core/token/TokenManager.kt | 19 +++++++++ .../chuseok22/ctauth/core/token/TokenPair.kt | 6 +++ .../ctauth/infrastructure/jwt/JwtManager.kt | 39 +++++++++++++++++++ 3 files changed, 64 insertions(+) create mode 100644 CT-auth/src/main/kotlin/com/chuseok22/ctauth/core/token/TokenManager.kt create mode 100644 CT-auth/src/main/kotlin/com/chuseok22/ctauth/core/token/TokenPair.kt create mode 100644 CT-auth/src/main/kotlin/com/chuseok22/ctauth/infrastructure/jwt/JwtManager.kt diff --git a/CT-auth/src/main/kotlin/com/chuseok22/ctauth/core/token/TokenManager.kt b/CT-auth/src/main/kotlin/com/chuseok22/ctauth/core/token/TokenManager.kt new file mode 100644 index 0000000..c7a7391 --- /dev/null +++ b/CT-auth/src/main/kotlin/com/chuseok22/ctauth/core/token/TokenManager.kt @@ -0,0 +1,19 @@ +package com.chuseok22.ctauth.core.token + +interface TokenManager { + + /** + * accessToken, refreshToken Pair 생성 + */ + fun createTokenPair(memberId: String): TokenPair + + /** + * refreshToken TTL 저장 + */ + fun saveRefreshTokenTtl(memberId: String, refreshToken: String) + + /** + * refreshToken TTL 삭제 + */ + fun removeRefreshTokenTtl(memberId: String) +} \ No newline at end of file diff --git a/CT-auth/src/main/kotlin/com/chuseok22/ctauth/core/token/TokenPair.kt b/CT-auth/src/main/kotlin/com/chuseok22/ctauth/core/token/TokenPair.kt new file mode 100644 index 0000000..b90de97 --- /dev/null +++ b/CT-auth/src/main/kotlin/com/chuseok22/ctauth/core/token/TokenPair.kt @@ -0,0 +1,6 @@ +package com.chuseok22.ctauth.core.token + +data class TokenPair( + val accessToken: String, + val refreshToken: String +) diff --git a/CT-auth/src/main/kotlin/com/chuseok22/ctauth/infrastructure/jwt/JwtManager.kt b/CT-auth/src/main/kotlin/com/chuseok22/ctauth/infrastructure/jwt/JwtManager.kt new file mode 100644 index 0000000..e4ff14c --- /dev/null +++ b/CT-auth/src/main/kotlin/com/chuseok22/ctauth/infrastructure/jwt/JwtManager.kt @@ -0,0 +1,39 @@ +package com.chuseok22.ctauth.infrastructure.jwt + +import com.chuseok22.ctauth.core.token.TokenManager +import com.chuseok22.ctauth.core.token.TokenPair +import com.chuseok22.ctauth.core.token.TokenProvider +import com.chuseok22.ctauth.core.token.TokenStore +import com.chuseok22.ctauth.infrastructure.properties.JwtProperties +import com.chuseok22.ctauth.infrastructure.util.AuthUtil +import io.github.oshai.kotlinlogging.KotlinLogging +import org.springframework.stereotype.Component + +private val log = KotlinLogging.logger { } + +@Component +class JwtManager( + private val tokenProvider: TokenProvider, + private val tokenStore: TokenStore, + private val jwtProperties: JwtProperties +) : TokenManager { + + override fun createTokenPair(memberId: String): TokenPair { + return TokenPair( + accessToken = tokenProvider.createAccessToken(memberId), + refreshToken = tokenProvider.createRefreshToken(memberId) + ) + } + + override fun saveRefreshTokenTtl(memberId: String, refreshToken: String) { + log.debug { "Redis에 refreshToken을 저장합니다" } + val key = AuthUtil.getRefreshTokenTtlKey(memberId) + tokenStore.save(key, refreshToken, jwtProperties.refreshExpMillis) + } + + override fun removeRefreshTokenTtl(memberId: String) { + log.debug { "Redis에 저장된 refreshToken을 삭제합니다: 회원=$memberId" } + val key = AuthUtil.getRefreshTokenTtlKey(memberId) + tokenStore.remove(key) + } +} \ No newline at end of file From ec8378bbf347dd72a3e7a643c2fac9b769575036 Mon Sep 17 00:00:00 2001 From: Chuseok22 Date: Mon, 12 Jan 2026 15:48:42 +0900 Subject: [PATCH 09/13] =?UTF-8?q?=EB=A1=9C=EA=B7=B8=EC=9D=B8=5F=EB=B0=8F?= =?UTF-8?q?=5F=EA=B8=B0=ED=83=80=5F=EC=9D=B8=EC=A6=9D=5F=EA=B4=80=EB=A0=A8?= =?UTF-8?q?=5F=EB=A1=9C=EC=A7=81=5F=EA=B0=9C=EB=B0=9C=20:=20feat=20:=20Rei?= =?UTF-8?q?ssue=20&=20=EB=A1=9C=EA=B7=B8=EC=95=84=EC=9B=83=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EC=B6=94=EA=B0=80=20https://github.com/CampusTable?= =?UTF-8?q?/campus-table-server/issues/5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/dto/request/ReissueRequest.kt | 8 ++++ .../dto/response/ReissueResponse.kt | 6 +++ .../ctauth/application/service/AuthService.kt | 38 ++++++++++++++++--- 3 files changed, 47 insertions(+), 5 deletions(-) create mode 100644 CT-auth/src/main/kotlin/com/chuseok22/ctauth/application/dto/request/ReissueRequest.kt create mode 100644 CT-auth/src/main/kotlin/com/chuseok22/ctauth/application/dto/response/ReissueResponse.kt diff --git a/CT-auth/src/main/kotlin/com/chuseok22/ctauth/application/dto/request/ReissueRequest.kt b/CT-auth/src/main/kotlin/com/chuseok22/ctauth/application/dto/request/ReissueRequest.kt new file mode 100644 index 0000000..c7da225 --- /dev/null +++ b/CT-auth/src/main/kotlin/com/chuseok22/ctauth/application/dto/request/ReissueRequest.kt @@ -0,0 +1,8 @@ +package com.chuseok22.ctauth.application.dto.request + +import jakarta.validation.constraints.NotBlank + +data class ReissueRequest( + @field:NotBlank + val refreshToken: String +) diff --git a/CT-auth/src/main/kotlin/com/chuseok22/ctauth/application/dto/response/ReissueResponse.kt b/CT-auth/src/main/kotlin/com/chuseok22/ctauth/application/dto/response/ReissueResponse.kt new file mode 100644 index 0000000..aa25a4b --- /dev/null +++ b/CT-auth/src/main/kotlin/com/chuseok22/ctauth/application/dto/response/ReissueResponse.kt @@ -0,0 +1,6 @@ +package com.chuseok22.ctauth.application.dto.response + +data class ReissueResponse( + val accessToken: String, + val refreshToken: String +) diff --git a/CT-auth/src/main/kotlin/com/chuseok22/ctauth/application/service/AuthService.kt b/CT-auth/src/main/kotlin/com/chuseok22/ctauth/application/service/AuthService.kt index fcc0f5b..dffbb60 100644 --- a/CT-auth/src/main/kotlin/com/chuseok22/ctauth/application/service/AuthService.kt +++ b/CT-auth/src/main/kotlin/com/chuseok22/ctauth/application/service/AuthService.kt @@ -1,7 +1,10 @@ package com.chuseok22.ctauth.application.service import com.chuseok22.ctauth.application.dto.request.LoginRequest +import com.chuseok22.ctauth.application.dto.request.ReissueRequest import com.chuseok22.ctauth.application.dto.response.LoginResponse +import com.chuseok22.ctauth.application.dto.response.ReissueResponse +import com.chuseok22.ctauth.core.token.TokenManager import com.chuseok22.ctauth.core.token.TokenProvider import com.chuseok22.ctcommon.application.exception.CustomException import com.chuseok22.ctcommon.application.exception.ErrorCode @@ -19,7 +22,8 @@ private val log = KotlinLogging.logger { } class AuthService( private val sejongPortalLoginService: SejongPortalLoginService, private val memberRepository: MemberRepository, - private val tokenProvider: TokenProvider + private val tokenProvider: TokenProvider, + private val tokenManager: TokenManager ) { @Transactional @@ -37,19 +41,43 @@ class AuthService( } // 토큰 발급 - val accessToken = tokenProvider.createAccessToken(member.id.toString()) - val refreshToken = tokenProvider.createRefreshToken(member.id.toString()) + val tokenPair = tokenManager.createTokenPair(member.id.toString()) log.info { "로그인 성공: 학번=$studentNumber, 이름=$name" } return LoginResponse( studentNumber = studentNumber, name = name, - accessToken = accessToken, - refreshToken = refreshToken + accessToken = tokenPair.accessToken, + refreshToken = tokenPair.refreshToken ) } + @Transactional + fun reissue(request: ReissueRequest): ReissueResponse { + log.debug { "accessToken이 만료되어 재발급을 진행합니다" } + val memberId = tokenProvider.getMemberId(request.refreshToken) + + log.debug { "기존에 저장된 refreshToken 삭제" } + tokenManager.removeRefreshTokenTtl(memberId) + + log.debug { "새로운 accessToken, refreshToken 발급" } + val tokenPair = tokenManager.createTokenPair(memberId) + + return ReissueResponse( + accessToken = tokenPair.accessToken, + refreshToken = tokenPair.refreshToken + ) + } + + @Transactional + fun logout(member: Member) { + val memberId = member.id + log.debug { "로그아웃을 진행합니다: 회원=$memberId" } + log.debug { "기존에 저장된 refreshToken 삭제" } + tokenManager.removeRefreshTokenTtl(memberId.toString()) + } + private fun sejongPortalLogin(sejongPortalId: String, sejongPortalPw: String): SejongMemberInfo { try { log.debug { "세종대학교 포털 로그인을 시도합니다: $sejongPortalId" } From e21b082e0f0cddca51dbaadb0dd5b470cf177619 Mon Sep 17 00:00:00 2001 From: Chuseok22 Date: Mon, 12 Jan 2026 15:52:37 +0900 Subject: [PATCH 10/13] =?UTF-8?q?=EB=A1=9C=EA=B7=B8=EC=9D=B8=5F=EB=B0=8F?= =?UTF-8?q?=5F=EA=B8=B0=ED=83=80=5F=EC=9D=B8=EC=A6=9D=5F=EA=B4=80=EB=A0=A8?= =?UTF-8?q?=5F=EB=A1=9C=EC=A7=81=5F=EA=B0=9C=EB=B0=9C=20:=20feat=20:=20Rei?= =?UTF-8?q?ssue=20&=20=EB=A1=9C=EA=B7=B8=EC=95=84=EC=9B=83=20=EC=BB=A8?= =?UTF-8?q?=ED=8A=B8=EB=A1=A4=EB=9F=AC=20=EC=B6=94=EA=B0=80=20https://gith?= =?UTF-8?q?ub.com/CampusTable/campus-table-server/issues/5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/controller/auth/AuthController.kt | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/CT-web/src/main/kotlin/com/chuseok22/ctweb/application/controller/auth/AuthController.kt b/CT-web/src/main/kotlin/com/chuseok22/ctweb/application/controller/auth/AuthController.kt index 84676d7..48ae549 100644 --- a/CT-web/src/main/kotlin/com/chuseok22/ctweb/application/controller/auth/AuthController.kt +++ b/CT-web/src/main/kotlin/com/chuseok22/ctweb/application/controller/auth/AuthController.kt @@ -1,10 +1,13 @@ package com.chuseok22.ctweb.application.controller.auth import com.chuseok22.ctauth.application.dto.request.LoginRequest +import com.chuseok22.ctauth.application.dto.request.ReissueRequest import com.chuseok22.ctauth.application.dto.response.LoginResponse +import com.chuseok22.ctauth.application.dto.response.ReissueResponse import com.chuseok22.ctauth.application.service.AuthService import com.chuseok22.logging.annotation.LogMonitoring import io.swagger.v3.oas.annotations.tags.Tag +import org.springframework.http.HttpStatus import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.PostMapping import org.springframework.web.bind.annotation.RequestMapping @@ -22,4 +25,16 @@ class AuthController( fun login(request: LoginRequest): ResponseEntity { return ResponseEntity.ok(authService.login(request)) } + + @LogMonitoring + @PostMapping("/reissue") + fun reissue(request: ReissueRequest): ResponseEntity { + return ResponseEntity.ok(authService.reissue(request)) + } + + @LogMonitoring + @PostMapping("/logout") + fun logout(): ResponseEntity { + return ResponseEntity.status(HttpStatus.NO_CONTENT).build() + } } \ No newline at end of file From 37e543cb919d728181d45aa7ae2ac6f3c84bb9b7 Mon Sep 17 00:00:00 2001 From: Chuseok22 Date: Mon, 12 Jan 2026 16:27:37 +0900 Subject: [PATCH 11/13] =?UTF-8?q?=EB=A1=9C=EA=B7=B8=EC=9D=B8=5F=EB=B0=8F?= =?UTF-8?q?=5F=EA=B8=B0=ED=83=80=5F=EC=9D=B8=EC=A6=9D=5F=EA=B4=80=EB=A0=A8?= =?UTF-8?q?=5F=EB=A1=9C=EC=A7=81=5F=EA=B0=9C=EB=B0=9C=20:=20feat=20:=20?= =?UTF-8?q?=EC=A0=80=EC=9E=A5=EB=90=9C=20=EB=A6=AC=ED=94=84=EB=A0=88?= =?UTF-8?q?=EC=8B=9C=20=ED=86=A0=ED=81=B0=20=EA=B2=80=EC=A6=9D=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EC=B6=94=EA=B0=80=20https://github.com/CampusTable?= =?UTF-8?q?/campus-table-server/issues/5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ctauth/core/token/TokenManager.kt | 5 ++++ .../ctauth/infrastructure/jwt/JwtManager.kt | 25 ++++++++++++++++--- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/CT-auth/src/main/kotlin/com/chuseok22/ctauth/core/token/TokenManager.kt b/CT-auth/src/main/kotlin/com/chuseok22/ctauth/core/token/TokenManager.kt index c7a7391..4837675 100644 --- a/CT-auth/src/main/kotlin/com/chuseok22/ctauth/core/token/TokenManager.kt +++ b/CT-auth/src/main/kotlin/com/chuseok22/ctauth/core/token/TokenManager.kt @@ -16,4 +16,9 @@ interface TokenManager { * refreshToken TTL 삭제 */ fun removeRefreshTokenTtl(memberId: String) + + /** + * Redis에 저장된 토큰인지 검증 + */ + fun validateSavedToken(token: String) } \ No newline at end of file diff --git a/CT-auth/src/main/kotlin/com/chuseok22/ctauth/infrastructure/jwt/JwtManager.kt b/CT-auth/src/main/kotlin/com/chuseok22/ctauth/infrastructure/jwt/JwtManager.kt index e4ff14c..86bea25 100644 --- a/CT-auth/src/main/kotlin/com/chuseok22/ctauth/infrastructure/jwt/JwtManager.kt +++ b/CT-auth/src/main/kotlin/com/chuseok22/ctauth/infrastructure/jwt/JwtManager.kt @@ -6,7 +6,10 @@ import com.chuseok22.ctauth.core.token.TokenProvider import com.chuseok22.ctauth.core.token.TokenStore import com.chuseok22.ctauth.infrastructure.properties.JwtProperties import com.chuseok22.ctauth.infrastructure.util.AuthUtil +import com.chuseok22.ctcommon.application.exception.CustomException +import com.chuseok22.ctcommon.application.exception.ErrorCode import io.github.oshai.kotlinlogging.KotlinLogging +import org.springframework.data.redis.core.RedisTemplate import org.springframework.stereotype.Component private val log = KotlinLogging.logger { } @@ -15,7 +18,8 @@ private val log = KotlinLogging.logger { } class JwtManager( private val tokenProvider: TokenProvider, private val tokenStore: TokenStore, - private val jwtProperties: JwtProperties + private val jwtProperties: JwtProperties, + private val redisTemplate: RedisTemplate ) : TokenManager { override fun createTokenPair(memberId: String): TokenPair { @@ -27,13 +31,28 @@ class JwtManager( override fun saveRefreshTokenTtl(memberId: String, refreshToken: String) { log.debug { "Redis에 refreshToken을 저장합니다" } - val key = AuthUtil.getRefreshTokenTtlKey(memberId) + val key = getKey(memberId) tokenStore.save(key, refreshToken, jwtProperties.refreshExpMillis) } override fun removeRefreshTokenTtl(memberId: String) { log.debug { "Redis에 저장된 refreshToken을 삭제합니다: 회원=$memberId" } - val key = AuthUtil.getRefreshTokenTtlKey(memberId) + val key = getKey(memberId) tokenStore.remove(key) } + + override fun validateSavedToken(token: String) { + val memberId = tokenProvider.getMemberId(token) + val key = getKey(memberId) + val savedToken = redisTemplate.opsForValue().get(key) + ?: throw CustomException(ErrorCode.INVALID_JWT) + if (savedToken != token) { + log.warn { "유효하지 않은 refreshToken 사용 시도: $memberId" } + throw CustomException(ErrorCode.INVALID_JWT) + } + } + + private fun getKey(memberId: String): String { + return AuthUtil.getRefreshTokenTtlKey(memberId) + } } \ No newline at end of file From 7af97fac40b80cf8a7ab47d4e8548b7b5de478a9 Mon Sep 17 00:00:00 2001 From: Chuseok22 Date: Mon, 12 Jan 2026 16:28:57 +0900 Subject: [PATCH 12/13] =?UTF-8?q?=EB=A1=9C=EA=B7=B8=EC=9D=B8=5F=EB=B0=8F?= =?UTF-8?q?=5F=EA=B8=B0=ED=83=80=5F=EC=9D=B8=EC=A6=9D=5F=EA=B4=80=EB=A0=A8?= =?UTF-8?q?=5F=EB=A1=9C=EC=A7=81=5F=EA=B0=9C=EB=B0=9C=20:=20feat=20:=20spr?= =?UTF-8?q?ing=20validate=20=EB=B0=8F=20requestBody=20=EC=96=B4=EB=85=B8?= =?UTF-8?q?=ED=85=8C=EC=9D=B4=EC=85=98=20=EC=B6=94=EA=B0=80=20&=20reissue?= =?UTF-8?q?=20=EC=8B=9C=20=EB=A6=AC=ED=94=84=EB=A0=88=EC=8B=9C=20=ED=86=A0?= =?UTF-8?q?=ED=81=B0=20=EA=B2=80=EC=A6=9D=20https://github.com/CampusTable?= =?UTF-8?q?/campus-table-server/issues/5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ctauth/application/service/AuthService.kt | 2 ++ .../infrastructure/user/CustomUserDetails.kt | 4 ++++ .../controller/auth/AuthController.kt | 17 ++++++++++++++--- 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/CT-auth/src/main/kotlin/com/chuseok22/ctauth/application/service/AuthService.kt b/CT-auth/src/main/kotlin/com/chuseok22/ctauth/application/service/AuthService.kt index dffbb60..68dcc6c 100644 --- a/CT-auth/src/main/kotlin/com/chuseok22/ctauth/application/service/AuthService.kt +++ b/CT-auth/src/main/kotlin/com/chuseok22/ctauth/application/service/AuthService.kt @@ -58,6 +58,8 @@ class AuthService( log.debug { "accessToken이 만료되어 재발급을 진행합니다" } val memberId = tokenProvider.getMemberId(request.refreshToken) + tokenManager.validateSavedToken(request.refreshToken) + log.debug { "기존에 저장된 refreshToken 삭제" } tokenManager.removeRefreshTokenTtl(memberId) diff --git a/CT-auth/src/main/kotlin/com/chuseok22/ctauth/infrastructure/user/CustomUserDetails.kt b/CT-auth/src/main/kotlin/com/chuseok22/ctauth/infrastructure/user/CustomUserDetails.kt index d7f131e..b1a28bf 100644 --- a/CT-auth/src/main/kotlin/com/chuseok22/ctauth/infrastructure/user/CustomUserDetails.kt +++ b/CT-auth/src/main/kotlin/com/chuseok22/ctauth/infrastructure/user/CustomUserDetails.kt @@ -34,4 +34,8 @@ class CustomUserDetails( override fun getName(): String { return member.name } + + fun getMember(): Member{ + return member + } } \ No newline at end of file diff --git a/CT-web/src/main/kotlin/com/chuseok22/ctweb/application/controller/auth/AuthController.kt b/CT-web/src/main/kotlin/com/chuseok22/ctweb/application/controller/auth/AuthController.kt index 48ae549..98fd4e4 100644 --- a/CT-web/src/main/kotlin/com/chuseok22/ctweb/application/controller/auth/AuthController.kt +++ b/CT-web/src/main/kotlin/com/chuseok22/ctweb/application/controller/auth/AuthController.kt @@ -5,11 +5,15 @@ import com.chuseok22.ctauth.application.dto.request.ReissueRequest import com.chuseok22.ctauth.application.dto.response.LoginResponse import com.chuseok22.ctauth.application.dto.response.ReissueResponse import com.chuseok22.ctauth.application.service.AuthService +import com.chuseok22.ctauth.infrastructure.user.CustomUserDetails import com.chuseok22.logging.annotation.LogMonitoring import io.swagger.v3.oas.annotations.tags.Tag import org.springframework.http.HttpStatus import org.springframework.http.ResponseEntity +import org.springframework.security.core.annotation.AuthenticationPrincipal +import org.springframework.validation.annotation.Validated import org.springframework.web.bind.annotation.PostMapping +import org.springframework.web.bind.annotation.RequestBody import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RestController @@ -22,19 +26,26 @@ class AuthController( @LogMonitoring @PostMapping("/login") - fun login(request: LoginRequest): ResponseEntity { + fun login( + @Validated @RequestBody request: LoginRequest + ): ResponseEntity { return ResponseEntity.ok(authService.login(request)) } @LogMonitoring @PostMapping("/reissue") - fun reissue(request: ReissueRequest): ResponseEntity { + fun reissue( + @Validated @RequestBody request: ReissueRequest + ): ResponseEntity { return ResponseEntity.ok(authService.reissue(request)) } @LogMonitoring @PostMapping("/logout") - fun logout(): ResponseEntity { + fun logout( + @AuthenticationPrincipal customUserDetails: CustomUserDetails + ): ResponseEntity { + authService.logout(customUserDetails.getMember()) return ResponseEntity.status(HttpStatus.NO_CONTENT).build() } } \ No newline at end of file From b214dd06acda2be09517af111910097c28d7ee30 Mon Sep 17 00:00:00 2001 From: Chuseok22 Date: Mon, 12 Jan 2026 16:46:04 +0900 Subject: [PATCH 13/13] =?UTF-8?q?=EB=A1=9C=EA=B7=B8=EC=9D=B8=5F=EB=B0=8F?= =?UTF-8?q?=5F=EA=B8=B0=ED=83=80=5F=EC=9D=B8=EC=A6=9D=5F=EA=B4=80=EB=A0=A8?= =?UTF-8?q?=5F=EB=A1=9C=EC=A7=81=5F=EA=B0=9C=EB=B0=9C=20:=20feat=20:=20?= =?UTF-8?q?=EB=A6=AC=ED=94=84=EB=A0=88=EC=8B=9C=20=ED=86=A0=ED=81=B0?= =?UTF-8?q?=EC=9D=84=20=ED=86=B5=ED=95=9C=20reissue=20=EA=B3=BC=EC=A0=95?= =?UTF-8?q?=20=EA=B2=80=EC=A6=9D=20=EA=B0=95=ED=99=94=20https://github.com?= =?UTF-8?q?/CampusTable/campus-table-server/issues/5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ctauth/application/service/AuthService.kt | 9 +++---- .../chuseok22/ctauth/core/token/TokenStore.kt | 5 ++++ .../ctauth/infrastructure/jwt/JwtManager.kt | 24 ++++++++++++++----- .../ctauth/infrastructure/jwt/JwtProvider.kt | 4 ++-- .../ctauth/infrastructure/jwt/JwtStore.kt | 5 ++++ .../infrastructure/user/CustomUserDetails.kt | 6 +---- .../controller/auth/AuthController.kt | 6 ++--- 7 files changed, 37 insertions(+), 22 deletions(-) diff --git a/CT-auth/src/main/kotlin/com/chuseok22/ctauth/application/service/AuthService.kt b/CT-auth/src/main/kotlin/com/chuseok22/ctauth/application/service/AuthService.kt index 68dcc6c..53f1f3c 100644 --- a/CT-auth/src/main/kotlin/com/chuseok22/ctauth/application/service/AuthService.kt +++ b/CT-auth/src/main/kotlin/com/chuseok22/ctauth/application/service/AuthService.kt @@ -42,6 +42,7 @@ class AuthService( // 토큰 발급 val tokenPair = tokenManager.createTokenPair(member.id.toString()) + tokenManager.saveRefreshTokenTtl(member.id.toString(), tokenPair.refreshToken) log.info { "로그인 성공: 학번=$studentNumber, 이름=$name" } @@ -53,18 +54,15 @@ class AuthService( ) } - @Transactional fun reissue(request: ReissueRequest): ReissueResponse { - log.debug { "accessToken이 만료되어 재발급을 진행합니다" } + log.debug { "토큰 재발급을 진행합니다" } val memberId = tokenProvider.getMemberId(request.refreshToken) tokenManager.validateSavedToken(request.refreshToken) - log.debug { "기존에 저장된 refreshToken 삭제" } - tokenManager.removeRefreshTokenTtl(memberId) - log.debug { "새로운 accessToken, refreshToken 발급" } val tokenPair = tokenManager.createTokenPair(memberId) + tokenManager.saveRefreshTokenTtl(memberId, tokenPair.refreshToken) return ReissueResponse( accessToken = tokenPair.accessToken, @@ -72,7 +70,6 @@ class AuthService( ) } - @Transactional fun logout(member: Member) { val memberId = member.id log.debug { "로그아웃을 진행합니다: 회원=$memberId" } diff --git a/CT-auth/src/main/kotlin/com/chuseok22/ctauth/core/token/TokenStore.kt b/CT-auth/src/main/kotlin/com/chuseok22/ctauth/core/token/TokenStore.kt index 2ca915d..658ccf5 100644 --- a/CT-auth/src/main/kotlin/com/chuseok22/ctauth/core/token/TokenStore.kt +++ b/CT-auth/src/main/kotlin/com/chuseok22/ctauth/core/token/TokenStore.kt @@ -2,6 +2,11 @@ package com.chuseok22.ctauth.core.token interface TokenStore { + /** + * Redis에 저장된 refreshToken 조회 + */ + fun get(key: String): String? + /** * 리프레시 토큰을 주어진 Key로 저장하고 TTL(ms) 설정 */ diff --git a/CT-auth/src/main/kotlin/com/chuseok22/ctauth/infrastructure/jwt/JwtManager.kt b/CT-auth/src/main/kotlin/com/chuseok22/ctauth/infrastructure/jwt/JwtManager.kt index 86bea25..7608c0c 100644 --- a/CT-auth/src/main/kotlin/com/chuseok22/ctauth/infrastructure/jwt/JwtManager.kt +++ b/CT-auth/src/main/kotlin/com/chuseok22/ctauth/infrastructure/jwt/JwtManager.kt @@ -9,7 +9,7 @@ import com.chuseok22.ctauth.infrastructure.util.AuthUtil import com.chuseok22.ctcommon.application.exception.CustomException import com.chuseok22.ctcommon.application.exception.ErrorCode import io.github.oshai.kotlinlogging.KotlinLogging -import org.springframework.data.redis.core.RedisTemplate +import io.jsonwebtoken.ExpiredJwtException import org.springframework.stereotype.Component private val log = KotlinLogging.logger { } @@ -18,8 +18,7 @@ private val log = KotlinLogging.logger { } class JwtManager( private val tokenProvider: TokenProvider, private val tokenStore: TokenStore, - private val jwtProperties: JwtProperties, - private val redisTemplate: RedisTemplate + private val jwtProperties: JwtProperties ) : TokenManager { override fun createTokenPair(memberId: String): TokenPair { @@ -42,10 +41,23 @@ class JwtManager( } override fun validateSavedToken(token: String) { - val memberId = tokenProvider.getMemberId(token) + + val memberId = try { + tokenProvider.getMemberId(token) + } catch (e: ExpiredJwtException) { + log.warn(e) { "만료된 refreshToken 사용 시도" } + throw e + } catch (e: Exception) { + log.warn(e) { "유효하지 않은 refreshToken 사용 시도" } + throw CustomException(ErrorCode.INVALID_JWT) + } + val key = getKey(memberId) - val savedToken = redisTemplate.opsForValue().get(key) - ?: throw CustomException(ErrorCode.INVALID_JWT) + val savedToken = tokenStore.get(key) ?: run { + log.warn { "Redis에 refreshToken이 존재하지 않습니다: 회원=$memberId" } + throw CustomException(ErrorCode.INVALID_JWT) + } + if (savedToken != token) { log.warn { "유효하지 않은 refreshToken 사용 시도: $memberId" } throw CustomException(ErrorCode.INVALID_JWT) diff --git a/CT-auth/src/main/kotlin/com/chuseok22/ctauth/infrastructure/jwt/JwtProvider.kt b/CT-auth/src/main/kotlin/com/chuseok22/ctauth/infrastructure/jwt/JwtProvider.kt index 860a9e6..b220792 100644 --- a/CT-auth/src/main/kotlin/com/chuseok22/ctauth/infrastructure/jwt/JwtProvider.kt +++ b/CT-auth/src/main/kotlin/com/chuseok22/ctauth/infrastructure/jwt/JwtProvider.kt @@ -24,7 +24,7 @@ class JwtProvider( category = AuthConstants.ACCESS_TOKEN_CATEGORY, memberId = memberId, expMillis = properties.accessExpMillis - ).also { log.info { "엑세스 토큰 생성완료: memberId = $memberId" } } + ).also { log.debug { "엑세스 토큰 생성완료: memberId = $memberId" } } } override fun createRefreshToken(memberId: String): String { @@ -32,7 +32,7 @@ class JwtProvider( category = AuthConstants.REFRESH_TOKEN_CATEGORY, memberId = memberId, expMillis = properties.refreshExpMillis - ).also { log.info { "리프레시 토큰 생성완료: memberId = $memberId" } } + ).also { log.debug { "리프레시 토큰 생성완료: memberId = $memberId" } } } override fun isValidToken(token: String): Boolean { diff --git a/CT-auth/src/main/kotlin/com/chuseok22/ctauth/infrastructure/jwt/JwtStore.kt b/CT-auth/src/main/kotlin/com/chuseok22/ctauth/infrastructure/jwt/JwtStore.kt index 9f394e9..f90f99e 100644 --- a/CT-auth/src/main/kotlin/com/chuseok22/ctauth/infrastructure/jwt/JwtStore.kt +++ b/CT-auth/src/main/kotlin/com/chuseok22/ctauth/infrastructure/jwt/JwtStore.kt @@ -7,6 +7,11 @@ import java.util.concurrent.TimeUnit class JwtStore( private val redisTemplate: RedisTemplate ) : TokenStore { + + override fun get(key: String): String? { + return redisTemplate.opsForValue().get(key) + } + override fun save(key: String, refreshToken: String, ttlMillis: Long) { redisTemplate.opsForValue().set(key, refreshToken, ttlMillis, TimeUnit.MILLISECONDS) } diff --git a/CT-auth/src/main/kotlin/com/chuseok22/ctauth/infrastructure/user/CustomUserDetails.kt b/CT-auth/src/main/kotlin/com/chuseok22/ctauth/infrastructure/user/CustomUserDetails.kt index b1a28bf..719600b 100644 --- a/CT-auth/src/main/kotlin/com/chuseok22/ctauth/infrastructure/user/CustomUserDetails.kt +++ b/CT-auth/src/main/kotlin/com/chuseok22/ctauth/infrastructure/user/CustomUserDetails.kt @@ -8,7 +8,7 @@ import org.springframework.security.core.userdetails.UserDetails import java.security.Principal class CustomUserDetails( - private val member: Member + val member: Member ) : UserDetails, UserPrincipal, Principal { override fun getAuthorities(): Collection { return listOf(SimpleGrantedAuthority(member.role.name)) @@ -34,8 +34,4 @@ class CustomUserDetails( override fun getName(): String { return member.name } - - fun getMember(): Member{ - return member - } } \ No newline at end of file diff --git a/CT-web/src/main/kotlin/com/chuseok22/ctweb/application/controller/auth/AuthController.kt b/CT-web/src/main/kotlin/com/chuseok22/ctweb/application/controller/auth/AuthController.kt index 98fd4e4..98cff79 100644 --- a/CT-web/src/main/kotlin/com/chuseok22/ctweb/application/controller/auth/AuthController.kt +++ b/CT-web/src/main/kotlin/com/chuseok22/ctweb/application/controller/auth/AuthController.kt @@ -44,8 +44,8 @@ class AuthController( @PostMapping("/logout") fun logout( @AuthenticationPrincipal customUserDetails: CustomUserDetails - ): ResponseEntity { - authService.logout(customUserDetails.getMember()) - return ResponseEntity.status(HttpStatus.NO_CONTENT).build() + ): ResponseEntity { + authService.logout(customUserDetails.member) + return ResponseEntity.noContent().build() } } \ No newline at end of file