TLS 란
TLS(Transport Layer Security) 는 인터넷에서 데이터를 안전하게 암호화하여 전송하는 보안 프로토콜
서버 및 클라이언트의 신원을 인증하는 보안 프로토콜이다
이 글에선 단방향 인증 방식을 설명 한다
인증서 생성
1️⃣ root (최상위 인증기관) 서명을 만든다
2️⃣ root 서명을 가지고 필요한 사용자의 공개키와 비공개 키를 만든다
3️⃣ 사용자는 공개키와 비공개키를 가지고 서버에 요청을 한다
root 인증서 생성
openssl genrsa -out rootCA.key 4096
openssl req -x509 -new -nodes -key rootCA.key -sha256 -days 3650 -out rootCA.crt -subj "/C=KR/ST=Seoul/L=Gangnam/O=MyCompany/OU=Dev/CN=MyRootCA"
서버용 인증서 생성
openssl genrsa -out server.key 2048
openssl req -new -key server.key -out server.csr -subj "/C=KR/ST=Seoul/L=Gangnam/O=MyCompany/OU=Dev/CN=myserver.com"
서버용 인증서 서명
openssl x509 -req -in server.csr -CA rootCA.crt -CAkey rootCA.key -CAcreateserial -out server.crt -days 365 -sha256
서버 인증서로 키스토어 생성 (.p12)
openssl pkcs12 -export -in server.crt -inkey server.key -out keystore.p12 -name myserver -CAfile rootCA.crt -caname rootCA -passout pass:password
서버가 신뢰하는 CA 저장
keytool -import -trustcacerts -alias rootCA -file rootCA.crt -keystore truststore.p12 -storepass password -noprompt
유저용(blue) 인증서 생성방법 ? -> 서버키 생성하는것과 같이 생성하고 rootCA 로 서명 해준다.
keystore 역할
HTTPS + mTLS 인증 (보안 강화)
truststore 역할
truststore를 만들때 사용한 root인증서를 비교해서 정상적인 인증서인지 비교
Spring Boot 에 적용하기
#application.properties
server:
port: 8443
ssl:
enabled: true
key-store: classpath:keystore.p12
key-store-password: password
key-store-type: PKCS12
key-alias: myserver
client-auth: need # ✅ 클라이언트 인증 필수
trust-store: classpath:truststore.p12 # ✅ 신뢰할 수 있는 CA 저장소
trust-store-password: password
@RestController
@RequestMapping
class AuthProxyController(
private val proxyService: AuthProxyService
) {
val httpClient: CloseableHttpClient = HttpClients.createDefault()
private val echoServerUrl= "http://localhost:8081/echo"
@GetMapping("/abcd")
@Throws(IOException::class)
fun proxyRequest(@RequestParam("param") param: String, request: HttpServletRequest): String {
proxyService.validateClientCertificate(request)
return try {
//인증서버에서 인증이 완료되면 들어온 body / param 을 포워딩 해준다
val httpGet = HttpGet("$echoServerUrl?param=$param")
val response = httpClient.execute(httpGet)
response.entity.content.bufferedReader().use { it.readText() }
} catch (e: IOException) {
"Error forwarding request: ${e.message}"
}
}
}
@Service
class AuthProxyService {
fun validateClientCertificate(request: HttpServletRequest) {
val certs = request.getAttribute("jakarta.servlet.request.X509Certificate") as? Array<X509Certificate>
?: throw SecurityException("클라이언트 인증서가 없습니다.")
val clientCert = certs.firstOrNull() ?: throw SecurityException("클라이언트 인증서가 없습니다.")
if (!isAllowedUser(clientCert)) {
throw SecurityException("접속이 허용되지 않았습니다.")
}
}
private fun isAllowedUser(cert: X509Certificate): Boolean {
val clientName = cert.subjectX500Principal?.name ?: return false
return AllowedUser.from(clientName).isAllowed()
}
}
wget --certificate=./blue.crt --private-key=./blue.key --ca-certificate=rootCA.crt \
--method=GET \
-O - "https://localhost:8443/abcd?param=Hello World"
TLS 인증 방식
1️⃣ 클라이언트 → 서버: "내 인증서(blue.crt)는 이거야!"
2️⃣ 서버: "이 인증서(blue.crt)가 신뢰할 수 있는 CA(rootCA.crt)에서 발급된 건지 확인할게!"
3️⃣ 서버가 blue.crt를 검증 ✅
4️⃣ 서버 → 클라이언트: "네가 진짜 blue.crt의 주인 맞아? 증명해 봐!"
5️⃣ 클라이언트: 개인 키(blue.key)로 서명한 메시지를 생성 ✍️
6️⃣ 클라이언트 → 서버: "여기 서명된 메시지야!" (🔹개인 키 자체가 아닌, 개인 키로 서명한 데이터만 전송)
7️⃣ 서버: blue.crt(공개 키)로 서명을 검증 ✅
8️⃣ 검증 완료 → 클라이언트 인증 성공 → 데이터를 특정 서버로 포워딩 시키기
'개-발 > Infra' 카테고리의 다른 글
[Infra] pub/sub 사용시 주의사항 (1) | 2024.12.21 |
---|---|
[Infra] 데이터의 멱등성 (0) | 2024.12.13 |
[nGrinder] 내 서버는 어느정도 까지 버틸까 (stress Test) (0) | 2024.11.21 |
[nGrinder] multipart-form 스크립트 작성하기 (1) | 2024.11.19 |
[Infra] ngrinder 부하 테스트 도구 (1) | 2024.11.18 |
TLS 란
TLS(Transport Layer Security) 는 인터넷에서 데이터를 안전하게 암호화하여 전송하는 보안 프로토콜
서버 및 클라이언트의 신원을 인증하는 보안 프로토콜이다
이 글에선 단방향 인증 방식을 설명 한다
인증서 생성
1️⃣ root (최상위 인증기관) 서명을 만든다
2️⃣ root 서명을 가지고 필요한 사용자의 공개키와 비공개 키를 만든다
3️⃣ 사용자는 공개키와 비공개키를 가지고 서버에 요청을 한다
root 인증서 생성
openssl genrsa -out rootCA.key 4096
openssl req -x509 -new -nodes -key rootCA.key -sha256 -days 3650 -out rootCA.crt -subj "/C=KR/ST=Seoul/L=Gangnam/O=MyCompany/OU=Dev/CN=MyRootCA"
서버용 인증서 생성
openssl genrsa -out server.key 2048
openssl req -new -key server.key -out server.csr -subj "/C=KR/ST=Seoul/L=Gangnam/O=MyCompany/OU=Dev/CN=myserver.com"
서버용 인증서 서명
openssl x509 -req -in server.csr -CA rootCA.crt -CAkey rootCA.key -CAcreateserial -out server.crt -days 365 -sha256
서버 인증서로 키스토어 생성 (.p12)
openssl pkcs12 -export -in server.crt -inkey server.key -out keystore.p12 -name myserver -CAfile rootCA.crt -caname rootCA -passout pass:password
서버가 신뢰하는 CA 저장
keytool -import -trustcacerts -alias rootCA -file rootCA.crt -keystore truststore.p12 -storepass password -noprompt
유저용(blue) 인증서 생성방법 ? -> 서버키 생성하는것과 같이 생성하고 rootCA 로 서명 해준다.
keystore 역할
HTTPS + mTLS 인증 (보안 강화)
truststore 역할
truststore를 만들때 사용한 root인증서를 비교해서 정상적인 인증서인지 비교
Spring Boot 에 적용하기
#application.properties
server:
port: 8443
ssl:
enabled: true
key-store: classpath:keystore.p12
key-store-password: password
key-store-type: PKCS12
key-alias: myserver
client-auth: need # ✅ 클라이언트 인증 필수
trust-store: classpath:truststore.p12 # ✅ 신뢰할 수 있는 CA 저장소
trust-store-password: password
@RestController
@RequestMapping
class AuthProxyController(
private val proxyService: AuthProxyService
) {
val httpClient: CloseableHttpClient = HttpClients.createDefault()
private val echoServerUrl= "http://localhost:8081/echo"
@GetMapping("/abcd")
@Throws(IOException::class)
fun proxyRequest(@RequestParam("param") param: String, request: HttpServletRequest): String {
proxyService.validateClientCertificate(request)
return try {
//인증서버에서 인증이 완료되면 들어온 body / param 을 포워딩 해준다
val httpGet = HttpGet("$echoServerUrl?param=$param")
val response = httpClient.execute(httpGet)
response.entity.content.bufferedReader().use { it.readText() }
} catch (e: IOException) {
"Error forwarding request: ${e.message}"
}
}
}
@Service
class AuthProxyService {
fun validateClientCertificate(request: HttpServletRequest) {
val certs = request.getAttribute("jakarta.servlet.request.X509Certificate") as? Array<X509Certificate>
?: throw SecurityException("클라이언트 인증서가 없습니다.")
val clientCert = certs.firstOrNull() ?: throw SecurityException("클라이언트 인증서가 없습니다.")
if (!isAllowedUser(clientCert)) {
throw SecurityException("접속이 허용되지 않았습니다.")
}
}
private fun isAllowedUser(cert: X509Certificate): Boolean {
val clientName = cert.subjectX500Principal?.name ?: return false
return AllowedUser.from(clientName).isAllowed()
}
}
wget --certificate=./blue.crt --private-key=./blue.key --ca-certificate=rootCA.crt \
--method=GET \
-O - "https://localhost:8443/abcd?param=Hello World"
TLS 인증 방식
1️⃣ 클라이언트 → 서버: "내 인증서(blue.crt)는 이거야!"
2️⃣ 서버: "이 인증서(blue.crt)가 신뢰할 수 있는 CA(rootCA.crt)에서 발급된 건지 확인할게!"
3️⃣ 서버가 blue.crt를 검증 ✅
4️⃣ 서버 → 클라이언트: "네가 진짜 blue.crt의 주인 맞아? 증명해 봐!"
5️⃣ 클라이언트: 개인 키(blue.key)로 서명한 메시지를 생성 ✍️
6️⃣ 클라이언트 → 서버: "여기 서명된 메시지야!" (🔹개인 키 자체가 아닌, 개인 키로 서명한 데이터만 전송)
7️⃣ 서버: blue.crt(공개 키)로 서명을 검증 ✅
8️⃣ 검증 완료 → 클라이언트 인증 성공 → 데이터를 특정 서버로 포워딩 시키기
'개-발 > Infra' 카테고리의 다른 글
[Infra] pub/sub 사용시 주의사항 (1) | 2024.12.21 |
---|---|
[Infra] 데이터의 멱등성 (0) | 2024.12.13 |
[nGrinder] 내 서버는 어느정도 까지 버틸까 (stress Test) (0) | 2024.11.21 |
[nGrinder] multipart-form 스크립트 작성하기 (1) | 2024.11.19 |
[Infra] ngrinder 부하 테스트 도구 (1) | 2024.11.18 |