我实现了一个自定义过滤器,它扩展了 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;
}
}