Closed thekalinga closed 1 year ago
I'm not sure that what you ask is possible: as per its name, @ParameterizedTest
parameterizes the test itself, not its annotations. When the test instance is called with parameters, the security context has already been populated by @WithMockJwtAuth
and I know no hook in WithSecurityContextFactory
to read @ParameterizedTest
value.
If you only test @Controller
security (not a @Service
or other @Component
), what you can already do is use MockMvc request post-processor or WebTestClient mutator:
@ParameterizedTest
@ValueSource(strings = { "NICE", "VERY_NICE" })
void givenUserIsGrantedWithAnyNiceAuthority_whenGetRestricted_thenOk(String authority) throws Exception {
api.perform(get("/restricted").with(SecurityMockMvcRequestPostProcessors.jwt().authorities(new SimpleGrantedAuthority(authority))))
.andExpect(status().isOk())
.andExpect(jsonPath("$.body").value("You are so nice!"));
}
I will investigate how complicated it would be to write an equivalent of @ValueSource
for Authentication
instances. Something like @JwtAuthenticationSource(auths = { @WithMockJwtAuth("role1"), @WithMockJwtAuth("role2") })
which would instantiate a JwtAuthenticationToken
for each entry, populate the test security context with it and then pass it as an argument to the test function
Here is what I came to so far:
@ParameterizedTest
@JwtAuthenticationSource({ @WithMockJwtAuth(authorities = "NICE"), @WithMockJwtAuth(authorities = "VERY_NICE") })
void givenUserIsGrantedWithAnyNiceAuthentication_whenGetRestricted_thenOk(@JwtAuth JwtAuthenticationToken auth) throws Exception {
api.perform(get("/restricted"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.body").value("You are so nice!"));
}
Mind the @JwtAuthenticationSource
decorating the test method and the @JwtAuth
decorating the variable argument. Both are required:
@JwtAuthenticationSource
defines the different authentications to build for each test@JwtAuth
inserts the current authentication in the security context@thekalinga what do you think of this solution?
This relies on the following:
@Target({ ElementType.ANNOTATION_TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@ArgumentsSource(JwtAuthenticationSource.JwtAuthenticationsProvider.class)
public @interface JwtAuthenticationSource {
WithMockJwtAuth[] value() default {};
static class JwtAuthenticationsProvider implements ArgumentsProvider, AnnotationConsumer<JwtAuthenticationSource> {
private final WithMockJwtAuth.JwtAuthenticationTokenFactory authFactory = new WithMockJwtAuth.JwtAuthenticationTokenFactory();
private Collection<JwtAuthenticationToken> arguments;
@Override
public void accept(JwtAuthenticationSource source) {
// @formatter:off
arguments =
Stream.of(source.value())
.map(authFactory::authentication)
.toList();
}
@Override
public Stream<? extends Arguments> provideArguments(ExtensionContext context) {
return arguments.stream().map(Arguments::of);
}
}
}
public class JwtAuthenticationArgumentProcessor extends TypedArgumentConverter<JwtAuthenticationToken, JwtAuthenticationToken> {
protected JwtAuthenticationArgumentProcessor() {
super(JwtAuthenticationToken.class, JwtAuthenticationToken.class);
}
@Override
protected JwtAuthenticationToken convert(JwtAuthenticationToken source) {
SecurityContextHolder.getContext().setAuthentication(source);
return source;
}
}
@Target({ ElementType.ANNOTATION_TYPE, ElementType.PARAMETER })
@Retention(RetentionPolicy.RUNTIME)
@ConvertWith(JwtAuthenticationArgumentProcessor.class)
public @interface JwtAuth {
}
Released in 6.1.12
. See release notes for usage details.
Is your feature request related to a problem? Please describe.
If an endpoint is accessible to users with multiple authorities, I have to create one test per role and put same annotations on each of them method except authority.
Describe the solution you'd like
I would want to use junit's
ParameterizedTest
to pass this informationDescribe alternatives you've considered None as I am still a noob with this framework.