+ AP2 · Token-Mediating Backend
+
+ confidential backend가 authorization code를 token으로 교환합니다.
+ refresh token은 서버의 OAuth2AuthorizedClientService에만
+ 보관됩니다.
+
+
+
+
+
+
+
+
diff --git a/token-mediator/src/test/java/com/example/keycloakpattern/mediator/TokenBoundaryControllerTest.java b/token-mediator/src/test/java/com/example/keycloakpattern/mediator/TokenBoundaryControllerTest.java
new file mode 100644
index 0000000..86dd241
--- /dev/null
+++ b/token-mediator/src/test/java/com/example/keycloakpattern/mediator/TokenBoundaryControllerTest.java
@@ -0,0 +1,50 @@
+package com.example.keycloakpattern.mediator;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.oidcLogin;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
+import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService;
+import org.springframework.security.oauth2.core.OAuth2AccessToken;
+import org.springframework.security.oauth2.core.OAuth2RefreshToken;
+import org.springframework.test.context.bean.override.mockito.MockitoBean;
+import org.springframework.test.web.servlet.MockMvc;
+
+@SpringBootTest(properties = "KEYCLOAK_CLIENT_SECRET=test-only-secret")
+@AutoConfigureMockMvc
+class TokenBoundaryControllerTest {
+
+ @Autowired
+ private MockMvc mockMvc;
+
+ @MockitoBean
+ private OAuth2AuthorizedClientService authorizedClientService;
+
+ @Test
+ void reportsServerSideTokensWithoutReturningTheirValues() throws Exception {
+ OAuth2AuthorizedClient client = mock(OAuth2AuthorizedClient.class);
+ when(client.getAccessToken()).thenReturn(mock(OAuth2AccessToken.class));
+ when(client.getRefreshToken()).thenReturn(mock(OAuth2RefreshToken.class));
+ when(authorizedClientService.loadAuthorizedClient("keycloak", "test-subject"))
+ .thenReturn(client);
+
+ mockMvc.perform(get("/token/boundary").with(oidcLogin()
+ .idToken(token -> token.subject("test-subject"))))
+ .andExpect(status().isOk())
+ .andExpect(header().string("Cache-Control", "no-store"))
+ .andExpect(jsonPath("$.accessTokenStored").value(true))
+ .andExpect(jsonPath("$.refreshTokenStored").value(true))
+ .andExpect(jsonPath("$.browserReceivesRefreshToken").value(false))
+ .andExpect(jsonPath("$.access_token").doesNotExist())
+ .andExpect(jsonPath("$.refresh_token").doesNotExist());
+ }
+}