Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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,114 @@
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"
private val logger = LoggerFactory.getLogger(PassPopupService::class.java)
}

@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) {
logger.error("Pass popup 생성 중 오류 발생: ${e.message}", e)
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
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?
}