We are building REST API and adding AuthenticationPrincipal as method argument
@Override
public ResponseEntity<MappingJacksonValue> listProduct(
@Valid @RequestParam(value = "fields", required = false) final String fields,
@Valid @RequestParam(value = "offset", required = false) final Integer offset,
@Valid @RequestParam(value = "limit", required = false) final Integer limit,
@AuthenticationPrincipal final Jwt jwt) throws Exception {
//Extract roles from jwt and do advanced role checking.
}
While testing using mockmvc
@Autowired
private WebApplicationContext context;
@BeforeEach
public void setup() {
mvc = MockMvcBuilders
.webAppContextSetup(context)
.apply(springSecurity())
.build();
}
@Test
void testGetListProductsWithFields() throws Exception {
Map<String, Object> attributeMap = new HashMap<>();
attributeMap.put(null, "");
//Code
final var result = mvc
.perform(get("/api" + requestFields)
.with(jwt().authorities(new OAuth2UserAuthority("ROLE_my_roll", attributeMap)))
.accept(MediaType.APPLICATION_JSON)
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andReturn();
}
JWT token is getting initialised using constructor (org.springframework.security.oauth2.jwt.Jwt) at ModelAttributeMethodProcessor and since no token is being sent assertion errors are thrown. Idea is not to connect to auth provider to generate tokens
Call trace
Jwt(AbstractOAuth2Token).<init>(String, Instant, Instant) line: 55
Jwt.<init>(String, Instant, Instant, Map<String,Object>, Map<String,Object>) line: 69
NativeConstructorAccessorImpl.newInstance0(Constructor<?>, Object[]) line: not available [native method]
NativeConstructorAccessorImpl.newInstance(Object[]) line: 62
DelegatingConstructorAccessorImpl.newInstance(Object[]) line: 45
Constructor<T>.newInstance(Object...) line: 490
BeanUtils.instantiateClass(Constructor<T>, Object...) line: 204
ServletModelAttributeMethodProcessor(ModelAttributeMethodProcessor).constructAttribute(Constructor<?>, String, MethodParameter, WebDataBinderFactory, NativeWebRequest) line: 320
ServletModelAttributeMethodProcessor(ModelAttributeMethodProcessor).createAttribute(String, MethodParameter, WebDataBinderFactory, NativeWebRequest) line: 224
ServletModelAttributeMethodProcessor.createAttribute(String, MethodParameter, WebDataBinderFactory, NativeWebRequest) line: 85
ServletModelAttributeMethodProcessor(ModelAttributeMethodProcessor).resolveArgument(MethodParameter, ModelAndViewContainer, NativeWebRequest, WebDataBinderFactory) line: 139
HandlerMethodArgumentResolverComposite.resolveArgument(MethodParameter, ModelAndViewContainer, NativeWebRequest, WebDataBinderFactory) line: 121
ServletInvocableHandlerMethod(InvocableHandlerMethod).getMethodArgumentValues(NativeWebRequest, ModelAndViewContainer, Object...) line: 167
ServletInvocableHandlerMethod(InvocableHandlerMethod).invokeForRequest(NativeWebRequest, ModelAndViewContainer, Object...) line: 134
ServletInvocableHandlerMethod.invokeAndHandle(ServletWebRequest, ModelAndViewContainer, Object...) line: 105
RequestMappingHandlerAdapter.invokeHandlerMethod(HttpServletRequest, HttpServletResponse, HandlerMethod) line: 878
RequestMappingHandlerAdapter.handleInternal(HttpServletRequest, HttpServletResponse, HandlerMethod) line: 792
RequestMappingHandlerAdapter(AbstractHandlerMethodAdapter).handle(HttpServletRequest, HttpServletResponse, Object) line: 87
TestDispatcherServlet(DispatcherServlet).doDispatch(HttpServletRequest, HttpServletResponse) line: 1040
TestDispatcherServlet(DispatcherServlet).doService(HttpServletRequest, HttpServletResponse) line: 943
TestDispatcherServlet(FrameworkServlet).processRequest(HttpServletRequest, HttpServletResponse) line: 1006
TestDispatcherServlet(FrameworkServlet).doGet(HttpServletRequest, HttpServletResponse) line: 898
TestDispatcherServlet(HttpServlet).service(HttpServletRequest, HttpServletResponse) line: 626
TestDispatcherServlet(FrameworkServlet).service(HttpServletRequest, HttpServletResponse) line: 883
TestDispatcherServlet.service(HttpServletRequest, HttpServletResponse) line: 72
TestDispatcherServlet(HttpServlet).service(ServletRequest, ServletResponse) line: 733
MockFilterChain$ServletFilterProxy.doFilter(ServletRequest, ServletResponse, FilterChain) line: 167
MockFilterChain.doFilter(ServletRequest, ServletResponse) line: 134
FilterChainProxy$VirtualFilterChain.doFilter(ServletRequest, ServletResponse) line: 320
FilterSecurityInterceptor.invoke(FilterInvocation) line: 126
FilterSecurityInterceptor.doFilter(ServletRequest, ServletResponse, FilterChain) line: 90
FilterChainProxy$VirtualFilterChain.doFilter(ServletRequest, ServletResponse) line: 334
ExceptionTranslationFilter.doFilter(ServletRequest, ServletResponse, FilterChain) line: 118
FilterChainProxy$VirtualFilterChain.doFilter(ServletRequest, ServletResponse) line: 334
SessionManagementFilter.doFilter(ServletRequest, ServletResponse, FilterChain) line: 137
FilterChainProxy$VirtualFilterChain.doFilter(ServletRequest, ServletResponse) line: 334
AnonymousAuthenticationFilter.doFilter(ServletRequest, ServletResponse, FilterChain) line: 111
FilterChainProxy$VirtualFilterChain.doFilter(ServletRequest, ServletResponse) line: 334
SecurityContextHolderAwareRequestFilter.doFilter(ServletRequest, ServletResponse, FilterChain) line: 158
FilterChainProxy$VirtualFilterChain.doFilter(ServletRequest, ServletResponse) line: 334
RequestCacheAwareFilter.doFilter(ServletRequest, ServletResponse, FilterChain) line: 63
FilterChainProxy$VirtualFilterChain.doFilter(ServletRequest, ServletResponse) line: 334
BearerTokenAuthenticationFilter.doFilterInternal(HttpServletRequest, HttpServletResponse, FilterChain) line: 114
BearerTokenAuthenticationFilter(OncePerRequestFilter).doFilter(ServletRequest, ServletResponse, FilterChain) line: 119
FilterChainProxy$VirtualFilterChain.doFilter(ServletRequest, ServletResponse) line: 334
LogoutFilter.doFilter(ServletRequest, ServletResponse, FilterChain) line: 116
FilterChainProxy$VirtualFilterChain.doFilter(ServletRequest, ServletResponse) line: 334
CsrfFilter(OncePerRequestFilter).doFilter(ServletRequest, ServletResponse, FilterChain) line: 103
FilterChainProxy$VirtualFilterChain.doFilter(ServletRequest, ServletResponse) line: 334
HeaderWriterFilter.doHeadersAfter(HttpServletRequest, HttpServletResponse, FilterChain) line: 92
HeaderWriterFilter.doFilterInternal(HttpServletRequest, HttpServletResponse, FilterChain) line: 77
HeaderWriterFilter(OncePerRequestFilter).doFilter(ServletRequest, ServletResponse, FilterChain) line: 119
FilterChainProxy$VirtualFilterChain.doFilter(ServletRequest, ServletResponse) line: 334
SecurityContextPersistenceFilter.doFilter(ServletRequest, ServletResponse, FilterChain) line: 105
FilterChainProxy$VirtualFilterChain.doFilter(ServletRequest, ServletResponse) line: 334
WebAsyncManagerIntegrationFilter.doFilterInternal(HttpServletRequest, HttpServletResponse, FilterChain) line: 56
WebAsyncManagerIntegrationFilter(OncePerRequestFilter).doFilter(ServletRequest, ServletResponse, FilterChain) line: 119
FilterChainProxy$VirtualFilterChain.doFilter(ServletRequest, ServletResponse) line: 334
FilterChainProxy.doFilterInternal(ServletRequest, ServletResponse, FilterChain) line: 215
FilterChainProxy.doFilter(ServletRequest, ServletResponse, FilterChain) line: 178
SecurityMockMvcConfigurer$DelegateFilter.doFilter(ServletRequest, ServletResponse, FilterChain) line: 132
MockFilterChain.doFilter(ServletRequest, ServletResponse) line: 134
MockMvc.perform(RequestBuilder) line: 183
Tried to pass jwt constructor parameters as request parameter.
Map<String, Object> attributeMap = new HashMap<>();
attributeMap.put(null, "");
//Code
final Map<String, String> headerMap = new HashMap<>();
headerMap.put("alg", "none");
final var mapper = new ObjectMapper();
final var jsonMap = mapper.writeValueAsString(headerMap);
final var result = mvc
.perform(get("/api" + requestFields)
.with(jwt().authorities(new OAuth2UserAuthority("ROLE_my_roll", attributeMap)))
.param("tokenValue", "tokenValue")
.param("!headers", jsonMap)
.accept(MediaType.APPLICATION_JSON)
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andReturn();
But it throws different error
Cannot convert value of type 'java.lang.String' to required type 'java.util.Map': no matching editors or conversion strategy found
Tried adding converter. But no luck
@Component
public class StringToMapConverter implements Converter<String, Map<String, Object>> {
@Override
public Map<String, Object> convert(final String source) {
try {
return new ObjectMapper().readValue(source,
new TypeReference<Map<String, Object>>() {});
} catch (final IOException e) {
throw new RuntimeException(e.getMessage());
}
}
@Override
public JavaType getInputType(final TypeFactory typeFactory) {
return typeFactory.constructFromCanonical(String.class.getName());
}
@Override
public JavaType getOutputType(final TypeFactory typeFactory) {
return typeFactory.constructFromCanonical(Map.class.getName());
}
}
I am able to solve this by extending WebMvcConfigurationSupport and proving argumentResolver
//Test configuration
@ContextConfiguration
@EnableWebSecurity
@TestConfiguration
public class ProductApiConfiguration extends WebMvcConfigurationSupport {
@Override
protected void addArgumentResolvers(
final List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(getArgumentResolver());
}
private HandlerMethodArgumentResolver getArgumentResolver() {
return new HandlerMethodArgumentResolver() {
@Override
public boolean supportsParameter(final MethodParameter parameter) {
return parameter.getParameterType().isAssignableFrom(Jwt.class);
}
@Override
public Object resolveArgument(final MethodParameter parameter,
final ModelAndViewContainer mavContainer,
final NativeWebRequest webRequest, final WebDataBinderFactory binderFactory)
throws Exception {
final var roles = new JSONArray();
roles.add("my_role");
final Map<String, JSONArray> jsonMap = new HashMap<>();
jsonMap.put("roles", roles);
final var claimObject = new JSONObject(jsonMap);
final var jwt = Jwt.withTokenValue("token")
.header("alg", "none")
.claim("realm_access", claimObject)
.build();
final Collection<GrantedAuthority> authorities =
AuthorityUtils.createAuthorityList("ROLE_my_roll");
final var token = new JwtAuthenticationToken(jwt, authorities);
return token.getToken();
}
};
}
}
Since this is not per test i can't change the jwt token for each test and verify different scenarios.
Is there a simple way to mock the method argument in the controller class when an API is being tested by webmvc?