mirror of
https://github.com/hs-web/hsweb-framework.git
synced 2026-09-03 06:35:25 +08:00
refactor: 优化权限逻辑
This commit is contained in:
@@ -21,6 +21,7 @@ import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.hswebframework.web.authorization.*;
|
||||
|
||||
import javax.annotation.concurrent.NotThreadSafe;
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.util.*;
|
||||
@@ -46,7 +47,6 @@ public class SimpleAuthentication implements Authentication {
|
||||
|
||||
private List<Dimension> dimensions = new ArrayList<>();
|
||||
|
||||
@Setter
|
||||
private Map<String, Serializable> attributes = new HashMap<>();
|
||||
|
||||
public static Authentication of() {
|
||||
@@ -72,6 +72,26 @@ public class SimpleAuthentication implements Authentication {
|
||||
return attributes == null ? Collections.emptyMap() : attributes;
|
||||
}
|
||||
|
||||
public void setAttributes(Map<String, Serializable> attributes) {
|
||||
this.attributes = new HashMap<>(attributes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAttribute(String key, Serializable value) {
|
||||
if (key == null) {
|
||||
return;
|
||||
}
|
||||
if (this.attributes == null) {
|
||||
this.attributes = new HashMap<>();
|
||||
}
|
||||
this.attributes.put(key, value);
|
||||
}
|
||||
|
||||
public void putAttributes(Map<String, Serializable> attributes) {
|
||||
this.attributes = new HashMap<>(this.attributes);
|
||||
this.attributes.putAll(attributes);
|
||||
}
|
||||
|
||||
public SimpleAuthentication merge(Authentication authentication) {
|
||||
Map<String, Permission> mePermissionGroup = permissions
|
||||
.stream()
|
||||
@@ -92,12 +112,16 @@ public class SimpleAuthentication implements Authentication {
|
||||
}
|
||||
me.getActions().addAll(permission.getActions());
|
||||
}
|
||||
//merge 保持 self 优先:仅当 self 不含该 (type,id) 时才并入 other 的维度
|
||||
this.dimensions = new ArrayList<>(this.getDimensions());
|
||||
for (Dimension dimension : authentication.getDimensions()) {
|
||||
if (getDimension(dimension.getType(), dimension.getId()).isEmpty()) {
|
||||
dimensions.add(dimension);
|
||||
if (!containsDimension(this.dimensions, dimension)) {
|
||||
this.dimensions.add(dimension);
|
||||
}
|
||||
}
|
||||
//重建了 permissions/dimensions 列表,失效索引缓存
|
||||
this.permissionMapping = null;
|
||||
this.dimensionMapping = null;
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -128,23 +152,116 @@ public class SimpleAuthentication implements Authentication {
|
||||
|
||||
public void setUser(User user) {
|
||||
this.user = user;
|
||||
dimensions.add(user);
|
||||
addDimension(user);
|
||||
}
|
||||
|
||||
protected void setUser0(User user) {
|
||||
this.user = user;
|
||||
}
|
||||
|
||||
protected List<Dimension> newDimensions() {
|
||||
this.dimensions = new ArrayList<>();
|
||||
if (user != null) {
|
||||
this.dimensions.add(user);
|
||||
}
|
||||
return this.dimensions;
|
||||
}
|
||||
|
||||
public void setDimensions(List<Dimension> dimensions) {
|
||||
this.dimensions.addAll(dimensions);
|
||||
this.dimensions = distinctLastWins(newDimensions(), dimensions);
|
||||
this.dimensionMapping = null;
|
||||
}
|
||||
|
||||
public void setDimensions(Collection<Dimension> dimensions) {
|
||||
this.dimensions.addAll(dimensions);
|
||||
this.dimensions = distinctLastWins(newDimensions(), dimensions);
|
||||
this.dimensionMapping = null;
|
||||
}
|
||||
|
||||
public void addDimension(Dimension dimension) {
|
||||
this.dimensions.add(dimension);
|
||||
if (dimension != null) {
|
||||
List<Dimension> target = writableDimensions();
|
||||
int index = indexOfDimension(target, dimension);
|
||||
//已存在相同 (type,id) 则替换为最新,否则追加;线性查找避免分配索引结构
|
||||
if (index >= 0) {
|
||||
target.set(index, dimension);
|
||||
} else {
|
||||
target.add(dimension);
|
||||
}
|
||||
}
|
||||
this.dimensionMapping = null;
|
||||
}
|
||||
|
||||
public void cleanPermissions() {
|
||||
this.permissions = new ArrayList<>();
|
||||
this.permissionMapping = null;
|
||||
}
|
||||
|
||||
protected List<Dimension> writableDimensions() {
|
||||
return this.dimensions == null ? this.dimensions = new ArrayList<>() : this.dimensions;
|
||||
}
|
||||
|
||||
public void addDimensions(Collection<? extends Dimension> dimensions) {
|
||||
this.dimensions = distinctLastWins(getDimensions(), dimensions);
|
||||
this.dimensionMapping = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 以 seed 为基础并入 toAdd,按 (type,id) 去重;命中相同标识时<b>后者覆盖前者(替换为最新)</b>,
|
||||
* 并保持该标识首次出现的位置.
|
||||
*/
|
||||
private static List<Dimension> distinctLastWins(List<Dimension> seed,
|
||||
Collection<? extends Dimension> toAdd) {
|
||||
//结构化 (type,id) -> 维度,LinkedHashMap 保位置、put 覆盖实现 last-wins
|
||||
LinkedHashMap<List<String>, Dimension> merged = new LinkedHashMap<>();
|
||||
for (Dimension dimension : seed) {
|
||||
merged.put(dimensionKey(dimension), dimension);
|
||||
}
|
||||
if (toAdd != null) {
|
||||
for (Dimension dimension : toAdd) {
|
||||
if (dimension != null) {
|
||||
merged.put(dimensionKey(dimension), dimension);
|
||||
}
|
||||
}
|
||||
}
|
||||
return new ArrayList<>(merged.values());
|
||||
}
|
||||
|
||||
//结构化 (type,id) key,避免拼接字符串带来的分隔符歧义/碰撞
|
||||
private static List<String> dimensionKey(Dimension dimension) {
|
||||
DimensionType type = dimension.getType();
|
||||
return Arrays.asList(type == null ? null : type.getId(), dimension.getId());
|
||||
}
|
||||
|
||||
//merge 中判断 self 是否已含相同 (type,id);零分配线性查找
|
||||
private static boolean containsDimension(List<Dimension> dimensions, Dimension dimension) {
|
||||
return indexOfDimension(dimensions, dimension) >= 0;
|
||||
}
|
||||
|
||||
//线性查找相同 (type,id) 的下标,无则 -1;零分配,用于单个 add 的就地替换
|
||||
private static int indexOfDimension(List<Dimension> dimensions, Dimension dimension) {
|
||||
DimensionType type = dimension.getType();
|
||||
String typeId = type == null ? null : type.getId();
|
||||
String id = dimension.getId();
|
||||
for (int i = 0; i < dimensions.size(); i++) {
|
||||
Dimension exist = dimensions.get(i);
|
||||
DimensionType existType = exist.getType();
|
||||
if (Objects.equals(id, exist.getId())
|
||||
&& Objects.equals(typeId, existType == null ? null : existType.getId())) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public void addPermissions(Collection<? extends Permission> permissions) {
|
||||
this.permissions = new ArrayList<>(getPermissions());
|
||||
this.permissions.addAll(permissions);
|
||||
this.permissionMapping = null;
|
||||
}
|
||||
|
||||
public void addPermission(Permission permission) {
|
||||
this.permissions.add(permission);
|
||||
this.permissionMapping = null;
|
||||
}
|
||||
|
||||
private transient volatile Map<String, Map<String, Dimension>> dimensionMapping;
|
||||
@@ -158,21 +275,21 @@ public class SimpleAuthentication implements Authentication {
|
||||
permissionMapping = permissions == null
|
||||
? Collections.emptyMap()
|
||||
: permissions
|
||||
.stream()
|
||||
.collect(Collectors
|
||||
.toMap(Permission::getId,
|
||||
Function.identity(),
|
||||
(a, b) -> b));
|
||||
.stream()
|
||||
.collect(Collectors
|
||||
.toMap(Permission::getId,
|
||||
Function.identity(),
|
||||
(a, b) -> b));
|
||||
dimensionMapping = dimensions == null
|
||||
? Collections.emptyMap()
|
||||
: dimensions
|
||||
.stream()
|
||||
.collect(Collectors
|
||||
.groupingBy(d -> d.getType().getId(),
|
||||
Collectors.toMap(
|
||||
Dimension::getId,
|
||||
Function.identity(),
|
||||
(a, b) -> a)));
|
||||
.stream()
|
||||
.collect(Collectors
|
||||
.groupingBy(d -> d.getType().getId(),
|
||||
Collectors.toMap(
|
||||
Dimension::getId,
|
||||
Function.identity(),
|
||||
(a, b) -> a)));
|
||||
}
|
||||
}
|
||||
return permissionMapping != null;
|
||||
|
||||
@@ -55,7 +55,7 @@ public class SimpleAuthenticationBuilder implements AuthenticationBuilder {
|
||||
|
||||
@Override
|
||||
public AuthenticationBuilder role(List<Role> role) {
|
||||
authentication.getDimensions().addAll(role);
|
||||
authentication.addDimensions(role);
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ public class SimpleAuthenticationBuilder implements AuthenticationBuilder {
|
||||
|
||||
@Override
|
||||
public AuthenticationBuilder permission(List<Permission> permission) {
|
||||
authentication.setPermissions(permission);
|
||||
authentication.addPermissions(permission);
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -95,7 +95,7 @@ public class SimpleAuthenticationBuilder implements AuthenticationBuilder {
|
||||
}
|
||||
permissions.add(permission);
|
||||
}
|
||||
authentication.setPermissions(permissions);
|
||||
authentication.addPermissions(permissions);
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -106,13 +106,13 @@ public class SimpleAuthenticationBuilder implements AuthenticationBuilder {
|
||||
|
||||
@Override
|
||||
public AuthenticationBuilder attributes(String attributes) {
|
||||
authentication.getAttributes().putAll(JSON.<Map<String, Serializable>>parseObject(attributes, Map.class));
|
||||
authentication.putAttributes(JSON.<Map<String, Serializable>>parseObject(attributes, Map.class));
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuthenticationBuilder attributes(Map<String, Serializable> permission) {
|
||||
authentication.getAttributes().putAll(permission);
|
||||
authentication.putAttributes(permission);
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -137,7 +137,7 @@ public class SimpleAuthenticationBuilder implements AuthenticationBuilder {
|
||||
options
|
||||
));
|
||||
}
|
||||
authentication.setDimensions(dimensions);
|
||||
authentication.addDimensions(dimensions);
|
||||
|
||||
return this;
|
||||
|
||||
|
||||
@@ -550,6 +550,87 @@ class SimpleAuthenticationTest {
|
||||
assertTrue(userDimension.isPresent());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetDimensionsDeduplicatesUser() {
|
||||
authentication.setUser(user);
|
||||
// setDimensions 再次带入 user(及角色)时,user 维度不应被重复添加
|
||||
authentication.setDimensions(Arrays.asList(user, dimension2));
|
||||
|
||||
long userCount = authentication
|
||||
.getDimensions()
|
||||
.stream()
|
||||
.filter(d -> d.getId().equals(user.getId())
|
||||
&& d.getType().getId().equals(DefaultDimensionType.user.getId()))
|
||||
.count();
|
||||
assertEquals(1, userCount, "user 维度不应被重复添加");
|
||||
assertEquals(2, authentication.getDimensions().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAddDimensionReplacesExisting() {
|
||||
authentication.addDimension(SimpleDimension.of("org-1", "OLD", SimpleDimensionType.of("org"), null));
|
||||
// 相同 (type,id) 再次 add => 替换为最新(last-wins),不重复
|
||||
authentication.addDimension(SimpleDimension.of("org-1", "NEW", SimpleDimensionType.of("org"), null));
|
||||
|
||||
assertEquals(1, authentication.getDimensions().size());
|
||||
assertEquals("NEW", authentication.getDimension("org", "org-1").get().getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetDimensionsLastWins() {
|
||||
// 列表内相同 (type,id),后者覆盖前者
|
||||
authentication.setDimensions(Arrays.asList(
|
||||
SimpleDimension.of("org-1", "OLD", SimpleDimensionType.of("org"), null),
|
||||
SimpleDimension.of("org-1", "NEW", SimpleDimensionType.of("org"), null)));
|
||||
|
||||
assertEquals(1, authentication.getDimensions().size());
|
||||
assertEquals("NEW", authentication.getDimension("org", "org-1").get().getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testMergeKeepsSelfOnDuplicate() {
|
||||
// merge 保持 self 优先: 相同 (type,id) 时 self 的值不被 other 覆盖
|
||||
authentication.setDimensions(Collections.singletonList(
|
||||
SimpleDimension.of("org-1", "SELF", SimpleDimensionType.of("org"), null)));
|
||||
SimpleAuthentication other = new SimpleAuthentication();
|
||||
other.setDimensions(Collections.singletonList(
|
||||
SimpleDimension.of("org-1", "OTHER", SimpleDimensionType.of("org"), null)));
|
||||
|
||||
authentication.merge(other);
|
||||
|
||||
assertEquals(1, authentication.getDimensions().size());
|
||||
assertEquals("SELF", authentication.getDimension("org", "org-1").get().getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAddDimensionsDeduplicates() {
|
||||
authentication.addDimension(dimension1);
|
||||
authentication.addDimensions(Arrays.asList(dimension1, dimension2));
|
||||
|
||||
assertEquals(2, authentication.getDimensions().size());
|
||||
assertTrue(authentication.hasDimension("org", "org-1"));
|
||||
assertTrue(authentication.hasDimension("role", "role-1"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testMergeInvalidatesFastPathCache() {
|
||||
authentication.setUser(user);
|
||||
authentication.setDimensions(Collections.singletonList(dimension1));
|
||||
|
||||
// 触发 fastPath 缓存构建(第8次访问)
|
||||
for (int i = 0; i < 8; i++) {
|
||||
authentication.getDimension("org", "org-1");
|
||||
}
|
||||
|
||||
SimpleAuthentication other = new SimpleAuthentication();
|
||||
other.setDimensions(Collections.singletonList(dimension2));
|
||||
authentication.merge(other);
|
||||
|
||||
// merge 重建了 dimensions/permissions 列表,缓存须失效,新维度应可被查询到
|
||||
assertTrue(authentication.getDimension("role", "role-1").isPresent(),
|
||||
"merge 后新维度应可查询(fastPath 缓存已失效)");
|
||||
}
|
||||
|
||||
// ========== 性能测试 ==========
|
||||
|
||||
@Test
|
||||
|
||||
@@ -78,7 +78,7 @@ public class EmbedAuthenticationInfo {
|
||||
user.setUsername(username);
|
||||
user.setUserType(type);
|
||||
authentication.setUser(user);
|
||||
authentication.getDimensions().addAll(roles);
|
||||
authentication.addDimensions(roles);
|
||||
List<Permission> permissionList = new ArrayList<>();
|
||||
|
||||
permissionList.addAll(permissions.stream()
|
||||
|
||||
Reference in New Issue
Block a user