spring-security Spring Boot 钥匙罩综合试验

u7up0aaq  于 2022-11-11  发布在  Spring
关注(0)|答案(1)|浏览(115)

我正在使用keycloak来验证我的spring Boot 应用程序,如下所示:

@Configuration
public class CustomKeycloakSpringBootConfigResolver extends KeycloakSpringBootConfigResolver {
    private final KeycloakDeployment keycloakDeployment;
    CustomKeycloakSpringBootConfigResolver(KeycloakSpringBootProperties properties) {
        keycloakDeployment = KeycloakDeploymentBuilder.build(properties);
    }

    @Override
    public KeycloakDeployment resolve(HttpFacade.Request facade) {
        return keycloakDeployment;
    }

@KeycloakConfiguration
class KeycloakSecurityConfig extends KeycloakWebSecurityConfigurerAdapter {
    @Autowired
    void configureGlobal(AuthenticationManagerBuilder auth) {
        KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider();
        keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper());
        auth.authenticationProvider(keycloakAuthenticationProvider);
    }

    @Override
    @Bean
    protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
        return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl());
    }

    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/resources/**");
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        super.configure(http);
        http.authorizeRequests()
                .antMatchers("/account/**").hasRole("user")
                .anyRequest().permitAll().and()
                .csrf().disable();
    }

    @Bean
    @Override
    @ConditionalOnMissingBean(HttpSessionManager.class)
    protected HttpSessionManager httpSessionManager() {
        return new HttpSessionManager();
    }

}

我只需要使用mockmvc编写集成测试,它将测试无论何时访问安全资源,都将触发对keycloak的身份验证,并且在成功的身份验证之后返回资源。
有谁能建议如何实现这一点。

2fjabf4q

2fjabf4q1#

正如在this answer中已经提到的,我在SecurityContext中使用KeycloakAuthenticationToken编写了a lib to ease unit tests
您可以从这里浏览一些包含公寓测试的范例应用程序:https://github.com/ch4mpy/spring-addons/tree/master/samples
请注意,所有示例都是针对Keycloak服务器运行的,使用KeycloakSpring启动适配器库可能不是最佳选择:

  • 不符合Spring启动2.7+(仍扩展WebSecurityConfigurerAdapter)
  • 不符合WebFlux
  • 非常依赖Keycloak(您几乎无法切换到其他OIDC授权服务器,如Auth0、Microsoft Identity Server等)

KeycloakMessageServiceTest

@ExtendWith(SpringExtension.class)
@Import(MessageServiceTest.TestConfig.class)
class MessageServiceTest {

    @Autowired
    MessageService service;

    @WithMockKeycloakAuth(authorities = "USER", claims = @OpenIdClaims(preferredUsername = "ch4mpy"))
    void whenAuthenticatedWithoutAuthorizedPersonnelThenCanNotGetSecret() {
        assertThrows(AccessDeniedException.class, () -> service.getSecret());
    }

    @Test()
    @WithMockKeycloakAuth(authorities = "AUTHORIZED_PERSONNEL", claims = @OpenIdClaims(preferredUsername = "ch4mpy"))
    void whenAuthenticatedWitAuthorizedPersonnelThenGetSecret() {
        final String actual = service.getSecret();
        assertEquals("Secret message", actual);
    }

    @Test
    void whenNotAuthenticatedThenCanNotGetGreeting() {
        assertThrows(Exception.class, () -> service.greet(null));
    }

    @Test()
    @WithMockKeycloakAuth(authorities = "AUTHORIZED_PERSONNEL", claims = @OpenIdClaims(preferredUsername = "ch4mpy"))
    void whenAuthenticatedThenGetGreeting() {
        final String actual = service.greet((KeycloakAuthenticationToken) SecurityContextHolder.getContext().getAuthentication());
        assertEquals("Hello ch4mpy! You are granted with [AUTHORIZED_PERSONNEL].", actual);
    }

    @TestConfiguration(proxyBeanMethods = false)
    @EnableGlobalMethodSecurity(prePostEnabled = true)
    @Import({ MessageService.class })
    static class TestConfig {
        @Bean
        GrantedAuthoritiesMapper authoritiesMapper() {
            return new NullAuthoritiesMapper();
        }
    }
}

控制器测试如下所示:

@WebMvcTest(controllers = GreetingController.class)
class GreetingControllerAnnotatedTest {
    private static final String GREETING = "Hello %s! You are granted with %s.";

    @MockBean
    MessageService messageService;

    @MockBean
    JwtDecoder jwtDecoder;

    @Autowired
    MockMvc api;

    @BeforeEach
    void setUp() {
        when(messageService.greet(any())).thenAnswer(invocation -> {
            final var auth = invocation.getArgument(0, Authentication.class);
            return String.format(GREETING, auth.getName(), auth.getAuthorities());
        });
    }

    // @formatter:off
    @Test
    @WithMockKeycloakAuth(
            authorities = {"USER", "AUTHORIZED_PERSONNEL" },
            claims = @OpenIdClaims(
                    sub = "42",
                    jti = "123-456-789",
                    nbf = "2020-11-18T20:38:00Z",
                    sessionState = "987-654-321",
                    email = "ch4mp@c4-soft.com",
                    emailVerified = true,
                    nickName = "Tonton-Pirate",
                    preferredUsername = "ch4mpy",
                    otherClaims = @Claims(jsonObjectClaims = @JsonObjectClaim(name = "foo", value = OTHER_CLAIMS))),
            accessToken = @KeycloakAccessToken(
                    realmAccess = @KeycloakAccess(roles = { "TESTER" }),
                    authorization = @KeycloakAuthorization(permissions = @KeycloakPermission(rsid = "toto", rsname = "truc", scopes = "abracadabra")),
                    resourceAccess = {
                            @KeycloakResourceAccess(resourceId = "resourceA", access = @KeycloakAccess(roles = {"A_TESTER"})),
                            @KeycloakResourceAccess(resourceId = "resourceB", access = @KeycloakAccess(roles = {"B_TESTER"}))}))
    // @formatter:on
    void whenAuthenticatedWithKeycloakAuthenticationTokenThenCanGreet() throws Exception {
        api
                .perform(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")))
                .andExpect(content().string(containsString("A_TESTER")))
                .andExpect(content().string(containsString("B_TESTER")));
    }

    @Test
    @WithMockKeycloakAuth
    void testAuthentication() throws Exception {
        api.perform(get("/authentication")).andExpect(status().isOk()).andExpect(content().string("Hello user"));
    }

    @Test
    @WithMockKeycloakAuth
    void testPrincipal() throws Exception {
        api.perform(get("/principal")).andExpect(status().isOk()).andExpect(content().string("Hello user"));
    }

    static final String OTHER_CLAIMS = "{\"bar\":\"bad\", \"nested\":{\"deep\":\"her\"}, \"arr\":[1,2,3]}";
}

相关问题