正如this answer 中已经提到的,我在SecurityContext 中写了a lib to ease unit tests 和KeycloakAuthenticationToken。
您可以从此处浏览带有@Controller 和@Service 单元测试的完整示例应用程序:https://github.com/ch4mpy/spring-addons/tree/master/spring-security-oauth2-test-webmvc-addons/src/test/java/com/c4_soft/springaddons/samples/webmvc/keycloak
KeycloakMessageServiceTest:
@RunWith(SpringRunner.class)
@Import(KeycloakMessageServiceTest.TestConfig.class)
public class KeycloakMessageServiceTest {
@Autowired
MessageService service;
@Test(expected = AccessDeniedException.class)
@WithMockKeycloakAuth(authorities = "USER", oidc = @OidcStandardClaims(preferredUsername = "ch4mpy"))
public void whenAuthenticatedWithoutAuthorizedPersonnelThenCanNotGetSecret() {
service.getSecret();
}
@Test()
@WithMockKeycloakAuth(
authorities = "AUTHORIZED_PERSONNEL",
oidc = @OidcStandardClaims(preferredUsername = "ch4mpy"))
public void whenAuthenticatedWitAuthorizedPersonnelThenGetSecret() {
final var actual = service.getSecret();
assertEquals("Secret message", actual);
}
@Test(expected = Exception.class)
public void whenNotAuthenticatedThenCanNotGetGreeting() {
service.greet(null);
}
@Test()
@WithMockKeycloakAuth(
authorities = "AUTHORIZED_PERSONNEL",
oidc = @OidcStandardClaims(preferredUsername = "ch4mpy"))
public void whenAuthenticatedThenGetGreeting() {
final var actual =
service.greet((KeycloakAuthenticationToken) SecurityContextHolder.getContext().getAuthentication());
assertEquals("Hello ch4mpy! You are granted with [AUTHORIZED_PERSONNEL].", actual);
}
@TestConfiguration
@EnableGlobalMethodSecurity(prePostEnabled = true)
@Import({ KeycloakMessageService.class })
public static class TestConfig {
@Bean
public GrantedAuthoritiesMapper authoritiesMapper() {
return new NullAuthoritiesMapper();
}
}
}
GreetingControllerAnnotatedTest:
@RunWith(SpringRunner.class)
@WebMvcTest(controllers = GreetingController.class)
@Import({
ServletKeycloakAuthUnitTestingSupport.UnitTestConfig.class,
KeycloakSpringBootSampleApp.KeycloakConfig.class })
// because this sample stands in the middle of non spring-boot-keycloak projects, keycloakproperties are isolated in
// application-keycloak.properties
@ActiveProfiles("keycloak")
public class GreetingControllerAnnotatedTest {
private static final String GREETING = "Hello %s! You are granted with %s.";
@MockBean
MessageService messageService;
@MockBean
JwtDecoder jwtDecoder;
@Autowired
MockMvcSupport api;
@Before
public void setUp() {
when(messageService.greet(any())).thenAnswer(invocation -> {
final var auth = invocation.getArgument(0, Authentication.class);
return String.format(GREETING, auth.getName(), auth.getAuthorities());
});
}
@Test
@WithMockKeycloakAuth(
authorities = { "USER", "AUTHORIZED_PERSONNEL" },
id = @IdTokenClaims(sub = "42"),
oidc = @OidcStandardClaims(
email = "ch4mp@c4-soft.com",
emailVerified = true,
nickName = "Tonton-Pirate",
preferredUsername = "ch4mpy"),
accessToken = @KeycloakAccessToken(
realmAccess = @KeycloakAccess(roles = { "TESTER" }),
authorization = @KeycloakAuthorization(
permissions = @KeycloakPermission(rsid = "toto", rsname = "truc", scopes = "abracadabra"))),
privateClaims = @ClaimSet(stringClaims = @StringClaim(name = "foo", value = "bar")))
public void whenAuthenticatedWithKeycloakAuthenticationTokenThenCanGreet() throws Exception {
api.get("/greet")
.andExpect(status().isOk())
.andExpect(content().string(startsWith("Hello ch4mpy! You are granted with ")))
.andExpect(content().string(containsString("AUTHORIZED_PERSONNEL")))
.andExpect(content().string(containsString("USER")))
.andExpect(content().string(containsString("TESTER")));
}
@Test
@WithMockKeycloakAuth(authorities = { "USER" }, oidc = @OidcStandardClaims(preferredUsername = "ch4mpy"))
public void whenAuthenticatedWithoutAuthorizedPersonnelThenSecuredRouteIsForbidden() throws Exception {
api.get("/secured-route").andExpect(status().isForbidden());
}
@Test
@WithMockKeycloakAuth(
authorities = { "AUTHORIZED_PERSONNEL" },
oidc = @OidcStandardClaims(preferredUsername = "ch4mpy"))
public void whenAuthenticatedWithAuthorizedPersonnelThenSecuredRouteIsOk() throws Exception {
api.get("/secured-route").andExpect(status().isOk());
}
}