У меня есть класс для весенней безопасности, проверяющий токен от пользователя. Я получил код с веб-сайта Auth0 и изменил часть antMatcher
для своей конфигурации. Вот код:
@EnableWebSecurity
public class SecurityConfig {
@Value("${auth0.audience}")
private String audience;
@Value("${spring.security.oauth2.resourceserver.jwt.issuer-uri}")
private String issuer;
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
/*
This is where we configure the security required for our endpoints and setup our app to serve as
an OAuth2 Resource Server, using JWT validation.
*/
http
.csrf().disable()
.authorizeRequests()
.antMatchers(HttpMethod.GET, "/data/actuator/**").permitAll()
.antMatchers(HttpMethod.PUT, "/data/**").hasAuthority("SCOPE_data:write")
.anyRequest().authenticated()
.and().cors()
.and().oauth2ResourceServer().jwt();
return http.build();
}
@Bean
JwtDecoder jwtDecoder() {
/*
By default, Spring Security does not validate the "aud" claim of the token, to ensure that this token is
indeed intended for our app. Adding our own validator is easy to do:
*/
NimbusJwtDecoder jwtDecoder = (NimbusJwtDecoder)
JwtDecoders.fromOidcIssuerLocation(issuer);
OAuth2TokenValidator<Jwt> audienceValidator =
new com.nuance.pindata.health.importer.security.AudienceValidator(audience);
OAuth2TokenValidator<Jwt> withIssuer = JwtValidators.createDefaultWithIssuer(issuer);
OAuth2TokenValidator<Jwt> withAudience = new DelegatingOAuth2TokenValidator<>(withIssuer, audienceValidator);
jwtDecoder.setJwtValidator(withAudience);
return jwtDecoder;
}
}
Сейчас я пытаюсь написать модульный тест, но нет хорошего способа его протестировать. Я могу практически протестировать изменение метода/пути, но не так просто написать этот модульный тест, и это можно сделать с помощью интеграционных (автоматизированных) тестов.
Из Тестирование конфигурации Spring Security HttpSecurity он также предлагает не писать модульный тест для такой конфигурации безопасности. Какой здесь правильный подход? Если я должен написать модульный тест, как я могу этого добиться?
Вы можете протестировать управление доступом к конечным точкам привода только в интеграционных тестах (@SpringBootTest
). Для вашего собственного защищенного @Components
вы можете сделать это также в юнит-тестах (много примеров в этом репо):
@Controller
с @WebMvcTest
(@WebfluxTest
, если вы были в реактивном приложении)@ExtendWith(SpringExtension.class)
, @EnableMethodSecurity
и @Import
тестируемого компонента (@Service
или @Repository
с безопасностью метода, такой как @PreAuthorize
выражения), чтобы получить автоматически подключенный экземпляр, оснащенный средствами безопасностиspring-security-test
поставляется с некоторыми постпроцессорами запросов MockMvc (см. org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.jwt
в вашем случае), а также мутаторами WebTestClient (см. org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.mockJwt
) для настройки аутентификации правильного типа (JwtAuthenticationToken
в вашем случае) и установки ее в тестовом контексте безопасности, но это ограничено для MockMvc и WebTestClient, а также для @Controller
тестов.
Пример использования в интеграционном тесте (@SpringBootTest
) для запуска привода (но вы поняли идею модульных тестов):
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.jwt;
@SpringBootTest(webEnvironment = WebEnvironment.MOCK)
@AutoConfigureMockMvc
class ApplicationIntegrationTest {
@Autowired
MockMvc api;
@Test
void givenUserIsAnonymous_whenGetLiveness_thenOk() throws Exception {
api.perform(get("/data/actuator/health/liveness"))
.andExpect(status().isOk());
}
@Test
void givenUserIsAnonymous_whenGetMachin_thenUnauthorized() throws Exception {
api.perform(get("/data/machin"))
.andExpect(status().isUnauthorized());
}
@Test
void givenUserIsGrantedWithDataWrite_whenGetMachin_thenOk() throws Exception {
api.perform(get("/data/machin")
.with(jwt().jwt(jwt -> jwt.authorities(List.of(new SimpleGrantedAuthority("SCOPE_data:write"))))))
.andExpect(status().isOk());
}
@Test
void givenUserIsAuthenticatedButNotGrantedWithDataWrite_whenGetMachin_thenForbidden() throws Exception {
api.perform(get("/data/machin")
.with(jwt().jwt(jwt -> jwt.authorities(List.of(new SimpleGrantedAuthority("SCOPE_openid"))))))
.andExpect(status().isForbidden());
}
}
Вы также можете использовать @WithMockJwtAuth
из этих библиотек, которые я поддерживаю. Этот репозиторий содержит довольно много примеров для модульного и интеграционного тестирования любого вида @Component
(@Controllers
, конечно, но также @Services
или @Repositories
, украшенных безопасностью методов).
Образец выше становится:
<dependency>
<groupId>com.c4-soft.springaddons</groupId>
<artifactId>spring-addons-oauth2-test</artifactId>
<version>6.0.12</version>
<scope>test</scope>
</dependency>
@SpringBootTest(webEnvironment = WebEnvironment.MOCK)
@AutoConfigureMockMvc
class ApplicationIntegrationTest {
@Autowired
MockMvc api;
@Test
void givenUserIsAnonymous_whenGetLiveness_thenOk() throws Exception {
api.perform(get("/data/actuator/health/liveness"))
.andExpect(status().isOk());
}
@Test
void givenUserIsAnonymous_whenGetMachin_thenUnauthorized() throws Exception {
api.perform(get("/data/machin"))
.andExpect(status().isUnauthorized());
}
@Test
@WithMockJwtAuth("SCOPE_data:write")
void givenUserIsGrantedWithDataWrite_whenGetMachin_thenOk() throws Exception {
api.perform(get("/data/machin"))
.andExpect(status().isOk());
}
@Test
@WithMockJwtAuth("SCOPE_openid")
void givenUserIsAuthenticatedButNotGrantedWithDataWrite_whenGetMachin_thenForbidden() throws Exception {
api.perform(get("/data/machin"))
.andExpect(status().isForbidden());
}
}
В том же репозитории, что и тестовые аннотации, вы найдете стартеры для упрощения конфигурации безопасности вашего сервера ресурсов (а также для улучшения конфигурации CORS и синхронизации сеансов и отключения защиты CSRF, поскольку второй не следует отключать при активных сеансах...).
Использование очень простое, и все, что вам нужно изменить, чтобы переключиться на другой сервер авторизации OIDC, это свойства. Это может произойти, например, из-за того, что вас вынуждает занятость (если они решат, что Auth0 слишком дорог или ему больше нельзя доверять) или, может быть, потому, что вы считаете более удобным использовать автономный Keycloak на вашей машине разработки (это доступны в автономном режиме, что я часто).
Вместо того, чтобы напрямую импортировать spring-boot-starter-oauth2-resource-server
, импортируйте вокруг него тонкую оболочку (, состоящую только из 3 файлов ):
<dependency>
<groupId>com.c4-soft.springaddons</groupId>
<artifactId>spring-addons-webmvc-jwt-resource-server</artifactId>
<version>6.0.12</version>
</dependency>
По умолчанию пользователи должны пройти аутентификацию для доступа к любому маршруту, кроме тех, которые перечислены в свойстве com.c4-soft.springaddons.security.permit-all
(см. ниже). Замените всю вашу конфигурацию Java на:
@Configuration
@EnableMethodSecurity
public class SecurityConfig {
// If not using method-security or to configure actuator RBAC
// You might define a bean of type ExpressionInterceptUrlRegistryPostProcessor
// and Fine tune AuthorizeHttpRequestsConfigurer<HttpSecurity>.AuthorizationManagerRequestMatcherRegistry
}
Вы можете удалить все свойства spring.security.oauth2.resourceserver
, они игнорируются (за исключением spring.security.oauth2.resourceserver.jwt.audiences
, если вы проверяете аудиторию). Вместо этого можно использовать следующие свойства:
# Define this instead of auth0.audience
spring.security.oauth2.resourceserver.jwt.audiences=http://localhost:8080,https://localhost:8080
# Single OIDC JWT issuer but you can add as many as you like
com.c4-soft.springaddons.security.issuers[0].location=https://dev-ch4mpy.eu.auth0.com/
# Mimic spring-security default converter: map authorities with "SCOPE_" prefix
# Difference with your current conf is authorities source is not only "scope" claim but also "roles" and "permissions" ones
# I would consider map authorities without "SCOPE_" prefix (the default behaviour of my starters) and update access control expressions accordingly
com.c4-soft.springaddons.security.issuers[0].authorities.claims=scope,roles,permissions
com.c4-soft.springaddons.security.issuers[0].authorities.prefix=SCOPE_
# Fine-grained CORS configuration can be set per path as follow:
com.c4-soft.springaddons.security.cors[0].path=/data/api/**
com.c4-soft.springaddons.security.cors[0].allowed-origins=https://localhost,https://localhost:8100,https://localhost:4200
com.c4-soft.springaddons.security.cors[0].allowedOrigins=*
com.c4-soft.springaddons.security.cors[0].allowedMethods=*
com.c4-soft.springaddons.security.cors[0].allowedHeaders=*
com.c4-soft.springaddons.security.cors[0].exposedHeaders=*
# Comma separated list of ant path matchers for resources accessible to anonymous
com.c4-soft.springaddons.security.permit-all=/data/actuator/**
Пипец, не так ли?
Вы хотите протестировать JwtDecoder?