-
Notifications
You must be signed in to change notification settings - Fork 0
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
feat(snackgame.biz): 게임 결과를 서명한다 #179
Merged
Merged
Changes from 1 commit
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
891a8a2
feat(sign): PKCS#8 PEM 키를 PrivateKey로 변환한다
0chil f90c01c
feat(sign): Json을 개인키로 서명한다
0chil 9ed3705
feat(sign): 어노테이션을 사용해 응답을 서명한다
0chil 9448e0b
feat(snackgame.biz): 게임 결과를 서명한다
0chil d671dee
chore: 게임 서명 개인키 추가
0chil 6e0049b
feat(Signer): JWT에 keyId를 포함한다
0chil File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
18 changes: 18 additions & 0 deletions
18
src/main/java/com/snackgame/server/game/sign/config/SignerConfig.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
package com.snackgame.server.game.sign.config | ||
|
||
import com.snackgame.server.game.sign.domain.PKCS8PEMKey | ||
import com.snackgame.server.game.sign.domain.Signer | ||
import org.springframework.beans.factory.annotation.Value | ||
import org.springframework.context.annotation.Bean | ||
import org.springframework.context.annotation.Configuration | ||
|
||
@Configuration | ||
class SignerConfig( | ||
@Value("\${game.signing.private-key-pem}") | ||
privateKeyPem: String | ||
) { | ||
private val privateKey = PKCS8PEMKey(privateKeyPem).toPrivateKey() | ||
|
||
@Bean | ||
fun signer() = Signer(privateKey) | ||
} |
37 changes: 37 additions & 0 deletions
37
src/main/java/com/snackgame/server/game/sign/service/ResponseSigner.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
package com.snackgame.server.game.sign.service | ||
|
||
import com.fasterxml.jackson.databind.ObjectMapper | ||
import com.snackgame.server.game.sign.domain.Signer | ||
import com.snackgame.server.game.sign.service.dto.SignedResponse | ||
import org.springframework.core.MethodParameter | ||
import org.springframework.http.MediaType | ||
import org.springframework.http.converter.HttpMessageConverter | ||
import org.springframework.http.server.ServerHttpRequest | ||
import org.springframework.http.server.ServerHttpResponse | ||
import org.springframework.web.bind.annotation.RestControllerAdvice | ||
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice | ||
|
||
@RestControllerAdvice | ||
class ResponseSigner( | ||
val signer: Signer, | ||
val objectMapper: ObjectMapper | ||
) : ResponseBodyAdvice<Any> { | ||
|
||
override fun supports(returnType: MethodParameter, converterType: Class<out HttpMessageConverter<*>>): Boolean { | ||
return returnType.hasMethodAnnotation(Signed::class.java) | ||
} | ||
|
||
override fun beforeBodyWrite( | ||
body: Any?, | ||
returnType: MethodParameter, | ||
selectedContentType: MediaType, | ||
selectedConverterType: Class<out HttpMessageConverter<*>>, | ||
request: ServerHttpRequest, | ||
response: ServerHttpResponse | ||
): Any? { | ||
return body?.let { | ||
val jsonContent = objectMapper.writeValueAsString(it) | ||
SignedResponse(it, signer.sign(jsonContent)) | ||
} | ||
} | ||
} |
5 changes: 5 additions & 0 deletions
5
src/main/java/com/snackgame/server/game/sign/service/Signed.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
package com.snackgame.server.game.sign.service | ||
|
||
@Retention(AnnotationRetention.RUNTIME) | ||
@Target(AnnotationTarget.FUNCTION) | ||
internal annotation class Signed |
6 changes: 6 additions & 0 deletions
6
src/main/java/com/snackgame/server/game/sign/service/dto/SignedResponse.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
package com.snackgame.server.game.sign.service.dto | ||
|
||
data class SignedResponse( | ||
val original: Any, | ||
val signed: String | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
47 changes: 47 additions & 0 deletions
47
src/test/java/com/snackgame/server/game/sign/service/ResponseSignerTest.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
@file:Suppress("NonAsciiCharacters") | ||
|
||
package com.snackgame.server.game.sign.service | ||
|
||
import com.snackgame.server.support.restassured.RestAssuredTest | ||
import io.restassured.RestAssured | ||
import org.assertj.core.api.Assertions.assertThat | ||
import org.junit.jupiter.api.Test | ||
import org.springframework.boot.test.context.TestConfiguration | ||
import org.springframework.web.bind.annotation.GetMapping | ||
import org.springframework.web.bind.annotation.RestController | ||
|
||
@RestAssuredTest | ||
class ResponseSignerTest { | ||
|
||
@TestConfiguration | ||
class TestConfig { | ||
|
||
@RestController | ||
class TestController { | ||
|
||
@Signed | ||
@GetMapping("/test") | ||
fun test(): Any { | ||
data class TestObject(val key: String) | ||
return TestObject("value") | ||
} | ||
} | ||
} | ||
|
||
@Test | ||
fun `Signed 어노테이션이 붙은 핸들러의 응답을 서명한다`() { | ||
val responseBody = RestAssured.given() | ||
.`when`().get("/test") | ||
.then().extract().body().asString() | ||
|
||
assertThat(responseBody).contains( | ||
""" | ||
"signed":"eyJhbGciOiJSUzI1NiJ9.eyJrZXkiOiJ2YWx1ZSJ9.Jrg2SkMT2c3LIMR_pIKmdEF2-3UyH1r8nS- | ||
q43l5KmKN1hQve4EG18JD5btvGMfZEZu2vjwRmgP-ybhKBNt0-khZ8zBGh3YQ4FILkMETyS-7ObJD | ||
6N_737BjVa0iMg7MKb6_enoTDJ_p-6_murR7Y1ujSGrGJWSvzdO9FooQTetc7LQorQXUhjk5Wm-ggQ | ||
rMnTSTYlNPaSuXrtdVzB3jbMbC0jK-rcr0-sntFCvC2WCC_vRcr7aRZk72RVBI5EEDy5DW_kxIRrbT | ||
dUSglBx5YPkCec9XKZtGKpFUhgWkyfKjEUgYgfg6e6AEzJPMuX5L2V6LKwBdn4TSkh4u3pyk-g" | ||
""".replace("\\s+".toRegex(), "") | ||
) | ||
} | ||
} |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
ResponseBodyAdvice
가 공통된 응답을 위해 쓰인다고 하는데 지금처럼 DTO를 만들어서 하던 방법과 차이점이나ResponseBodyAdvice
를 쓰게 된 이유가 있을까요??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.
선요약: 최소한의 접점만으로 문제를 가장 직접적으로 해결하는 방법이다
이것도 풀자면 긴 스토리긴 한데요.
우선 목적은 최대한 기존 코드를 건드리지 않고 반환값을 서명하는 것입니다. (서명에 대한 관심사 분리)
물론 멋있다는 점도 한 몫 합니다 ㅋ.ㅋ원래 HandlerMethodReturnValueHandler 혹은 HandlerInterceptor를 사용하려고 했습니다. (둘 다 실패)
전자는
@ResponseBody
에 대한 ReturnValueHandler가 이미 있기 때문에 제가 새로 추가한 핸들러가 실행되지 않아서 실패했구용후자는 HttpServletResponse의 body를 조작해야 하는데, 이 body는 이미 String이 된 상태이므로 조작이 조금 복잡해지며 부작용의 우려가 있습니다.
이것보다 더 직접적인 방법(ResponseBodyAdvice)가 있어 이걸 선택했습니다.
ResponseBodyAdvice 인터페이스는 이름처럼
@ResponseBody
를 처리하는데 참고자료의 역할을 할 수 있습니다. (Spring API 문서)말씀해주신 목적으로도 사용하긴 하는 것 같은데,
특별히 '공통된 응답을 만드는데 사용하라' 같은 의도는 없는 것으로 보입니다.
그래서 앞서 말씀드린 목적으로 사용할 수 있다고 생각했구요,
이 친구의 작동방식을 고려해보면 - 실제로 MessageConverter가 바디를 변환하기 직전!에 작동하므로, 변환하고자 하는 값을 직접적으로(타입도 살아있는 상태로) 받고 제어할 수 있게 됩니다.
이 틈에 기존 객체에 서명하고 감쌈으로써 최소한의 접점만으로 원하는 목적을 달성하는 AOP를 할 수 있게 됩니다.
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.
역시 땡칠... 어노테이션만으로 처리할 수 있게 된게 인상적입니다..☺️
오늘도 하나배워갑니다