java.lang.AssertionError: Status expected:<200 OK> but was:<401 UNAUTHORIZED> for Spring Filter Chain Test

问题描述 投票:0回答:0

我实现了一个自定义过滤器,它扩展了 OncePerRequest 过滤器,它验证所有传入请求中的标头,并在标头令牌无效时抛出 UnAuthorizedException。

我在测试控制器时遇到此错误。

java.lang.AssertionError:预期状态:<200 OK>但是:<401 UNAUTHORIZED>在org.springframework.test.util.AssertionErrors.fail(AssertionErrors.java:59) 在 org.springframework.test.util.AssertionErrors.assertEquals(AssertionErrors.java:122)

抽象控制器

public abstract class AbstractControllerTest {

    static final String VALID_TOKEN = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI1NmFiYmNkZC1hMTJkLTRmNjQtOTQ1OS1kYmEzMzczMmEzNmUiLCJraW5kIjoiQUNDRVNTIiwicHJpbmNpcGFsSWQiOiI1NmFiYmNkZC1hMTJkLTRmNjQtOTQ1OS1kYmEzMzczMmEzNmUiLCJwcm92aWRlcklkIjoiMThlZjJjODMtYmYzMi00NjkzLWE5ODYtZjhmMmFjNTFkODk1Iiwib3JnSWQiOiJkODdmMTYyZC05Yzc2LTQ3YmYtYTM0NS1iYTc4MjQ3MDMzM2MiLCJ0ZW5hbnRJZCI6ImQ4N2YxNjJkLTljNzYtNDdiZi1hMzQ1LWJhNzgyNDcwMzMzYyIsInByaW5jaXBhbEtpbmQiOiJJQU1fVVNFUiIsImV4dGVybmFsSWQiOiJ0Z3VwdGFAaW50cmFsaW5rcy5jb20iLCJqdGkiOiJjMmUyNzRjOS1iYzE4LTQzMjgtYmRkNC01Yzk5NDJmYTkyZjQiLCJydGkiOiI1ZTM4NGM2Ny1lNDZkLTRiNzEtYmRkYy1kZTQwZDE5OWY4M2UiLCJpYXQiOjE2NjU2OTcwNTQsImV4cCI6MTY2NTcwMDY1NCwiaXNzIjoiaWFtLWludC5pbnRyYWxpbmtzLmNvbSJ9.Zh79HewZlCJwUjp0lZxo0NpsRLawwpq8dHPE6QKGo-Pvgza65ivvkIW5_nreLwQkucLMQSgjRp1ObvRLJWgB3zmWzXe0tLzu4T-932OUBvmPIWNB7qPe5D-JKjPORlzMuOsWfTSfcCtWy0boQdZVaoqhvCo7OG0FGZwtp_jpt4_Z3adG44KdYW7UDcCvnOcbW4SpZuCseRiVFvUrC2K-_MbI_PG0Sxd3DRRVZjdl_NxCHYuMZ7DNy5miva3JwNmzjekPE2uV2SrwUv1NBgvlJ2w3LfSIPL-me0LD1c3mP0gG19fZVJa4BhU43ZD5eTr8bgWVDNwNj6AWypoWsnt3bQ";
    static final String EXPIRED_TOKEN = "test_expired_token";

    static final String SERVICE_TOKEN_HEADER = "x-il-ctx-service-token";

    static final String AUTH_TOKEN_HEADER = "x-il-ctx-auth-token";

    @Value("${application.apiBaseUrl}")
    private String API_BASE_URL;

    @MockBean
    IAMTokenService iamTokenService;

    public void setUp() throws IAMClientException, ServerErrorException {
        when(this.iamTokenService.getServiceToken()).thenReturn(VALID_TOKEN);
        when(this.iamTokenService.validateToken(VALID_TOKEN)).thenReturn(TokenValidationResult.VALID);
        when(this.iamTokenService.validateToken(EXPIRED_TOKEN)).thenReturn(TokenValidationResult.INVALID);
    }

    String appendBaseUrl(String url) {
        return this.API_BASE_URL.concat(url);
    }
}

控制器测试

@Log4j2
@WebFluxTest(controllers = MainController.class)
@Import({BeanConfiguration.class, WebFluxControllerSecurityTestConfig.class})
public class MainControllerTest extends AbstractControllerTest {

    private WebTestClient client;

    @MockBean
    private NotificationAdminService service;

    @MockBean
    private JwtTokenAuthenticationFilter filter;

    @Autowired
    ApplicationContext context;

    @BeforeAll
    static void before() {
        log.info("Begin executing ".concat(MainControllerTest.class.getName()));
    }

    @AfterAll
    static void after() {
        log.info("Finished executing ".concat(MainControllerTest.class.getName()));
    }

    @BeforeEach
    public void setup() {
        this.client = WebTestClient
                .bindToApplicationContext(this.context)
                .configureClient()
                .build();
    }

