I have a Spring Boot API running on localhost:8001
, which sends a request to another backend service on a different domain (localhost:8080
).
I tried to send an external request to the exact api below, which is hosted in localhost:8080
https://demo.thingsboard.io/swagger-ui/index.html#/admin-controller/getFeaturesInfo
Tried to send requests to this getFeaturesInfo api in Postman, no problem at all.
The problematic code is as follows:
/GetMapping("/test")
@PreAuthorize("isAuthenticated()")
public Mono<Object> getThingsboardTest() {
String token = "valid-token";
WebClient webClient = WebClient.create();
return webClient
.get()
.uri("http://localhost:8080/api/admin/featuresInfo")
.headers(h -> {
String authHeader = "Bearer " + token;
h.set("Authorization", authHeader);
log.info("Authorization Header: " + authHeader);
})
.retrieve()
.onStatus(HttpStatusCode::isError, clientResponse ->
clientResponse.bodyToMono(String.class)
.flatMap(errorBody -> {
log.error("Error Body: " + errorBody);
return Mono.error(new RuntimeException("Error Getting test: " + clientResponse.statusCode()));
})
)
.bodyToMono(Object.class);
}
When I use Postman to call this endpoint, it returns a 403 error without a response body. There is no error shown in the Intellij Console.
What I’ve Tried
1.Remove WebClient code and return a simple String:
@GetMapping("/test")
@PreAuthorize("isAuthenticated()")
public String getThingsboardTest() {
String token = "valid-token";
return token;
}
This works successfully, indicating that the problem is not related to my authentication logic.
2.Test another API that doesn’t require authentication
I tested another external API that doesn’t require authentication(/api/auth/login
), and it worked perfectly.
private void loginTest() {
WebClient webClient = WebClient.create();
String jsonBody = "{"username":"sysadmin@test","password":"test"}";
Mono<LoginResponse> response = webClient.post()
.uri("http://localhost:8080/api/auth/login")
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(jsonBody)
.retrieve()
.bodyToMono(ThingsboardLoginResponse.class);
response.subscribe(result -> {
redisTemplate.opsForValue().set("token", result.getToken());
});
}
This suggests the problem is not related to CORS.
3.Comment out the .headers method:
I commented out the headers method as follows:
return webClient
.get()
.uri("http://localhost:8080/api/admin/featuresInfo")
// .headers(h -> {
// String authHeader = "Bearer " + token;
// h.set("Authorization", authHeader);
// log.info("Authorization Header: " + authHeader);
// })
.retrieve()
.onStatus(HttpStatusCode::isError, clientResponse ->
clientResponse.bodyToMono(String.class)
.flatMap(errorBody -> {
log.error("Error Body: " + errorBody);
return Mono.error(new RuntimeException("Error Getting Thingsboard test: " + clientResponse.statusCode()));
})
)
.bodyToMono(Object.class);
When I do this, I see a log in my application in Intellij:
Error Body: {"status":401,"message":"Authentication failed","errorCode":10,"timestamp":1723832741717}
This log doesn’t appear when the .headers method is uncommented.
Summary
When the Authentication header is commented out:
I get a 401 error in the IntelliJ console due to log.error("Error Body: " + errorBody);
I see a 403 error in Postman.
When the Authentication header is uncommented:
No error body is logged from log.error("Error Body: " + errorBody);
The 403 error still occurs in Postman.