package com.baiyue.common.security; import com.fasterxml.jackson.databind.ObjectMapper; import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.time.Duration; import java.util.Map; /** Bounded, non-redirecting internal call. Never forwards an end-user Authorization header. */ public final class HttpSessionStateVerifier implements SessionStateVerifier { private final HttpClient client = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(1)) .followRedirects(HttpClient.Redirect.NEVER).build(); private final URI uri; private final String token; private final ObjectMapper mapper; public HttpSessionStateVerifier(String uri, String token, ObjectMapper mapper) { this.uri = URI.create(uri); this.token = token; this.mapper = mapper; } @Override public void verify(String jwtId, Long userId) { try { var request = HttpRequest.newBuilder(uri).timeout(Duration.ofSeconds(4)) .header("Content-Type", "application/json").header("X-Internal-Service-Token", token) .POST(HttpRequest.BodyPublishers.ofByteArray(mapper.writeValueAsBytes( Map.of("jwtId", jwtId, "userId", userId)))).build(); var response = client.send(request, HttpResponse.BodyHandlers.ofString()); var body = mapper.readTree(response.body()); if (response.statusCode() != 200 || body == null || !body.path("code").isTextual() || !"200".equals(body.path("code").asText()) || !body.path("data").isBoolean() || !body.path("data").booleanValue()) { throw new IllegalArgumentException("Authorization state is revoked or unavailable"); } } catch (InterruptedException failure) { Thread.currentThread().interrupt(); throw new IllegalArgumentException("Authorization state validation interrupted", failure); } catch (Exception failure) { throw new IllegalArgumentException("Authorization state is revoked or unavailable", failure); } } }