微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

在测试 REST 端点时模拟 JWT AuthenticationPrincipal

如何解决在测试 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 举报,一经查实,本站将立刻删除。