fix: project overseas authorization outside jwt
This commit is contained in:
+11
-3
@@ -4,6 +4,7 @@ import jakarta.validation.constraints.Min;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Set;
|
||||
|
||||
public final class LastlegSecurityDtos {
|
||||
private LastlegSecurityDtos() { }
|
||||
@@ -14,10 +15,17 @@ public final class LastlegSecurityDtos {
|
||||
@NotNull @Min(0) Long tenantId,
|
||||
@NotNull @Min(0) Long permissionVersion) { }
|
||||
|
||||
public record ValidationResponse(boolean valid, FailureReason failureReason) {
|
||||
public static ValidationResponse accepted() { return new ValidationResponse(true, null); }
|
||||
public record ValidationResponse(boolean valid, FailureReason failureReason,
|
||||
Set<String> roles, Set<String> authorities) {
|
||||
public ValidationResponse {
|
||||
roles = roles == null ? Set.of() : Set.copyOf(roles);
|
||||
authorities = authorities == null ? Set.of() : Set.copyOf(authorities);
|
||||
}
|
||||
public static ValidationResponse accepted(Set<String> roles, Set<String> authorities) {
|
||||
return new ValidationResponse(true, null, roles, authorities);
|
||||
}
|
||||
public static ValidationResponse rejected(FailureReason reason) {
|
||||
return new ValidationResponse(false, reason);
|
||||
return new ValidationResponse(false, reason, Set.of(), Set.of());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+28
-8
@@ -19,6 +19,7 @@ import java.time.Instant;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
@Service
|
||||
public class LastlegSecurityProjectionService {
|
||||
@@ -103,7 +104,7 @@ public class LastlegSecurityProjectionService {
|
||||
requireStatus(value.status());
|
||||
yield new ProjectionInput("USER", String.valueOf(value.authUserId()), value.tenantId(),
|
||||
value.status(), value.permissionVersion(), envelope.aggregateVersion(),
|
||||
payloadHash, envelope.eventId(), value.changedAt());
|
||||
payloadHash, envelope.eventId(), value.changedAt(), Set.of(), Set.of());
|
||||
}
|
||||
case EventTypes.IAM_TENANT_STATUS_CHANGED -> {
|
||||
var value = objectMapper.treeToValue(envelope.payload(),
|
||||
@@ -114,7 +115,7 @@ public class LastlegSecurityProjectionService {
|
||||
requireStatus(value.status());
|
||||
yield new ProjectionInput("TENANT", String.valueOf(value.tenantId()), value.tenantId(),
|
||||
value.status(), null, envelope.aggregateVersion(), payloadHash,
|
||||
envelope.eventId(), value.changedAt());
|
||||
envelope.eventId(), value.changedAt(), Set.of(), Set.of());
|
||||
}
|
||||
case EventTypes.IAM_PERMISSION_VERSION_CHANGED -> {
|
||||
var value = objectMapper.treeToValue(envelope.payload(),
|
||||
@@ -122,9 +123,10 @@ public class LastlegSecurityProjectionService {
|
||||
requireIdentity(envelope, "AUTHORIZATION", value.authUserId(), value.tenantId());
|
||||
requirePayload(value.appCode(), value.changedAt(), value.permissionVersion(),
|
||||
envelope.aggregateVersion());
|
||||
requireAuthorizationSnapshot(value.roles(), value.authorities());
|
||||
yield new ProjectionInput("AUTHORIZATION", String.valueOf(value.authUserId()), value.tenantId(),
|
||||
null, value.permissionVersion(), envelope.aggregateVersion(), payloadHash,
|
||||
envelope.eventId(), value.changedAt());
|
||||
envelope.eventId(), value.changedAt(), value.roles(), value.authorities());
|
||||
}
|
||||
default -> throw new IllegalArgumentException("unsupported type");
|
||||
};
|
||||
@@ -158,6 +160,14 @@ public class LastlegSecurityProjectionService {
|
||||
}
|
||||
}
|
||||
|
||||
private static void requireAuthorizationSnapshot(Set<String> roles, Set<String> authorities) {
|
||||
if (roles == null || authorities == null
|
||||
|| roles.stream().anyMatch(value -> value == null || value.isBlank())
|
||||
|| authorities.stream().anyMatch(value -> value == null || value.isBlank())) {
|
||||
throw new BizException(ResultCode.BAD_REQUEST, "IAM 海外授权快照无效");
|
||||
}
|
||||
}
|
||||
|
||||
private ProjectionRow findForUpdate(String type, String id) {
|
||||
List<ProjectionRow> rows = jdbc.query(
|
||||
"SELECT aggregate_version,payload_hash FROM ll_security_projection "
|
||||
@@ -167,15 +177,24 @@ public class LastlegSecurityProjectionService {
|
||||
}
|
||||
|
||||
private void upsert(ProjectionInput value) {
|
||||
final String rolesJson;
|
||||
final String authoritiesJson;
|
||||
try {
|
||||
rolesJson = objectMapper.writeValueAsString(value.roles());
|
||||
authoritiesJson = objectMapper.writeValueAsString(value.authorities());
|
||||
} catch (Exception exception) {
|
||||
throw new IllegalStateException("海外授权快照序列化失败", exception);
|
||||
}
|
||||
jdbc.update("INSERT INTO ll_security_projection(aggregate_type,aggregate_id,tenant_id,security_status,"
|
||||
+ "permission_version,aggregate_version,payload_hash,last_event_id,changed_at,updated_at) "
|
||||
+ "VALUES (?,?,?,?,?,?,?,?,?,NOW(3)) ON DUPLICATE KEY UPDATE tenant_id=VALUES(tenant_id),"
|
||||
+ "permission_version,roles_json,authorities_json,aggregate_version,payload_hash,last_event_id,changed_at,updated_at) "
|
||||
+ "VALUES (?,?,?,?,?,?,?,?,?,?,?,NOW(3)) ON DUPLICATE KEY UPDATE tenant_id=VALUES(tenant_id),"
|
||||
+ "security_status=VALUES(security_status),permission_version=VALUES(permission_version),"
|
||||
+ "roles_json=VALUES(roles_json),authorities_json=VALUES(authorities_json),"
|
||||
+ "aggregate_version=VALUES(aggregate_version),payload_hash=VALUES(payload_hash),"
|
||||
+ "last_event_id=VALUES(last_event_id),changed_at=VALUES(changed_at),updated_at=NOW(3)",
|
||||
value.aggregateType(), value.aggregateId(), value.tenantId(), value.status(),
|
||||
value.permissionVersion(), value.aggregateVersion(), value.payloadHash(), value.eventId(),
|
||||
value.changedAt());
|
||||
value.permissionVersion(), rolesJson, authoritiesJson, value.aggregateVersion(),
|
||||
value.payloadHash(), value.eventId(), value.changedAt());
|
||||
}
|
||||
|
||||
private record InboxRow(String eventType, String payloadHash, String status) { }
|
||||
@@ -184,5 +203,6 @@ public class LastlegSecurityProjectionService {
|
||||
public record ProjectionChanged(ProjectionInput projection) { }
|
||||
public record ProjectionInput(String aggregateType, String aggregateId, long tenantId, String status,
|
||||
Long permissionVersion, long aggregateVersion, String payloadHash,
|
||||
String eventId, Instant changedAt) { }
|
||||
String eventId, Instant changedAt, Set<String> roles,
|
||||
Set<String> authorities) { }
|
||||
}
|
||||
|
||||
+28
-6
@@ -3,17 +3,22 @@ package com.baiyue.lastleg.security.service;
|
||||
import com.baiyue.lastleg.security.dto.LastlegSecurityDtos.FailureReason;
|
||||
import com.baiyue.lastleg.security.dto.LastlegSecurityDtos.ValidationRequest;
|
||||
import com.baiyue.lastleg.security.dto.LastlegSecurityDtos.ValidationResponse;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
@Service
|
||||
public class LastlegSecurityValidationService {
|
||||
private final JdbcTemplate jdbc;
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
public LastlegSecurityValidationService(JdbcTemplate jdbc) {
|
||||
public LastlegSecurityValidationService(JdbcTemplate jdbc, ObjectMapper objectMapper) {
|
||||
this.jdbc = jdbc;
|
||||
this.objectMapper = objectMapper;
|
||||
}
|
||||
|
||||
public ValidationResponse validate(ValidationRequest request) {
|
||||
@@ -34,10 +39,10 @@ public class LastlegSecurityValidationService {
|
||||
}
|
||||
}
|
||||
List<Long> projectedVersions = jdbc.query(
|
||||
"SELECT MAX(permission_version) FROM ll_security_projection "
|
||||
+ "WHERE aggregate_id=? AND tenant_id=? "
|
||||
+ "AND aggregate_type IN ('USER','AUTHORIZATION') HAVING COUNT(permission_version)>0",
|
||||
(rs, row) -> rs.getLong(1), String.valueOf(request.userId()), request.tenantId());
|
||||
"SELECT permission_version FROM ll_security_projection "
|
||||
+ "WHERE aggregate_type='AUTHORIZATION' AND aggregate_id=? AND tenant_id=?",
|
||||
(rs, row) -> rs.getLong(1),
|
||||
String.valueOf(request.userId()), request.tenantId());
|
||||
if (projectedVersions.isEmpty()) {
|
||||
return ValidationResponse.rejected(FailureReason.SECURITY_PROJECTION_MISSING);
|
||||
}
|
||||
@@ -48,7 +53,19 @@ public class LastlegSecurityValidationService {
|
||||
if (request.permissionVersion() > projectedVersion) {
|
||||
return ValidationResponse.rejected(FailureReason.SECURITY_PROJECTION_NOT_READY);
|
||||
}
|
||||
return ValidationResponse.accepted();
|
||||
List<String> snapshots = jdbc.query(
|
||||
"SELECT CONCAT(roles_json, CHAR(10), authorities_json) FROM ll_security_projection "
|
||||
+ "WHERE aggregate_type='AUTHORIZATION' AND aggregate_id=? AND tenant_id=?",
|
||||
(rs, row) -> rs.getString(1), String.valueOf(request.userId()), request.tenantId());
|
||||
if (snapshots.isEmpty() || snapshots.get(0) == null || !snapshots.get(0).contains("\n")) {
|
||||
return ValidationResponse.rejected(FailureReason.SECURITY_PROJECTION_MISSING);
|
||||
}
|
||||
try {
|
||||
String[] values = snapshots.get(0).split("\n", 2);
|
||||
return ValidationResponse.accepted(readSet(values[0]), readSet(values[1]));
|
||||
} catch (Exception exception) {
|
||||
return ValidationResponse.rejected(FailureReason.SECURITY_PROJECTION_MISSING);
|
||||
}
|
||||
}
|
||||
|
||||
private String status(String type, String id, long tenantId) {
|
||||
@@ -57,4 +74,9 @@ public class LastlegSecurityValidationService {
|
||||
(rs, row) -> rs.getString(1), type, id, tenantId);
|
||||
return values.isEmpty() ? null : values.get(0);
|
||||
}
|
||||
|
||||
private Set<String> readSet(String json) throws Exception {
|
||||
if (json == null || json.isBlank()) return Set.of();
|
||||
return objectMapper.readValue(json, new TypeReference<Set<String>>() { });
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user