如何解决在测试 REST 端点时模拟 JWT AuthenticationPrincipal
我们正在构建 REST API 并添加 AuthenticationPrincipal 作为方法参数
@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.
}
使用 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 令牌正在使用 org.springframework.security.oauth2.jwt.Jwt
处的构造函数 (@H_677_3@modelattributeMethodProcessor) 进行初始化,并且由于没有发送令牌,因此引发了断言错误。想法不是连接到auth provider来生成token
调用跟踪
Jwt(AbstractOAuth2Token).<init>(String,Instant,Instant) line: 55
Jwt.<init>(String,Map<String,Object>,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,NativeWebRequest) line: 224
ServletmodelattributeMethodProcessor.createAttribute(String,NativeWebRequest) line: 85
ServletmodelattributeMethodProcessor(modelattributeMethodProcessor).resolveArgument(MethodParameter,ModelAndViewContainer,NativeWebRequest,WebDataBinderFactory) line: 139
HandlerMethodArgumentResolverComposite.resolveArgument(MethodParameter,WebDataBinderFactory) line: 121
ServletinvocableHandlerMethod(invocableHandlerMethod).getmethodArgumentValues(NativeWebRequest,Object...) line: 167
ServletinvocableHandlerMethod(invocableHandlerMethod).invokeForRequest(NativeWebRequest,Object...) line: 134
ServletinvocableHandlerMethod.invokeAndHandle(ServletWebRequest,Object...) line: 105
RequestMappingHandlerAdapter.invokeHandlerMethod(HttpServletRequest,HttpServletResponse,HandlerMethod) line: 878
RequestMappingHandlerAdapter.handleInternal(HttpServletRequest,HandlerMethod) line: 792
RequestMappingHandlerAdapter(AbstractHandlerMethodAdapter).handle(HttpServletRequest,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,FilterChain) line: 90
FilterChainProxy$VirtualFilterChain.doFilter(ServletRequest,ServletResponse) line: 334
ExceptionTranslationFilter.doFilter(ServletRequest,FilterChain) line: 118
FilterChainProxy$VirtualFilterChain.doFilter(ServletRequest,ServletResponse) line: 334
SessionManagementFilter.doFilter(ServletRequest,FilterChain) line: 137
FilterChainProxy$VirtualFilterChain.doFilter(ServletRequest,ServletResponse) line: 334
AnonymousAuthenticationFilter.doFilter(ServletRequest,FilterChain) line: 111
FilterChainProxy$VirtualFilterChain.doFilter(ServletRequest,ServletResponse) line: 334
SecurityContextHolderAwareRequestFilter.doFilter(ServletRequest,FilterChain) line: 158
FilterChainProxy$VirtualFilterChain.doFilter(ServletRequest,ServletResponse) line: 334
RequestCacheAwareFilter.doFilter(ServletRequest,FilterChain) line: 63
FilterChainProxy$VirtualFilterChain.doFilter(ServletRequest,ServletResponse) line: 334
BearerTokenAuthenticationFilter.doFilterInternal(HttpServletRequest,FilterChain) line: 114
BearerTokenAuthenticationFilter(OncePerRequestFilter).doFilter(ServletRequest,FilterChain) line: 119
FilterChainProxy$VirtualFilterChain.doFilter(ServletRequest,ServletResponse) line: 334
logoutFilter.doFilter(ServletRequest,FilterChain) line: 116
FilterChainProxy$VirtualFilterChain.doFilter(ServletRequest,ServletResponse) line: 334
CsrfFilter(OncePerRequestFilter).doFilter(ServletRequest,FilterChain) line: 103
FilterChainProxy$VirtualFilterChain.doFilter(ServletRequest,ServletResponse) line: 334
HeaderWriterFilter.doHeadersAfter(HttpServletRequest,FilterChain) line: 92
HeaderWriterFilter.doFilterInternal(HttpServletRequest,FilterChain) line: 77
HeaderWriterFilter(OncePerRequestFilter).doFilter(ServletRequest,FilterChain) line: 119
FilterChainProxy$VirtualFilterChain.doFilter(ServletRequest,ServletResponse) line: 334
SecurityContextPersistenceFilter.doFilter(ServletRequest,FilterChain) line: 105
FilterChainProxy$VirtualFilterChain.doFilter(ServletRequest,ServletResponse) line: 334
WebAsyncManagerIntegrationFilter.doFilterInternal(HttpServletRequest,FilterChain) line: 56
WebAsyncManagerIntegrationFilter(OncePerRequestFilter).doFilter(ServletRequest,FilterChain) line: 119
FilterChainProxy$VirtualFilterChain.doFilter(ServletRequest,ServletResponse) line: 334
FilterChainProxy.doFilterInternal(ServletRequest,FilterChain) line: 215
FilterChainProxy.doFilter(ServletRequest,FilterChain) line: 178
SecuritymockmvcConfigurer$DelegateFilter.doFilter(ServletRequest,FilterChain) line: 132
MockFilterChain.doFilter(ServletRequest,ServletResponse) line: 134
mockmvc.perform(RequestBuilder) line: 183
尝试将 jwt 构造函数参数作为请求参数传递。
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();
但它抛出不同的错误
Cannot convert value of type 'java.lang.String' to required type 'java.util.Map': no matching editors or conversion strategy found
尝试添加转换器。但没有运气
@Component
public class StringToMapConverter implements Converter<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());
}
}
我可以通过扩展 WebMvcConfigurationSupport
并证明 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();
}
};
}
}
由于这不是每个测试,因此我无法更改每个测试的 jwt 令牌并验证不同的场景。
当 webmvc 测试 API 时,是否有一种简单的方法来模拟控制器类中的方法参数?
解决方法
我们可以通过设置 SecurityContextHolder 来解决这个问题
window.top.location.href = this.routingService.payRequest.callBackUrl;
和测试工具类
@Autowired
private MockMvc mvc;
@Autowired
private WebApplicationContext context;
@MockBean
SecurityContext securityContext;
@Test
public void test() {
//some code
given(securityContext.getAuthentication())
.willReturn(Utils
.getMockJwtToken("my_role","testSubject"));
SecurityContextHolder.setContext(securityContext);
final var result = this.mvc
.perform(get("/api" + requestFields)
.accept(MediaType.APPLICATION_JSON)
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andReturn();
}
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。