| name | auth |
| description | Use when working on JWT tokens, Kakao/Apple OAuth flows, `@UserId` custom annotation + `UserIdArgumentResolver`, `SecurityConfig`, `JwtAuthenticationFilter`, or login/logout/withdraw/refresh endpoints. Never use `@AuthenticationPrincipal` directly โ always `@UserId`. Trigger on any auth-related controller, service, or filter. |
Auth Patterns
ํต์ฌ ์์น
@AuthenticationPrincipal์ ์ ๋ ์ง์ ์ฌ์ฉํ์ง ์๋๋ค. ํญ์ @UserId ์ปค์คํ
์ด๋
ธํ
์ด์
์ ์ฌ์ฉํ๋ค.
- Auth ๊ด๋ จ ์์ธ๋ ๋ฐ๋์
AuthException(ErrorCode.XXX) ํํ๋ก ๋์ง๋ค.
- OAuth ์ธ๋ถ API ํธ์ถ์
ssolv-infrastructure ๋ ์ด์ด์ Ktor Client์์ ์ํํ๋ค.
@UserId ์ด๋
ธํ
์ด์
ssolv-api-common: org.depromeet.team3.common.annotation.UserId
suspend fun getProfile(@UserId userId: Long): DpmApiResponse<ProfileResponse>
suspend fun getPublicData(@UserId userId: Long?): DpmApiResponse<PublicResponse>
suspend fun getMeeting(@MeetingId meetingId: Long): DpmApiResponse<MeetingResponse>
๋ด๋ถ ๋์: JwtAuthenticationToken์์ principal(userId: Long?)์ ์ถ์ถ.
SecurityContextHolder โ JwtAuthenticationToken.getUserId() ๊ฒฝ๋ก.
JWT ํ ํฐ ๊ตฌ์กฐ
accessToken โ Authorization: Bearer {token}
refreshToken โ ์์ฒญ ๋ฐ๋ (RefreshTokenRequest.refreshToken)
ํ ํฐ ๊ฐฑ์ ์๋ํฌ์ธํธ: POST /api/v1/auth/reissue-token
์์
๋ก๊ทธ์ธ ํ๋ฆ
Kakao
ํ๋ก ํธ์๋ โ ์นด์นด์ค ์ธ๊ฐ์ฝ๋ ํ๋
โ GET /api/v1/auth/kakao-login?code={code}&redirect_uri={uri}
โ KakaoLoginService.login(KakaoLoginCommand)
โ KakaoOAuthClient (Ktor) โ ์นด์นด์ค ํ ํฐ ๊ตํ โ ํ๋กํ ์กฐํ
โ ์ฌ์ฉ์ ์ฐพ๊ธฐ/์์ฑ โ JWT ๋ฐ๊ธ โ LoginResponse ๋ฐํ
Apple
ํ๋ก ํธ์๋ โ ์ ํ ์ธ๊ฐ์ฝ๋ + user(JSON) ํ๋
โ POST /api/v1/auth/apple-login?code={code}&user={userJson}
โ AppleOAuthService.login(AppleLoginCommand)
โ AppleOAuthClient (Ktor) โ JWT ๊ฒ์ฆ (JWKS) โ ์ฌ์ฉ์ ์ ๋ณด ํ์ฑ
โ ์ฌ์ฉ์ ์ฐพ๊ธฐ/์์ฑ โ JWT ๋ฐ๊ธ โ LoginResponse ๋ฐํ
์ฃผ์: user ํ๋ผ๋ฏธํฐ๋ ์ต์ด ๋ก๊ทธ์ธ ์์๋ง ์ ํ์ด ์ ๋ฌํ๋ค. ์ฌ๋ก๊ทธ์ธ ์ null.
ErrorCode ๋ฒ์ (OAuth ๋๋ฉ์ธ)
| ์ฝ๋ | ์๋ฏธ |
|---|
| O001 | ์นด์นด์ค ์ธ๊ฐ์ฝ๋ ๊ตํ ์คํจ |
| O002 | ์นด์นด์ค ์ฌ์ฉ์ ์ ๋ณด ์กฐํ ์คํจ |
| O007 | ๋ค๋ฅธ ์์
์๋จ์ผ๋ก ์ด๋ฏธ ๊ฐ์
๋ ์ด๋ฉ์ผ (409 Conflict) |
| O011 | ์ ํ JWT ์๋ช
๊ฒ์ฆ ์คํจ |
| O012 | ์ ํ JWKS ์กฐํ ์คํจ |
| O018 | ์ ํ ์ธ๊ฐ์ฝ๋ ๋ง๋ฃ |
| O019 | ์ ํ sub ๋ถ์ผ์น |
๋ก๊ทธ์์ / ํํด ํจํด
suspend fun logout(@UserId userId: Long): DpmApiResponse<LogoutResponse>
suspend fun withdraw(@UserId userId: Long): DpmApiResponse<Unit>
ํ
์คํธ์์ ์ธ์ฆ ์ฒ๋ฆฌ
์ปจํธ๋กค๋ฌ ๋จ์ ํ
์คํธ์์ @UserId๋ฅผ ์ฃผ์
ํ๋ ค๋ฉด TestUserIdArgumentResolver๋ฅผ ๋ฑ๋กํ๋ค.
private val testUserIdResolver = TestUserIdArgumentResolver()
private val mockMvc: MockMvc by lazy {
MockMvcBuilders.standaloneSetup(controller)
.setCustomArgumentResolvers(testUserIdResolver)
.build()
}
@Test
fun `์ธ์ฆ๋ ์ฌ์ฉ์ ์์ฒญ`() = runTest {
testUserIdResolver.setTestUserId(1L)
}
@Test
fun `์ธ์ฆ ์๋ ์์ฒญ`() = runTest {
testUserIdResolver.setTestUserId(null)
}
ํตํฉ ํ
์คํธ์์๋ TestSecurityConfig๊ฐ @IntegrationTest์ ํฌํจ๋์ด ์์ด ๋ณ๋ ์ค์ ๋ถํ์.
๋ฐ๋ชจ ๋ก๊ทธ์ธ (์ฑ์คํ ์ด ์ฌ์ฌ์ฉ)
POST /api/v1/auth/demo-login
Swagger ๋ฌธ์ํ ์ฒดํฌ๋ฆฌ์คํธ
์ธ์ฆ ๊ด๋ จ ์ ๊ท ์๋ํฌ์ธํธ ์ถ๊ฐ ์: