优化
This commit is contained in:
+5
-2
@@ -50,13 +50,16 @@ public class BaiyueSecurityAutoConfiguration {
|
||||
org.springframework.beans.factory.ObjectProvider<StringRedisTemplate> redis,
|
||||
ObjectMapper objectMapper,
|
||||
@Value("${baiyue.security.token-key-prefix:baiyue:auth}") String keyPrefix,
|
||||
@Value("${baiyue.security.session-mode:redis}") String sessionMode) {
|
||||
@Value("${baiyue.security.session-mode:redis}") String sessionMode,
|
||||
@Value("${baiyue.security.session-validation-uri:http://auth:9100/internal/auth/accounts/sessions/validate}") String validationUri,
|
||||
@Value("${baiyue.security.internal-service-token}") String internalToken) {
|
||||
if (!"redis".equalsIgnoreCase(sessionMode)) {
|
||||
throw new IllegalStateException("Only shared Redis access sessions are supported");
|
||||
}
|
||||
StringRedisTemplate template = redis.getIfAvailable();
|
||||
if (template == null) throw new IllegalStateException("Redis access sessions require StringRedisTemplate");
|
||||
AccessTokenSessionValidator validator = new RedisAccessTokenSessionValidator(template, keyPrefix, objectMapper);
|
||||
AccessTokenSessionValidator validator = new RedisAccessTokenSessionValidator(template, keyPrefix, objectMapper,
|
||||
new HttpSessionStateVerifier(validationUri, internalToken, objectMapper));
|
||||
return new JwtAuthenticationFilter(jwtDecoder, claimsMapper, entryPoint, validator);
|
||||
}
|
||||
|
||||
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+4
-4
@@ -12,15 +12,14 @@ public final class RedisAccessTokenSessionValidator implements AccessTokenSessio
|
||||
private final String keyPrefix;
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
public RedisAccessTokenSessionValidator(StringRedisTemplate redisTemplate, String keyPrefix) {
|
||||
this(redisTemplate, keyPrefix, new ObjectMapper());
|
||||
}
|
||||
private final SessionStateVerifier stateVerifier;
|
||||
|
||||
public RedisAccessTokenSessionValidator(StringRedisTemplate redisTemplate, String keyPrefix,
|
||||
ObjectMapper objectMapper) {
|
||||
ObjectMapper objectMapper, SessionStateVerifier stateVerifier) {
|
||||
this.redisTemplate = redisTemplate;
|
||||
this.keyPrefix = RedisTokenKeys.normalize(keyPrefix);
|
||||
this.objectMapper = objectMapper;
|
||||
this.stateVerifier = java.util.Objects.requireNonNull(stateVerifier);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -39,6 +38,7 @@ public final class RedisAccessTokenSessionValidator implements AccessTokenSessio
|
||||
if (userId == null || !userId.equals(session.userId())) {
|
||||
throw new IllegalArgumentException("Access token session identity mismatch");
|
||||
}
|
||||
stateVerifier.verify(jwtId, userId);
|
||||
return session;
|
||||
} catch (org.springframework.dao.DataAccessException exception) {
|
||||
throw new IllegalArgumentException("Token session store is unavailable", exception);
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.baiyue.common.security;
|
||||
|
||||
/** Verifies persisted authorization state, without positive caching. */
|
||||
@FunctionalInterface
|
||||
public interface SessionStateVerifier {
|
||||
void verify(String jwtId, Long userId);
|
||||
}
|
||||
Reference in New Issue
Block a user