    @Test
    @Order(1)
    public void testCreateNotification() {
        NotificationModel data = new NotificationModel(null, MessageType.MESSAGE, "title", "content", Priority.LOW,
                LocalDate.now(),
                LocalDate.now(),
                null, null, null,
                "createdByPrincipalId", NotificationStatus.ACTIVE);
        Mockito
            .when(this.service.create(Mockito.any(NotificationModel.class), eq("createdByPrincipalId")))
            .thenReturn(Mono.just(data));
        this
            .client
            .post()
            .uri(appendBaseUrl("/notifications"))
                .header(SERVICE_TOKEN_HEADER,VALID_TOKEN)
                .header(AUTH_TOKEN_HEADER,VALID_TOKEN)
            .contentType(MediaType.APPLICATION_JSON)
            .body(Mono.just(data), NotificationModel.class)
            .exchange().expectStatus().isCreated()
            .expectHeader().contentType(MediaType.APPLICATION_JSON);
    }
}

安全配置

@Configuration
@EnableWebSecurity
public class SecurityConfiguration {

    private final JwtTokenAuthenticationFilter jwtTokenAuthenticationFilter;
    private final FilterChainExceptionHandler filterChainExceptionHandler;

    SecurityConfiguration(JwtTokenAuthenticationFilter jwtTokenAuthenticationFilter,
                          FilterChainExceptionHandler filterChainExceptionHandler) {
        this.jwtTokenAuthenticationFilter = jwtTokenAuthenticationFilter;
        this.filterChainExceptionHandler = filterChainExceptionHandler;
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.csrf().disable();
        http.addFilterAfter(jwtTokenAuthenticationFilter, BasicAuthenticationFilter.class);
        http.addFilterBefore(filterChainExceptionHandler, LogoutFilter.class);
        return http.build();
    }
}

filterChainExceptionHandler

@Component
public class FilterChainExceptionHandler extends OncePerRequestFilter {
    private HandlerExceptionResolver resolver;

    FilterChainExceptionHandler(@Qualifier("handlerExceptionResolver") HandlerExceptionResolver resolver) {
        this.resolver = resolver;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) {
        try {
            filterChain.doFilter(request, response);
        } catch (Exception e) {
            logger.error("Spring Security Filter Chain Exception:", e);
            resolver.resolveException(request, response, null, e);
        }
    }
}

jwtTokenAuthenticationFilter

@Component
public class JwtTokenAuthenticationFilter extends OncePerRequestFilter {

    @Value("x-il-ctx-service-token")
    private String serviceTokenHeaderName;

    @Value("x-il-ctx-auth-token")
    private String authTokenHeaderName;
    private final IAMTokenService iamTokenService;
    private String principalId;

    public JwtTokenAuthenticationFilter(IAMTokenService iamTokenService) {
        this.iamTokenService = iamTokenService;

    }

    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {

        if (validateToken(request, serviceTokenHeaderName)
                && validateToken(request, serviceTokenHeaderName)) {
            if (!processAuthToken(request)) {
                logger.warn(getServletContext(), new Error("Could not read data from auth token ".concat(authTokenHeaderName)));
                throw new UnauthorizedException(new ErrorInfo("", "Could not read data from auth token ".concat(authTokenHeaderName), Map.of()));
            }
        }

        filterChain.doFilter(request, response);
    }

    private boolean validateToken(HttpServletRequest request, String tokenHeaderName) {
        String token = request.getHeader(tokenHeaderName);
        TokenValidationResult result = iamTokenService.validateToken(token);
        if (!TokenValidationResult.VALID.equals(result)) {
            logger.warn(getServletContext(), new Error("Unable to validate ".concat(tokenHeaderName)));
            throw new UnauthorizedException(new ErrorInfo("", "Unable to Verify ".concat(tokenHeaderName), Map.of()));
        }
        return true;
    }

    private boolean processAuthToken(HttpServletRequest request) {
        String authToken = request.getHeader(authTokenHeaderName);
        try {
            Map<String, Object> json = SignedJWT.parse(authToken).getPayload().toJSONObject();
            String principalId = json.get("principalId").toString();
            String principalKind = json.get("principalKind").toString();
            String support = "true";
            //String support = ((Map<String, Object>)json.get("admin")).get("support").toString();
            if (StringUtils.hasText(principalId)
                    && "IAM_USER".equals(principalKind)
                    && "true".equals(support)) {
                this.principalId = principalId;
            } else {
                throw new Exception("principalId is empty");
            }
        } catch (Exception e) {
            logger.error(getServletContext(), e);
            throw new UnauthorizedException(new ErrorInfo("", "Unable to Verify ".concat(authTokenHeaderName), Map.of()));
        }
        return true;
    }
}
spring-boot spring-security spring-webflux spring-test spring-webclient
© www.soinside.com 2019 - 2024. All rights reserved.