-
Notifications
You must be signed in to change notification settings - Fork 0
feature/13-layered-to-hexagonal-auth #21
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 9 commits
7f95e56
d85f1a4
51a12d4
6332078
b0d6809
81f784e
1263db5
f245912
d4ede1b
bbf106f
e7b2e1e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| package hs.kr.entrydsm.user.domain.auth.adapter.`in`.web | ||
|
|
||
| import hs.kr.entrydsm.user.domain.auth.adapter.`in`.web.dto.request.PassPopupRequest | ||
| import hs.kr.entrydsm.user.domain.auth.adapter.`in`.web.dto.resopnse.QueryPassInfoResponse | ||
| import hs.kr.entrydsm.user.domain.auth.application.port.`in`.PassPopupUseCase | ||
| import hs.kr.entrydsm.user.domain.auth.application.port.`in`.QueryPassInfoUseCase | ||
| import hs.kr.entrydsm.user.global.document.auth.AuthApiDocument | ||
| import jakarta.validation.Valid | ||
| import org.springframework.web.bind.annotation.GetMapping | ||
| 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.RequestParam | ||
| import org.springframework.web.bind.annotation.RestController | ||
|
|
||
| /** | ||
| * 패스 인증 관련 HTTP 요청을 처리하는 REST 컨트롤러 클래스입니다. | ||
| */ | ||
| @RestController | ||
| @RequestMapping("/user/verify") | ||
| class PassInfoController( | ||
| private val passPopupUseCase: PassPopupUseCase, | ||
| private val queryPassInfoUseCase: QueryPassInfoUseCase, | ||
| ) : AuthApiDocument { | ||
| /** | ||
| * 패스 인증 정보를 조회합니다. | ||
| */ | ||
| @GetMapping("/info") | ||
| override fun getPassInfo( | ||
| @RequestParam("mdl_tkn") token: String, | ||
| ): QueryPassInfoResponse = queryPassInfoUseCase.queryPassInfo(token) | ||
|
|
||
| /** | ||
| * 패스 인증 팝업을 생성합니다. | ||
| */ | ||
| @PostMapping("/popup") | ||
| override fun popupPass( | ||
| @RequestBody request: @Valid PassPopupRequest, | ||
| ): String = passPopupUseCase.generatePopup(request) | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| package hs.kr.entrydsm.user.domain.auth.adapter.`in`.web.dto.request | ||
|
|
||
| import jakarta.validation.constraints.NotBlank | ||
|
|
||
| /** | ||
| * 패스 팝업 생성 요청 데이터를 담는 DTO 클래스입니다. | ||
| */ | ||
| data class PassPopupRequest( | ||
| @NotBlank(message = "redirect_url은 Null 또는 공백 또는 띄어쓰기를 허용하지 않습니다.") | ||
| val redirectUrl: String, | ||
| ) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| package hs.kr.entrydsm.user.domain.auth.adapter.`in`.web.dto.resopnse | ||
|
|
||
| /** | ||
| * 패스 인증 정보 조회 응답 데이터를 담는 DTO 클래스입니다. | ||
| */ | ||
| data class QueryPassInfoResponse( | ||
| val phoneNumber: String, | ||
| val name: String, | ||
| ) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| package hs.kr.entrydsm.user.domain.auth.adapter.out | ||
|
|
||
| import org.springframework.data.annotation.Id | ||
| import org.springframework.data.redis.core.RedisHash | ||
| import org.springframework.data.redis.core.TimeToLive | ||
| import org.springframework.data.redis.core.index.Indexed | ||
|
|
||
| /** | ||
| * Redis에 저장되는 Pass 인증 정보를 나타내는 클래스입니다. | ||
| * Pass 인증을 통해 검증된 사용자 정보를 임시로 저장합니다. | ||
| * | ||
| * @property phoneNumber 암호화된 전화번호 (Redis 키로 사용) | ||
| * @property name 암호화된 사용자 이름 | ||
| * @property ttl Time To Live (데이터 만료 시간, 초 단위) | ||
| */ | ||
| @RedisHash | ||
| class PassInfo( | ||
| @Id | ||
| val phoneNumberHash: String, | ||
| val phoneNumber: String, | ||
| val name: String, | ||
| @TimeToLive | ||
| val ttl: Long | ||
| ) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| package hs.kr.entrydsm.user.domain.auth.adapter.out.repository | ||
|
|
||
| import hs.kr.entrydsm.user.domain.auth.adapter.out.PassInfo | ||
| import org.springframework.data.repository.CrudRepository | ||
| import java.util.Optional | ||
|
|
||
| /** | ||
| * Pass 인증 정보를 위한 Redis 저장소 인터페이스입니다. | ||
| * Spring Data Redis를 통해 Pass 인증 데이터의 관리를 담당합니다. | ||
| */ | ||
| interface PassInfoRepository : CrudRepository<PassInfo, String> { | ||
|
|
||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| package hs.kr.entrydsm.user.domain.auth.application.port.`in` | ||
|
|
||
| import hs.kr.entrydsm.user.domain.auth.adapter.`in`.web.dto.request.PassPopupRequest | ||
|
|
||
| /** | ||
| * 패스 팝업 생성 기능을 정의하는 UseCase 인터페이스입니다. | ||
| */ | ||
| interface PassPopupUseCase { | ||
| /** | ||
| * 패스 인증 팝업을 생성합니다. | ||
| */ | ||
| fun generatePopup(request: PassPopupRequest): String | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| package hs.kr.entrydsm.user.domain.auth.application.port.`in` | ||
|
|
||
| import hs.kr.entrydsm.user.domain.auth.adapter.`in`.web.dto.resopnse.QueryPassInfoResponse | ||
|
|
||
| /** | ||
| * 패스 인증 정보 조회 기능을 정의하는 UseCase 인터페이스입니다. | ||
| */ | ||
| interface QueryPassInfoUseCase { | ||
| /** | ||
| * 토큰을 이용하여 패스 인증 정보를 조회합니다. | ||
| */ | ||
| fun queryPassInfo(token: String?): QueryPassInfoResponse | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,113 @@ | ||
| package hs.kr.entrydsm.user.domain.auth.application.service | ||
|
|
||
| import hs.kr.entrydsm.user.domain.auth.adapter.`in`.web.dto.request.PassPopupRequest | ||
| import hs.kr.entrydsm.user.domain.auth.application.port.`in`.PassPopupUseCase | ||
| import hs.kr.entrydsm.user.global.exception.InternalServerErrorException | ||
| import hs.kr.entrydsm.user.global.utils.pass.RedirectUrlChecker | ||
| import kcb.module.v3.OkCert | ||
| import org.json.JSONObject | ||
| import org.springframework.beans.factory.annotation.Value | ||
| import org.springframework.stereotype.Service | ||
| import org.springframework.transaction.annotation.Transactional | ||
|
|
||
| /** | ||
| * 패스 팝업 생성 비즈니스 로직을 처리하는 서비스 클래스입니다. | ||
| */ | ||
| @Service | ||
| class PassPopupService( | ||
| private val redirectUrlChecker: RedirectUrlChecker, | ||
| ) : PassPopupUseCase { | ||
| companion object { | ||
| private const val TARGET = "PROD" | ||
| } | ||
|
|
||
| @Value("\${pass.site-name}") | ||
| private lateinit var siteName: String | ||
|
|
||
| @Value("\${pass.site-url}") | ||
| private lateinit var siteUrl: String | ||
|
|
||
| @Value("\${pass.popup-url}") | ||
| private lateinit var popupUrl: String | ||
|
|
||
| @Value("\${pass.cp-cd}") | ||
| private lateinit var cpCd: String | ||
|
|
||
| @Value("\${pass.license}") | ||
| private lateinit var license: String | ||
|
|
||
| private val svcName = "IDS_HS_POPUP_START" | ||
|
|
||
| private val rqstCausCd = "00" | ||
|
|
||
| /** | ||
| * 패스 인증 팝업 HTML을 생성합니다. | ||
| */ | ||
| @Transactional | ||
| override fun generatePopup(passPopupRequest: PassPopupRequest): String { | ||
| redirectUrlChecker.checkRedirectUrl(passPopupRequest.redirectUrl) | ||
| try { | ||
| val reqJson = JSONObject() | ||
| reqJson.put("RETURN_URL", passPopupRequest.redirectUrl) | ||
| reqJson.put("SITE_NAME", siteName) | ||
| reqJson.put("SITE_URL", siteUrl) | ||
| reqJson.put("RQST_CAUS_CD", rqstCausCd) | ||
|
|
||
| val reqStr: String = reqJson.toString() | ||
|
|
||
| val okcert = OkCert() | ||
|
|
||
| val resultStr: String = okcert.callOkCert(TARGET, cpCd, svcName, license, reqStr) | ||
|
|
||
| val resJson = JSONObject(resultStr) | ||
|
|
||
| val rsltCd: String = resJson.getString("RSLT_CD") | ||
| val rsltMsg: String = resJson.getString("RSLT_MSG") | ||
| var mdlTkn = "" | ||
|
|
||
| var succ = false | ||
|
|
||
| if ("B000" == rsltCd && resJson.has("MDL_TKN")) { | ||
| mdlTkn = resJson.getString("MDL_TKN") | ||
| succ = true | ||
| } | ||
|
|
||
| val htmlBuilder = StringBuilder() | ||
| htmlBuilder.append("<html>") | ||
| htmlBuilder.append("<title>KCB 휴대폰 본인확인 서비스 샘플 2</title>") | ||
| htmlBuilder.append("<head>") | ||
| htmlBuilder.append("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=euc-kr\">") | ||
| htmlBuilder.append("<script type=\"text/javascript\">") | ||
| htmlBuilder.append("function request(){") | ||
| htmlBuilder.append("document.form1.action = \"$popupUrl\";") | ||
| htmlBuilder.append("document.form1.method = \"post\";") | ||
| htmlBuilder.append("document.form1.submit();") | ||
| htmlBuilder.append("}") | ||
| htmlBuilder.append("</script>") | ||
| htmlBuilder.append("</head>") | ||
| htmlBuilder.append("<body>") | ||
| htmlBuilder.append("<form name=\"form1\">") | ||
| htmlBuilder.append( | ||
| "<input type=\"hidden\" name=\"tc\" value=\"kcb.oknm.online.safehscert.popup.cmd.P931_CertChoiceCmd\"/>", | ||
| ) | ||
| htmlBuilder.append("<input type=\"hidden\" name=\"cp_cd\" value=\"$cpCd\"/>") | ||
| htmlBuilder.append("<input type=\"hidden\" name=\"mdl_tkn\" value=\"$mdlTkn\"/>") | ||
| htmlBuilder.append("<input type=\"hidden\" name=\"target_id\" value=\"\"/>") | ||
| htmlBuilder.append("</form>") | ||
| htmlBuilder.append("</body>") | ||
| htmlBuilder.append("<script>") | ||
| if (succ) { | ||
| htmlBuilder.append("request();") | ||
| } else { | ||
| htmlBuilder.append("alert('$rsltCd : $rsltMsg'); self.close();") | ||
| } | ||
| htmlBuilder.append("</script>") | ||
| htmlBuilder.append("</html>") | ||
|
|
||
| return htmlBuilder.toString() | ||
| } catch (e: Exception) { | ||
| println(e.message) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. println 말고 로거 사용이 적절해 보이는데 확인부탁드립니다 @qkrwndnjs1075
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. bbf106f 수정했습니다 |
||
| throw InternalServerErrorException | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| package hs.kr.entrydsm.user.domain.refreshtoken.adapter.out | ||
|
|
||
| import org.springframework.data.annotation.Id | ||
| import org.springframework.data.redis.core.RedisHash | ||
| import org.springframework.data.redis.core.TimeToLive | ||
| import org.springframework.data.redis.core.index.Indexed | ||
|
|
||
| /** | ||
| * Redis에 저장되는 리프레시 토큰 정보를 나타내는 클래스입니다. | ||
| * JWT 리프레시 토큰을 관리하여 토큰 갱신을 처리합니다. | ||
| * | ||
| * @property id 사용자 ID (Redis 키로 사용) | ||
| * @property token 리프레시 토큰 값 | ||
| * @property ttl Time To Live (토큰 만료 시간, 초 단위) | ||
| */ | ||
| @RedisHash | ||
| class RefreshToken( | ||
| @Id | ||
| val id: String, | ||
| @Indexed | ||
| var token: String, | ||
| @TimeToLive | ||
| var ttl: Long, | ||
| ) { | ||
| /** | ||
| * 리프레시 토큰과 TTL을 업데이트합니다. | ||
| * | ||
| * @param token 새로운 리프레시 토큰 | ||
| * @param ttl 새로운 만료 시간 (초) | ||
| */ | ||
| fun update( | ||
| token: String?, | ||
| ttl: Long, | ||
| ) { | ||
| this.token = token!! | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. null이 들어왔을때 대응 로직이 있을까요 ? @qkrwndnjs1075
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. non-null 캐스트 제거 했습니다. ( 과거의 유산 ) |
||
| this.ttl = ttl | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| package hs.kr.entrydsm.user.domain.refreshtoken.adapter.out.repository | ||
|
|
||
| import hs.kr.entrydsm.user.domain.refreshtoken.adapter.out.RefreshToken | ||
| import org.springframework.data.repository.CrudRepository | ||
|
|
||
| /** | ||
| * 리프레시 토큰에 대한 Redis 액세스를 담당하는 리포지토리 인터페이스입니다. | ||
| */ | ||
| interface RefreshTokenRepository : CrudRepository<RefreshToken, String> { | ||
| /** | ||
| * 토큰 값으로 리프레시 토큰을 조회합니다. | ||
| */ | ||
| fun findByToken(token: String): RefreshToken? | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
패키지명 resopnse 오타입니다. response로 수정하세요 @qkrwndnjs1075
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
수정했습니다