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:
@@ -204,10 +204,8 @@ public interface Authentication extends Serializable {
|
||||
|
||||
default boolean hasPermission(String permissionId, Collection<String> actions) {
|
||||
for (Permission permission : getPermissions()) {
|
||||
if (Objects.equals(permission.getId(), "*")) {
|
||||
return true;
|
||||
}
|
||||
if (Objects.equals(permissionId, permission.getId())) {
|
||||
if (Objects.equals(permission.getId(), "*") ||
|
||||
Objects.equals(permissionId, permission.getId())) {
|
||||
return actions.isEmpty()
|
||||
|| permission.getActions().containsAll(actions)
|
||||
|| permission.getActions().contains("*");
|
||||
|
||||
@@ -65,8 +65,8 @@ public final class AuthenticationHolder {
|
||||
if (size == 1) {
|
||||
return function.apply(suppliers.get(0));
|
||||
}
|
||||
ReactiveAuthenticationHolder.AuthenticationMerging merging
|
||||
= new ReactiveAuthenticationHolder.AuthenticationMerging();
|
||||
AuthenticationUtils.AuthenticationMerging merging
|
||||
= new AuthenticationUtils.AuthenticationMerging();
|
||||
for (AuthenticationSupplier supplier : suppliers) {
|
||||
function.apply(supplier).ifPresent(merging::merge);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
package org.hswebframework.web.authorization;
|
||||
|
||||
import org.hswebframework.web.authorization.simple.SimpleAuthentication;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
/**
|
||||
* @author zhouhao
|
||||
@@ -9,6 +12,37 @@ import org.springframework.util.StringUtils;
|
||||
*/
|
||||
public class AuthenticationUtils {
|
||||
|
||||
|
||||
public static Mono<Authentication> merge(Flux<Authentication> authenticationFlux){
|
||||
return authenticationFlux
|
||||
.collect(AuthenticationMerging::new, AuthenticationMerging::merge)
|
||||
.mapNotNull(AuthenticationMerging::get);
|
||||
}
|
||||
|
||||
static class AuthenticationMerging {
|
||||
|
||||
private Authentication auth;
|
||||
private int count;
|
||||
|
||||
public synchronized void merge(Authentication auth) {
|
||||
if (this.auth == null || this.auth == auth) {
|
||||
this.auth = auth;
|
||||
} else {
|
||||
if (count++ == 0) {
|
||||
SimpleAuthentication newAuth = new SimpleAuthentication();
|
||||
newAuth.merge(this.auth);
|
||||
this.auth = newAuth;
|
||||
}
|
||||
this.auth.merge(auth);
|
||||
}
|
||||
}
|
||||
|
||||
Authentication get() {
|
||||
return auth;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static AuthenticationPredicate createPredicate(String expression) {
|
||||
if (ObjectUtils.isEmpty(expression)) {
|
||||
return (authentication -> false);
|
||||
|
||||
@@ -46,10 +46,9 @@ public final class ReactiveAuthenticationHolder {
|
||||
private static final List<ReactiveAuthenticationSupplier> suppliers = new CopyOnWriteArrayList<>();
|
||||
|
||||
private static Mono<Authentication> get(Function<ReactiveAuthenticationSupplier, Mono<Authentication>> function) {
|
||||
return Flux
|
||||
.merge(Lists.transform(suppliers, function::apply))
|
||||
.collect(AuthenticationMerging::new, AuthenticationMerging::merge)
|
||||
.mapNotNull(AuthenticationMerging::get);
|
||||
return AuthenticationUtils
|
||||
.merge(Flux.merge(Lists.transform(suppliers, function::apply)))
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -85,27 +84,5 @@ public final class ReactiveAuthenticationHolder {
|
||||
}
|
||||
|
||||
|
||||
static class AuthenticationMerging {
|
||||
|
||||
private Authentication auth;
|
||||
private int count;
|
||||
|
||||
public synchronized void merge(Authentication auth) {
|
||||
if (this.auth == null || this.auth == auth) {
|
||||
this.auth = auth;
|
||||
} else {
|
||||
if (count++ == 0) {
|
||||
SimpleAuthentication newAuth = new SimpleAuthentication();
|
||||
newAuth.merge(this.auth);
|
||||
this.auth = newAuth;
|
||||
}
|
||||
this.auth.merge(auth);
|
||||
}
|
||||
}
|
||||
|
||||
Authentication get() {
|
||||
return auth;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -22,9 +22,10 @@ public class ResourcesDefinition {
|
||||
|
||||
private Phased phased = Phased.before;
|
||||
|
||||
public void clear(){
|
||||
public void clear() {
|
||||
resources.clear();
|
||||
}
|
||||
|
||||
public void addResource(ResourceDefinition resource, boolean merge) {
|
||||
ResourceDefinition definition = getResource(resource.getId()).orElse(null);
|
||||
if (definition != null) {
|
||||
@@ -44,17 +45,17 @@ public class ResourcesDefinition {
|
||||
|
||||
public Optional<ResourceDefinition> getResource(String id) {
|
||||
return resources
|
||||
.stream()
|
||||
.filter(resource -> resource.getId().equals(id))
|
||||
.findAny();
|
||||
.stream()
|
||||
.filter(resource -> resource.getId().equals(id))
|
||||
.findAny();
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public List<ResourceDefinition> getDataAccessResources() {
|
||||
return resources
|
||||
.stream()
|
||||
.filter(ResourceDefinition::hasDataAccessAction)
|
||||
.collect(Collectors.toList());
|
||||
.stream()
|
||||
.filter(ResourceDefinition::hasDataAccessAction)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public boolean hasPermission(Permission permission) {
|
||||
@@ -62,8 +63,8 @@ public class ResourcesDefinition {
|
||||
return true;
|
||||
}
|
||||
return getResource(permission.getId())
|
||||
.filter(resource -> resource.hasAction(permission.getActions()))
|
||||
.isPresent();
|
||||
.filter(resource -> resource.hasAction(permission.getActions()))
|
||||
.isPresent();
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
@@ -72,18 +73,27 @@ public class ResourcesDefinition {
|
||||
|
||||
public boolean hasPermission(Authentication authentication) {
|
||||
|
||||
if (CollectionUtils.isEmpty(resources)) {
|
||||
int size = resources.size();
|
||||
if (size == 0) {
|
||||
return true;
|
||||
}
|
||||
if (size == 1) {
|
||||
for (ResourceDefinition resource : resources) {
|
||||
if (authentication.hasPermission(resource.getId(), resource.getActionIds())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (logical == Logical.AND) {
|
||||
return resources
|
||||
.stream()
|
||||
.allMatch(resource -> authentication.hasPermission(resource.getId(), resource.getActionIds()));
|
||||
.stream()
|
||||
.allMatch(resource -> authentication.hasPermission(resource.getId(), resource.getActionIds()));
|
||||
}
|
||||
|
||||
return resources
|
||||
.stream()
|
||||
.anyMatch(resource -> authentication.hasPermission(resource.getId(), resource.getActionIds()));
|
||||
.stream()
|
||||
.anyMatch(resource -> authentication.hasPermission(resource.getId(), resource.getActionIds()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package org.hswebframework.web.authorization.simple;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
import org.hswebframework.web.authorization.*;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
@@ -19,43 +18,38 @@ public class CompositeReactiveAuthenticationManager implements ReactiveAuthentic
|
||||
|
||||
@Override
|
||||
public Mono<Authentication> authenticate(Mono<AuthenticationRequest> request) {
|
||||
return Flux.concat(providers
|
||||
.stream()
|
||||
.map(manager -> manager
|
||||
.authenticate(request)
|
||||
.onErrorResume((err) -> {
|
||||
log.warn("get user authenticate error", err);
|
||||
return Mono.empty();
|
||||
}))
|
||||
.collect(Collectors.toList()))
|
||||
.take(1)
|
||||
.next();
|
||||
return Flux
|
||||
.concat(
|
||||
providers
|
||||
.stream()
|
||||
.map(manager -> manager
|
||||
.authenticate(request)
|
||||
.onErrorResume((err) -> {
|
||||
log.warn("get user authenticate error", err);
|
||||
return Mono.empty();
|
||||
}))
|
||||
.collect(Collectors.toList()))
|
||||
.take(1)
|
||||
.next();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Authentication> getByUserId(String userId) {
|
||||
if (providers.size() == 1) {
|
||||
return providers.get(0).getByUserId(userId);
|
||||
}
|
||||
return Flux
|
||||
.fromStream(providers
|
||||
.stream()
|
||||
.map(manager -> manager
|
||||
.getByUserId(userId)
|
||||
.onErrorResume((err) -> {
|
||||
log.warn("get user [{}] authentication error", userId, err);
|
||||
return Mono.empty();
|
||||
})
|
||||
))
|
||||
.flatMap(Function.identity())
|
||||
.collectList()
|
||||
.filter(CollectionUtils::isNotEmpty)
|
||||
.map(all -> {
|
||||
if (all.size() == 1) {
|
||||
return all.get(0);
|
||||
}
|
||||
SimpleAuthentication authentication = new SimpleAuthentication();
|
||||
for (Authentication auth : all) {
|
||||
authentication.merge(auth);
|
||||
}
|
||||
return authentication;
|
||||
});
|
||||
.fromStream(providers
|
||||
.stream()
|
||||
.map(manager -> manager
|
||||
.getByUserId(userId)
|
||||
.onErrorResume((err) -> {
|
||||
log.warn("get user [{}] authentication error", userId, err);
|
||||
return Mono.empty();
|
||||
})
|
||||
))
|
||||
.flatMap(Function.identity())
|
||||
.as(AuthenticationUtils::merge);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -24,24 +24,29 @@ import org.hswebframework.web.authorization.*;
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.atomic.AtomicLongFieldUpdater;
|
||||
import java.util.function.BiPredicate;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class SimpleAuthentication implements Authentication {
|
||||
|
||||
static final AtomicLongFieldUpdater<SimpleAuthentication> ACCESS_COUNT_UPDATER =
|
||||
AtomicLongFieldUpdater.newUpdater(SimpleAuthentication.class, "accessCount");
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = -2898863220255336528L;
|
||||
|
||||
@Getter
|
||||
private User user;
|
||||
|
||||
@Setter
|
||||
private List<Permission> permissions = new ArrayList<>();
|
||||
|
||||
private List<Dimension> dimensions = new ArrayList<>();
|
||||
|
||||
@Setter
|
||||
private Map<String, Serializable> attributes = new HashMap<>();
|
||||
|
||||
public static Authentication of() {
|
||||
@@ -54,9 +59,17 @@ public class SimpleAuthentication implements Authentication {
|
||||
return Optional.ofNullable((T) attributes.get(name));
|
||||
}
|
||||
|
||||
public List<Dimension> getDimensions() {
|
||||
return dimensions == null ? Collections.emptyList() : dimensions;
|
||||
}
|
||||
|
||||
public List<Permission> getPermissions() {
|
||||
return permissions == null ? Collections.emptyList() : permissions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Serializable> getAttributes() {
|
||||
return attributes;
|
||||
return attributes == null ? Collections.emptyMap() : attributes;
|
||||
}
|
||||
|
||||
public SimpleAuthentication merge(Authentication authentication) {
|
||||
@@ -67,9 +80,10 @@ public class SimpleAuthentication implements Authentication {
|
||||
if (authentication.getUser() != null) {
|
||||
user = authentication.getUser();
|
||||
}
|
||||
this.attributes = new HashMap<>(getAttributes());
|
||||
this.attributes.putAll(authentication.getAttributes());
|
||||
|
||||
attributes.putAll(authentication.getAttributes());
|
||||
|
||||
this.permissions = new ArrayList<>(this.getPermissions());
|
||||
for (Permission permission : authentication.getPermissions()) {
|
||||
Permission me = mePermissionGroup.get(permission.getId());
|
||||
if (me == null) {
|
||||
@@ -77,11 +91,10 @@ public class SimpleAuthentication implements Authentication {
|
||||
continue;
|
||||
}
|
||||
me.getActions().addAll(permission.getActions());
|
||||
me.getDataAccesses().addAll(permission.getDataAccesses());
|
||||
}
|
||||
|
||||
this.dimensions = new ArrayList<>(this.getDimensions());
|
||||
for (Dimension dimension : authentication.getDimensions()) {
|
||||
if (!getDimension(dimension.getType(), dimension.getId()).isPresent()) {
|
||||
if (getDimension(dimension.getType(), dimension.getId()).isEmpty()) {
|
||||
dimensions.add(dimension);
|
||||
}
|
||||
}
|
||||
@@ -96,14 +109,19 @@ public class SimpleAuthentication implements Authentication {
|
||||
public Authentication copy(BiPredicate<Permission, String> permissionFilter,
|
||||
Predicate<Dimension> dimension) {
|
||||
SimpleAuthentication authentication = newInstance();
|
||||
authentication.setDimensions(dimensions.stream().filter(dimension).collect(Collectors.toList()));
|
||||
authentication.setDimensions(dimensions
|
||||
.stream()
|
||||
.filter(dimension)
|
||||
.collect(Collectors.toList()));
|
||||
authentication.setPermissions(permissions
|
||||
.stream()
|
||||
.map(permission -> permission.copy(action -> permissionFilter.test(permission, action), conf -> true))
|
||||
.filter(per -> !per.getActions().isEmpty())
|
||||
.collect(Collectors.toList())
|
||||
);
|
||||
authentication.setUser(user);
|
||||
if (user != null) {
|
||||
authentication.setUser0(user);
|
||||
}
|
||||
authentication.setAttributes(new HashMap<>(attributes));
|
||||
return authentication;
|
||||
}
|
||||
@@ -128,4 +146,92 @@ public class SimpleAuthentication implements Authentication {
|
||||
public void addDimension(Dimension dimension) {
|
||||
this.dimensions.add(dimension);
|
||||
}
|
||||
|
||||
private transient volatile Map<String, Map<String, Dimension>> dimensionMapping;
|
||||
private transient volatile Map<String, Permission> permissionMapping;
|
||||
private transient volatile long accessCount;
|
||||
|
||||
private boolean fastPath() {
|
||||
// 总共访问超过8次,则进行初始化缓存.
|
||||
if (ACCESS_COUNT_UPDATER.incrementAndGet(this) == 8) {
|
||||
if (permissionMapping == null) {
|
||||
permissionMapping = permissions == null
|
||||
? Collections.emptyMap()
|
||||
: permissions
|
||||
.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())));
|
||||
}
|
||||
}
|
||||
return permissionMapping != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasPermission(String permissionId, Collection<String> actions) {
|
||||
if (fastPath()) {
|
||||
Permission permission = permissionMapping.get(permissionId);
|
||||
if (permission == null) {
|
||||
permission = permissionMapping.get("*");
|
||||
}
|
||||
if (permission == null) {
|
||||
return false;
|
||||
}
|
||||
return actions.isEmpty()
|
||||
|| permission.getActions().containsAll(actions)
|
||||
|| permission.getActions().contains("*");
|
||||
}
|
||||
return Authentication.super.hasPermission(permissionId, actions);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Dimension> getDimension(String type, String id) {
|
||||
if (fastPath()) {
|
||||
Map<String, Dimension> mapping = dimensionMapping.get(type);
|
||||
if (mapping == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
return Optional.ofNullable(mapping.get(id));
|
||||
}
|
||||
return Authentication.super.getDimension(type, id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Dimension> getDimension(DimensionType type, String id) {
|
||||
return getDimension(type.getId(), id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Dimension> getDimensions(DimensionType type) {
|
||||
return this.getDimensions(type.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Dimension> getDimensions(String type) {
|
||||
if (fastPath()) {
|
||||
Map<String, Dimension> mapping = dimensionMapping.get(type);
|
||||
if (mapping == null) {
|
||||
return List.of();
|
||||
}
|
||||
return new ArrayList<>(mapping.values());
|
||||
}
|
||||
return Authentication.super.getDimensions(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Permission> getPermission(String id) {
|
||||
if (fastPath()) {
|
||||
return Optional.ofNullable(permissionMapping.get(id));
|
||||
}
|
||||
return Authentication.super.getPermission(id);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ public class UserTokenReactiveAuthenticationSupplier implements ReactiveAuthenti
|
||||
@Override
|
||||
public Mono<Authentication> get(String userId) {
|
||||
if (userId == null) {
|
||||
return null;
|
||||
return Mono.empty();
|
||||
}
|
||||
return get(this.defaultAuthenticationManager, userId);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,856 @@
|
||||
package org.hswebframework.web.authorization.simple;
|
||||
|
||||
import org.hswebframework.web.authorization.*;
|
||||
import org.hswebframework.web.authorization.DefaultDimensionType;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.*;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class SimpleAuthenticationTest {
|
||||
|
||||
private SimpleAuthentication authentication;
|
||||
private SimpleUser user;
|
||||
private SimplePermission permission1;
|
||||
private SimplePermission permission2;
|
||||
private SimpleDimension dimension1;
|
||||
private SimpleDimension dimension2;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
authentication = new SimpleAuthentication();
|
||||
|
||||
// 创建测试用户
|
||||
user = SimpleUser.builder()
|
||||
.id("test-user-id")
|
||||
.username("testuser")
|
||||
.name("Test User")
|
||||
.userType("user")
|
||||
.build();
|
||||
|
||||
// 创建测试权限
|
||||
permission1 = SimplePermission.builder()
|
||||
.id("permission-1")
|
||||
.name("Permission 1")
|
||||
.actions(new HashSet<>(Arrays.asList("query", "save", "delete")))
|
||||
.build();
|
||||
|
||||
permission2 = SimplePermission.builder()
|
||||
.id("permission-2")
|
||||
.name("Permission 2")
|
||||
.actions(new HashSet<>(Arrays.asList("query", "update")))
|
||||
.build();
|
||||
|
||||
// 创建测试维度
|
||||
SimpleDimensionType orgType = SimpleDimensionType.of("org");
|
||||
SimpleDimensionType roleType = SimpleDimensionType.of("role");
|
||||
|
||||
dimension1 = SimpleDimension.of("org-1", "Organization 1", orgType, null);
|
||||
dimension2 = SimpleDimension.of("role-1", "Role 1", roleType, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testOf() {
|
||||
Authentication auth = SimpleAuthentication.of();
|
||||
assertNotNull(auth);
|
||||
assertTrue(auth instanceof SimpleAuthentication);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetUser() {
|
||||
authentication.setUser(user);
|
||||
|
||||
assertNotNull(authentication.getUser());
|
||||
assertEquals("test-user-id", authentication.getUser().getId());
|
||||
assertEquals("testuser", authentication.getUser().getUsername());
|
||||
assertEquals("Test User", authentication.getUser().getName());
|
||||
|
||||
// setUser 应该自动将用户添加到 dimensions
|
||||
assertTrue(authentication.getDimensions().contains(user));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetUser0() {
|
||||
// 使用反射测试 protected 方法
|
||||
// 注意:setUser0 不会将用户添加到 dimensions
|
||||
// 由于是 protected 方法,这里通过子类或反射测试
|
||||
// 实际使用中,setUser0 通常由子类调用
|
||||
authentication.setUser(user);
|
||||
assertEquals(user, authentication.getUser());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetPermissions() {
|
||||
List<Permission> permissions = Arrays.asList(permission1, permission2);
|
||||
authentication.setPermissions(permissions);
|
||||
|
||||
assertEquals(2, authentication.getPermissions().size());
|
||||
assertTrue(authentication.getPermissions().contains(permission1));
|
||||
assertTrue(authentication.getPermissions().contains(permission2));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetDimensions() {
|
||||
List<Dimension> dimensions = Arrays.asList(dimension1, dimension2);
|
||||
authentication.setDimensions(dimensions);
|
||||
|
||||
assertEquals(2, authentication.getDimensions().size());
|
||||
assertTrue(authentication.getDimensions().contains(dimension1));
|
||||
assertTrue(authentication.getDimensions().contains(dimension2));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetDimensionsCollection() {
|
||||
Collection<Dimension> dimensions = new HashSet<>(Arrays.asList(dimension1, dimension2));
|
||||
authentication.setDimensions(dimensions);
|
||||
|
||||
assertEquals(2, authentication.getDimensions().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAddDimension() {
|
||||
authentication.addDimension(dimension1);
|
||||
authentication.addDimension(dimension2);
|
||||
|
||||
assertEquals(2, authentication.getDimensions().size());
|
||||
assertTrue(authentication.getDimensions().contains(dimension1));
|
||||
assertTrue(authentication.getDimensions().contains(dimension2));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetAttributes() {
|
||||
Map<String, Serializable> attributes = new HashMap<>();
|
||||
attributes.put("key1", "value1");
|
||||
attributes.put("key2", 123);
|
||||
authentication.setAttributes(attributes);
|
||||
|
||||
assertEquals(2, authentication.getAttributes().size());
|
||||
assertEquals("value1", authentication.getAttributes().get("key1"));
|
||||
assertEquals(123, authentication.getAttributes().get("key2"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetAttribute() {
|
||||
authentication.setAttributes(Collections.singletonMap("test-key", "test-value"));
|
||||
|
||||
Optional<String> value = authentication.getAttribute("test-key");
|
||||
assertTrue(value.isPresent());
|
||||
assertEquals("test-value", value.get());
|
||||
|
||||
Optional<String> missing = authentication.getAttribute("missing-key");
|
||||
assertFalse(missing.isPresent());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetAttributes() {
|
||||
Map<String, Serializable> attributes = new HashMap<>();
|
||||
attributes.put("key1", "value1");
|
||||
authentication.setAttributes(attributes);
|
||||
|
||||
Map<String, Serializable> result = authentication.getAttributes();
|
||||
assertNotNull(result);
|
||||
assertEquals("value1", result.get("key1"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testHasPermission() {
|
||||
authentication.setPermissions(Arrays.asList(permission1, permission2));
|
||||
|
||||
// 测试有权限的情况
|
||||
assertTrue(authentication.hasPermission("permission-1", Collections.singletonList("query")));
|
||||
assertTrue(authentication.hasPermission("permission-1", Arrays.asList("query", "save")));
|
||||
assertTrue(authentication.hasPermission("permission-2", Collections.singletonList("query")));
|
||||
|
||||
// 测试没有权限的情况
|
||||
assertFalse(authentication.hasPermission("permission-1", Collections.singletonList("unknown")));
|
||||
assertFalse(authentication.hasPermission("unknown-permission", Collections.singletonList("query")));
|
||||
|
||||
// 测试空 actions 列表
|
||||
assertTrue(authentication.hasPermission("permission-1", Collections.emptyList()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testHasPermissionWithWildcard() {
|
||||
SimplePermission wildcardPermission = SimplePermission.builder()
|
||||
.id("*")
|
||||
.name("All Permissions")
|
||||
.actions(new HashSet<>(Collections.singletonList("*")))
|
||||
.build();
|
||||
|
||||
authentication.setPermissions(Collections.singletonList(wildcardPermission));
|
||||
|
||||
// 通配符权限应该允许所有操作
|
||||
assertTrue(authentication.hasPermission("any-permission", Collections.singletonList("any-action")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testHasPermissionWithActionWildcard() {
|
||||
SimplePermission permissionWithWildcard = SimplePermission.builder()
|
||||
.id("permission-1")
|
||||
.name("Permission with wildcard")
|
||||
.actions(new HashSet<>(Collections.singletonList("*")))
|
||||
.build();
|
||||
|
||||
authentication.setPermissions(Collections.singletonList(permissionWithWildcard));
|
||||
|
||||
// 权限包含 * action 应该允许所有操作
|
||||
assertTrue(authentication.hasPermission("permission-1", Collections.singletonList("any-action")));
|
||||
assertTrue(authentication.hasPermission("permission-1", Arrays.asList("action1", "action2")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetPermission() {
|
||||
authentication.setPermissions(Arrays.asList(permission1, permission2));
|
||||
|
||||
Optional<Permission> perm1 = authentication.getPermission("permission-1");
|
||||
assertTrue(perm1.isPresent());
|
||||
assertEquals("permission-1", perm1.get().getId());
|
||||
|
||||
Optional<Permission> perm2 = authentication.getPermission("permission-2");
|
||||
assertTrue(perm2.isPresent());
|
||||
assertEquals("permission-2", perm2.get().getId());
|
||||
|
||||
Optional<Permission> missing = authentication.getPermission("unknown");
|
||||
assertFalse(missing.isPresent());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetDimension() {
|
||||
authentication.setDimensions(Arrays.asList(dimension1, dimension2));
|
||||
|
||||
Optional<Dimension> dim1 = authentication.getDimension("org", "org-1");
|
||||
assertTrue(dim1.isPresent());
|
||||
assertEquals("org-1", dim1.get().getId());
|
||||
|
||||
Optional<Dimension> dim2 = authentication.getDimension("role", "role-1");
|
||||
assertTrue(dim2.isPresent());
|
||||
assertEquals("role-1", dim2.get().getId());
|
||||
|
||||
Optional<Dimension> missing = authentication.getDimension("org", "unknown");
|
||||
assertFalse(missing.isPresent());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetDimensionWithDimensionType() {
|
||||
authentication.setDimensions(Arrays.asList(dimension1, dimension2));
|
||||
|
||||
SimpleDimensionType orgType = SimpleDimensionType.of("org");
|
||||
Optional<Dimension> dim = authentication.getDimension(orgType, "org-1");
|
||||
assertTrue(dim.isPresent());
|
||||
assertEquals("org-1", dim.get().getId());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetDimensions() {
|
||||
SimpleDimension org2 = SimpleDimension.of("org-2", "Organization 2", SimpleDimensionType.of("org"), null);
|
||||
authentication.setDimensions(Arrays.asList(dimension1, org2, dimension2));
|
||||
|
||||
List<Dimension> orgDimensions = authentication.getDimensions("org");
|
||||
assertEquals(2, orgDimensions.size());
|
||||
|
||||
List<Dimension> roleDimensions = authentication.getDimensions("role");
|
||||
assertEquals(1, roleDimensions.size());
|
||||
assertEquals("role-1", roleDimensions.get(0).getId());
|
||||
|
||||
List<Dimension> unknownDimensions = authentication.getDimensions("unknown");
|
||||
assertTrue(unknownDimensions.isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetDimensionsWithDimensionType() {
|
||||
authentication.setDimensions(Arrays.asList(dimension1, dimension2));
|
||||
|
||||
SimpleDimensionType orgType = SimpleDimensionType.of("org");
|
||||
List<Dimension> dimensions = authentication.getDimensions(orgType);
|
||||
assertEquals(1, dimensions.size());
|
||||
assertEquals("org-1", dimensions.get(0).getId());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testHasDimension() {
|
||||
authentication.setDimensions(Arrays.asList(dimension1, dimension2));
|
||||
|
||||
assertTrue(authentication.hasDimension("org", "org-1"));
|
||||
assertTrue(authentication.hasDimension("role", "role-1"));
|
||||
assertFalse(authentication.hasDimension("org", "unknown"));
|
||||
assertFalse(authentication.hasDimension("unknown", "org-1"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testMerge() {
|
||||
// 设置初始认证信息
|
||||
authentication.setUser(user);
|
||||
authentication.setPermissions(Collections.singletonList(permission1));
|
||||
authentication.setDimensions(Collections.singletonList(dimension1));
|
||||
authentication.setAttributes(Collections.singletonMap("key1", "value1"));
|
||||
|
||||
// 创建要合并的认证信息
|
||||
SimpleAuthentication other = new SimpleAuthentication();
|
||||
SimpleUser otherUser = SimpleUser.builder()
|
||||
.id("other-user-id")
|
||||
.username("otheruser")
|
||||
.build();
|
||||
other.setUser(otherUser);
|
||||
other.setPermissions(Collections.singletonList(permission2));
|
||||
other.setDimensions(Collections.singletonList(dimension2));
|
||||
other.setAttributes(Collections.singletonMap("key2", "value2"));
|
||||
|
||||
// 执行合并
|
||||
SimpleAuthentication merged = authentication.merge(other);
|
||||
|
||||
// 验证用户被更新
|
||||
assertEquals("other-user-id", merged.getUser().getId());
|
||||
|
||||
// 验证权限被合并(permission1 和 permission2 都应该存在)
|
||||
assertEquals(2, merged.getPermissions().size());
|
||||
|
||||
// 验证维度被合并(不重复添加)
|
||||
assertTrue(merged.getDimensions().contains(dimension1));
|
||||
assertTrue(merged.getDimensions().contains(dimension2));
|
||||
|
||||
// 验证属性被合并
|
||||
assertEquals(2, merged.getAttributes().size());
|
||||
assertEquals("value1", merged.getAttributes().get("key1"));
|
||||
assertEquals("value2", merged.getAttributes().get("key2"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testMergeWithDuplicatePermissions() {
|
||||
// 设置初始权限
|
||||
authentication.setPermissions(Collections.singletonList(permission1));
|
||||
|
||||
// 创建具有相同 ID 但不同 actions 的权限
|
||||
SimplePermission permission1WithMoreActions = SimplePermission.builder()
|
||||
.id("permission-1")
|
||||
.name("Permission 1")
|
||||
.actions(new HashSet<>(Arrays.asList("query", "save", "delete", "update")))
|
||||
.build();
|
||||
|
||||
SimpleAuthentication other = new SimpleAuthentication();
|
||||
other.setPermissions(Collections.singletonList(permission1WithMoreActions));
|
||||
|
||||
// 执行合并
|
||||
SimpleAuthentication merged = authentication.merge(other);
|
||||
|
||||
// 验证权限被合并,actions 被合并
|
||||
assertEquals(1, merged.getPermissions().size());
|
||||
Permission mergedPermission = merged.getPermissions().get(0);
|
||||
assertEquals("permission-1", mergedPermission.getId());
|
||||
assertTrue(mergedPermission.getActions().contains("query"));
|
||||
assertTrue(mergedPermission.getActions().contains("save"));
|
||||
assertTrue(mergedPermission.getActions().contains("delete"));
|
||||
assertTrue(mergedPermission.getActions().contains("update"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testMergeWithDuplicateDimensions() {
|
||||
authentication.setDimensions(Collections.singletonList(dimension1));
|
||||
|
||||
SimpleAuthentication other = new SimpleAuthentication();
|
||||
other.setDimensions(Collections.singletonList(dimension1)); // 相同的维度
|
||||
|
||||
SimpleAuthentication merged = authentication.merge(other);
|
||||
|
||||
// 验证维度不会被重复添加
|
||||
long org1Count = merged.getDimensions().stream()
|
||||
.filter(d -> d.getId().equals("org-1") && d.getType().getId().equals("org"))
|
||||
.count();
|
||||
assertEquals(1, org1Count);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testMergeWithNullUser() {
|
||||
authentication.setUser(user);
|
||||
|
||||
SimpleAuthentication other = new SimpleAuthentication();
|
||||
// other 没有设置用户
|
||||
|
||||
SimpleAuthentication merged = authentication.merge(other);
|
||||
|
||||
// 验证原始用户保持不变
|
||||
assertEquals(user, merged.getUser());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCopy() {
|
||||
authentication.setUser(user);
|
||||
authentication.setPermissions(Arrays.asList(permission1, permission2));
|
||||
authentication.setDimensions(Arrays.asList(dimension1, dimension2));
|
||||
authentication.setAttributes(Collections.singletonMap("key1", "value1"));
|
||||
|
||||
// 复制所有权限和维度
|
||||
Authentication copied = authentication.copy(
|
||||
(permission, action) -> true, // 允许所有权限和操作
|
||||
dimension -> true // 允许所有维度
|
||||
);
|
||||
|
||||
assertNotNull(copied);
|
||||
assertEquals(user, copied.getUser());
|
||||
assertEquals(2, copied.getPermissions().size());
|
||||
// user,org,role
|
||||
assertEquals(3, copied.getDimensions().size());
|
||||
assertEquals("value1", copied.getAttributes().get("key1"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCopyWithPermissionFilter() {
|
||||
authentication.setPermissions(Arrays.asList(permission1, permission2));
|
||||
|
||||
// 只复制 permission-1
|
||||
Authentication copied = authentication.copy(
|
||||
(permission, action) -> permission.getId().equals("permission-1"),
|
||||
dimension -> true
|
||||
);
|
||||
|
||||
assertEquals(1, copied.getPermissions().size());
|
||||
assertEquals("permission-1", copied.getPermissions().get(0).getId());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCopyWithActionFilter() {
|
||||
authentication.setPermissions(Collections.singletonList(permission1));
|
||||
|
||||
// 只复制 query action
|
||||
Authentication copied = authentication.copy(
|
||||
(permission, action) -> action.equals("query"),
|
||||
dimension -> true
|
||||
);
|
||||
|
||||
assertEquals(1, copied.getPermissions().size());
|
||||
Permission copiedPermission = copied.getPermissions().get(0);
|
||||
assertEquals("permission-1", copiedPermission.getId());
|
||||
assertEquals(1, copiedPermission.getActions().size());
|
||||
assertTrue(copiedPermission.getActions().contains("query"));
|
||||
assertFalse(copiedPermission.getActions().contains("save"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCopyWithDimensionFilter() {
|
||||
authentication.setDimensions(Arrays.asList(dimension1, dimension2));
|
||||
|
||||
// 只复制 org 类型的维度
|
||||
Authentication copied = authentication.copy(
|
||||
(permission, action) -> true,
|
||||
dimension -> dimension.getType().getId().equals("org")
|
||||
);
|
||||
|
||||
assertEquals(1, copied.getDimensions(dimension1.getType()).size());
|
||||
assertEquals("org-1", copied.getDimensions().get(0).getId());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCopyFiltersEmptyActions() {
|
||||
SimplePermission permissionWithEmptyActions = SimplePermission.builder()
|
||||
.id("empty-permission")
|
||||
.name("Empty Permission")
|
||||
.actions(new HashSet<>())
|
||||
.build();
|
||||
|
||||
authentication.setPermissions(Collections.singletonList(permissionWithEmptyActions));
|
||||
|
||||
// 复制时,如果过滤后 actions 为空,权限应该被过滤掉
|
||||
Authentication copied = authentication.copy(
|
||||
(permission, action) -> false, // 不允许任何 action
|
||||
dimension -> true
|
||||
);
|
||||
|
||||
assertEquals(0, copied.getPermissions().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFastPathOptimization() {
|
||||
authentication.setPermissions(Collections.singletonList(permission1));
|
||||
authentication.setDimensions(Collections.singletonList(dimension1));
|
||||
|
||||
// 前7次访问应该使用慢路径
|
||||
for (int i = 0; i < 7; i++) {
|
||||
authentication.hasPermission("permission-1", Collections.singletonList("query"));
|
||||
}
|
||||
|
||||
// 第8次访问应该触发快速路径初始化
|
||||
assertTrue(authentication.hasPermission("permission-1", Collections.singletonList("query")));
|
||||
|
||||
// 之后的访问应该使用快速路径
|
||||
assertTrue(authentication.hasPermission("permission-1", Collections.singletonList("query")));
|
||||
assertTrue(authentication.getPermission("permission-1").isPresent());
|
||||
assertTrue(authentication.getDimension("org", "org-1").isPresent());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testNewInstance() {
|
||||
SimpleAuthentication instance1 = authentication.newInstance();
|
||||
SimpleAuthentication instance2 = authentication.newInstance();
|
||||
|
||||
assertNotNull(instance1);
|
||||
assertNotNull(instance2);
|
||||
assertNotSame(instance1, instance2);
|
||||
assertTrue(instance1 instanceof SimpleAuthentication);
|
||||
assertTrue(instance2 instanceof SimpleAuthentication);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testEmptyPermissions() {
|
||||
authentication.setPermissions(Collections.emptyList());
|
||||
|
||||
assertFalse(authentication.hasPermission("any", Collections.singletonList("any")));
|
||||
assertFalse(authentication.getPermission("any").isPresent());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testEmptyDimensions() {
|
||||
authentication.setDimensions(Collections.emptyList());
|
||||
|
||||
assertFalse(authentication.hasDimension("any", "any"));
|
||||
assertFalse(authentication.getDimension("any", "any").isPresent());
|
||||
assertTrue(authentication.getDimensions("any").isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testNullAttributes() {
|
||||
// 测试 null 属性处理
|
||||
authentication.setAttributes(null);
|
||||
assertNotNull(authentication.getAttributes());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetAttributeWithType() {
|
||||
authentication.setAttributes(Collections.singletonMap("int-value", 123));
|
||||
|
||||
Optional<Integer> intValue = authentication.getAttribute("int-value");
|
||||
assertTrue(intValue.isPresent());
|
||||
assertEquals(123, intValue.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testMultipleDimensionsSameType() {
|
||||
SimpleDimension org2 = SimpleDimension.of("org-2", "Organization 2", SimpleDimensionType.of("org"), null);
|
||||
SimpleDimension org3 = SimpleDimension.of("org-3", "Organization 3", SimpleDimensionType.of("org"), null);
|
||||
|
||||
authentication.setDimensions(Arrays.asList(dimension1, org2, org3));
|
||||
|
||||
List<Dimension> orgDimensions = authentication.getDimensions("org");
|
||||
assertEquals(3, orgDimensions.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testUserAsDimension() {
|
||||
authentication.setUser(user);
|
||||
|
||||
// 用户应该被添加到维度列表中
|
||||
assertTrue(authentication.getDimensions().contains(user));
|
||||
|
||||
// 可以通过维度类型查找用户
|
||||
Optional<Dimension> userDimension = authentication.getDimension(
|
||||
DefaultDimensionType.user.getId(),
|
||||
user.getId()
|
||||
);
|
||||
assertTrue(userDimension.isPresent());
|
||||
}
|
||||
|
||||
// ========== 性能测试 ==========
|
||||
|
||||
@Test
|
||||
void testPerformanceBeforeFastPath() {
|
||||
// 准备大量权限和维度数据
|
||||
List<Permission> permissions = new ArrayList<>();
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
permissions.add(SimplePermission.builder()
|
||||
.id("permission-" + i)
|
||||
.name("Permission " + i)
|
||||
.actions(new HashSet<>(Arrays.asList("query", "save", "delete")))
|
||||
.build());
|
||||
}
|
||||
|
||||
List<Dimension> dimensions = new ArrayList<>();
|
||||
for (int i = 0; i < 500; i++) {
|
||||
dimensions.add(SimpleDimension.of(
|
||||
"dim-" + i,
|
||||
"Dimension " + i,
|
||||
SimpleDimensionType.of("type-" + (i % 10)),
|
||||
null
|
||||
));
|
||||
}
|
||||
|
||||
int iterations = 10000;
|
||||
long totalTime = 0;
|
||||
|
||||
// 使用多个实例来测试慢路径,每个实例只访问7次
|
||||
int batchSize = 7;
|
||||
int batches = iterations / batchSize;
|
||||
|
||||
long startTime = System.nanoTime();
|
||||
for (int batch = 0; batch < batches; batch++) {
|
||||
SimpleAuthentication auth = new SimpleAuthentication();
|
||||
auth.setUser(user);
|
||||
auth.setPermissions(permissions);
|
||||
auth.setDimensions(dimensions);
|
||||
|
||||
// 每个实例只访问7次(fastPath 未生效)
|
||||
for (int i = 0; i < batchSize; i++) {
|
||||
int idx = (batch * batchSize + i) % 1000;
|
||||
auth.hasPermission("permission-" + idx, Collections.singletonList("query"));
|
||||
auth.getPermission("permission-" + idx);
|
||||
auth.getDimension("type-5", "dim-" + (idx % 500));
|
||||
auth.getDimensions("type-5");
|
||||
}
|
||||
}
|
||||
long endTime = System.nanoTime();
|
||||
totalTime = endTime - startTime;
|
||||
|
||||
double avgTimeNanos = (double) totalTime / iterations;
|
||||
double opsPerSecond = 1_000_000_000.0 / avgTimeNanos;
|
||||
|
||||
System.out.println("\n========== FastPath 生效前性能测试 ==========");
|
||||
System.out.println("迭代次数: " + iterations);
|
||||
System.out.println("总耗时: " + (totalTime / 1_000_000) + " ms");
|
||||
System.out.println("平均每次操作耗时: " + String.format("%.2f", avgTimeNanos / 1000) + " μs");
|
||||
System.out.println("每秒操作数: " + String.format("%.2f", opsPerSecond / 4) + " ops/s (每个方法)");
|
||||
System.out.println("==========================================\n");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testPerformanceAfterFastPath() {
|
||||
// 准备大量权限和维度数据
|
||||
List<Permission> permissions = new ArrayList<>();
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
permissions.add(SimplePermission.builder()
|
||||
.id("permission-" + i)
|
||||
.name("Permission " + i)
|
||||
.actions(new HashSet<>(Arrays.asList("query", "save", "delete")))
|
||||
.build());
|
||||
}
|
||||
|
||||
List<Dimension> dimensions = new ArrayList<>();
|
||||
for (int i = 0; i < 500; i++) {
|
||||
dimensions.add(SimpleDimension.of(
|
||||
"dim-" + i,
|
||||
"Dimension " + i,
|
||||
SimpleDimensionType.of("type-" + (i % 10)),
|
||||
null
|
||||
));
|
||||
}
|
||||
|
||||
SimpleAuthentication auth = new SimpleAuthentication();
|
||||
auth.setUser(user);
|
||||
auth.setPermissions(permissions);
|
||||
auth.setDimensions(dimensions);
|
||||
|
||||
// 触发 fastPath 初始化(访问8次)
|
||||
for (int i = 0; i < 8; i++) {
|
||||
auth.hasPermission("permission-500", Collections.singletonList("query"));
|
||||
}
|
||||
|
||||
int iterations = 10000;
|
||||
long totalTime = 0;
|
||||
|
||||
// 测试 fastPath 生效后的性能(快路径)
|
||||
long startTime = System.nanoTime();
|
||||
for (int i = 0; i < iterations; i++) {
|
||||
auth.hasPermission("permission-" + (i % 1000), Collections.singletonList("query"));
|
||||
auth.getPermission("permission-" + (i % 1000));
|
||||
auth.getDimension("type-5", "dim-" + (i % 500));
|
||||
auth.getDimensions("type-5");
|
||||
}
|
||||
long endTime = System.nanoTime();
|
||||
totalTime = endTime - startTime;
|
||||
|
||||
double avgTimeNanos = (double) totalTime / iterations;
|
||||
double opsPerSecond = 1_000_000_000.0 / avgTimeNanos;
|
||||
|
||||
System.out.println("\n========== FastPath 生效后性能测试 ==========");
|
||||
System.out.println("迭代次数: " + iterations);
|
||||
System.out.println("总耗时: " + (totalTime / 1_000_000) + " ms");
|
||||
System.out.println("平均每次操作耗时: " + String.format("%.2f", avgTimeNanos / 1000) + " μs");
|
||||
System.out.println("每秒操作数: " + String.format("%.2f", opsPerSecond / 4) + " ops/s (每个方法)");
|
||||
System.out.println("==========================================\n");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testPerformanceComparison() {
|
||||
// 准备大量权限和维度数据
|
||||
List<Permission> permissions = new ArrayList<>();
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
permissions.add(SimplePermission.builder()
|
||||
.id("permission-" + i)
|
||||
.name("Permission " + i)
|
||||
.actions(new HashSet<>(Arrays.asList("query", "save", "delete")))
|
||||
.build());
|
||||
}
|
||||
|
||||
List<Dimension> dimensions = new ArrayList<>();
|
||||
for (int i = 0; i < 500; i++) {
|
||||
dimensions.add(SimpleDimension.of(
|
||||
"dim-" + i,
|
||||
"Dimension " + i,
|
||||
SimpleDimensionType.of("type-" + (i % 10)),
|
||||
null
|
||||
));
|
||||
}
|
||||
|
||||
int iterations = 10000;
|
||||
|
||||
// 测试慢路径性能
|
||||
SimpleAuthentication slowPathAuth = new SimpleAuthentication();
|
||||
slowPathAuth.setUser(user);
|
||||
slowPathAuth.setPermissions(permissions);
|
||||
slowPathAuth.setDimensions(dimensions);
|
||||
|
||||
// 只访问7次,确保 fastPath 不生效
|
||||
for (int i = 0; i < 7; i++) {
|
||||
slowPathAuth.hasPermission("permission-500", Collections.singletonList("query"));
|
||||
}
|
||||
|
||||
long slowPathStart = System.nanoTime();
|
||||
for (int i = 0; i < iterations; i++) {
|
||||
slowPathAuth.hasPermission("permission-" + (i % 1000), Collections.singletonList("query"));
|
||||
slowPathAuth.getPermission("permission-" + (i % 1000));
|
||||
slowPathAuth.getDimension("type-5", "dim-" + (i % 500));
|
||||
slowPathAuth.getDimensions("type-5");
|
||||
}
|
||||
long slowPathTime = System.nanoTime() - slowPathStart;
|
||||
|
||||
// 测试快路径性能
|
||||
SimpleAuthentication fastPathAuth = new SimpleAuthentication();
|
||||
fastPathAuth.setUser(user);
|
||||
fastPathAuth.setPermissions(permissions);
|
||||
fastPathAuth.setDimensions(dimensions);
|
||||
|
||||
// 触发 fastPath 初始化(访问8次)
|
||||
for (int i = 0; i < 8; i++) {
|
||||
fastPathAuth.hasPermission("permission-500", Collections.singletonList("query"));
|
||||
}
|
||||
|
||||
long fastPathStart = System.nanoTime();
|
||||
for (int i = 0; i < iterations; i++) {
|
||||
fastPathAuth.hasPermission("permission-" + (i % 1000), Collections.singletonList("query"));
|
||||
fastPathAuth.getPermission("permission-" + (i % 1000));
|
||||
fastPathAuth.getDimension("type-5", "dim-" + (i % 500));
|
||||
fastPathAuth.getDimensions("type-5");
|
||||
}
|
||||
long fastPathTime = System.nanoTime() - fastPathStart;
|
||||
|
||||
// 计算性能提升
|
||||
double slowPathAvg = (double) slowPathTime / iterations;
|
||||
double fastPathAvg = (double) fastPathTime / iterations;
|
||||
double improvement = ((slowPathAvg - fastPathAvg) / slowPathAvg) * 100;
|
||||
|
||||
System.out.println("\n========== FastPath 性能对比测试 ==========");
|
||||
System.out.println("测试数据规模:");
|
||||
System.out.println(" - 权限数量: 1000");
|
||||
System.out.println(" - 维度数量: 500");
|
||||
System.out.println(" - 迭代次数: " + iterations);
|
||||
System.out.println();
|
||||
System.out.println("慢路径 (FastPath 未生效):");
|
||||
System.out.println(" - 总耗时: " + (slowPathTime / 1_000_000) + " ms");
|
||||
System.out.println(" - 平均每次操作: " + String.format("%.2f", slowPathAvg / 1000) + " μs");
|
||||
System.out.println();
|
||||
System.out.println("快路径 (FastPath 已生效):");
|
||||
System.out.println(" - 总耗时: " + (fastPathTime / 1_000_000) + " ms");
|
||||
System.out.println(" - 平均每次操作: " + String.format("%.2f", fastPathAvg / 1000) + " μs");
|
||||
System.out.println();
|
||||
System.out.println("性能提升: " + String.format("%.2f", improvement) + "%");
|
||||
System.out.println("性能倍数: " + String.format("%.2f", slowPathAvg / fastPathAvg) + "x");
|
||||
System.out.println("==========================================\n");
|
||||
|
||||
// 验证 fastPath 确实提升了性能
|
||||
assertTrue(fastPathTime < slowPathTime,
|
||||
"FastPath 应该比慢路径更快。慢路径: " + slowPathTime + " ns, 快路径: " + fastPathTime + " ns");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testPerformanceWithDifferentDataSizes() {
|
||||
int[] permissionSizes = {100, 500, 1000, 2000};
|
||||
int[] dimensionSizes = {50, 250, 500, 1000};
|
||||
int iterations = 5000;
|
||||
|
||||
System.out.println("\n========== 不同数据规模下的性能测试 ==========");
|
||||
System.out.println("迭代次数: " + iterations);
|
||||
System.out.println();
|
||||
|
||||
for (int permSize : permissionSizes) {
|
||||
for (int dimSize : dimensionSizes) {
|
||||
// 准备数据
|
||||
List<Permission> permissions = new ArrayList<>();
|
||||
for (int i = 0; i < permSize; i++) {
|
||||
permissions.add(SimplePermission.builder()
|
||||
.id("permission-" + i)
|
||||
.name("Permission " + i)
|
||||
.actions(new HashSet<>(Arrays.asList("query", "save")))
|
||||
.build());
|
||||
}
|
||||
|
||||
List<Dimension> dimensions = new ArrayList<>();
|
||||
for (int i = 0; i < dimSize; i++) {
|
||||
dimensions.add(SimpleDimension.of(
|
||||
"dim-" + i,
|
||||
"Dimension " + i,
|
||||
SimpleDimensionType.of("type-" + (i % 10)),
|
||||
null
|
||||
));
|
||||
}
|
||||
|
||||
SimpleAuthentication auth = new SimpleAuthentication();
|
||||
auth.setUser(user);
|
||||
auth.setPermissions(permissions);
|
||||
auth.setDimensions(dimensions);
|
||||
|
||||
// 触发 fastPath
|
||||
for (int i = 0; i < 8; i++) {
|
||||
auth.hasPermission("permission-0", Collections.singletonList("query"));
|
||||
}
|
||||
|
||||
long start = System.nanoTime();
|
||||
for (int i = 0; i < iterations; i++) {
|
||||
auth.hasPermission("permission-" + (i % permSize), Collections.singletonList("query"));
|
||||
auth.getPermission("permission-" + (i % permSize));
|
||||
auth.getDimension("type-0", "dim-" + (i % dimSize));
|
||||
auth.getDimensions("type-0");
|
||||
}
|
||||
long time = System.nanoTime() - start;
|
||||
|
||||
double avgTime = (double) time / iterations;
|
||||
System.out.println(String.format(
|
||||
"权限: %4d, 维度: %4d -> 总耗时: %6.2f ms, 平均: %6.2f μs/op",
|
||||
permSize, dimSize, time / 1_000_000.0, avgTime / 1000.0
|
||||
));
|
||||
}
|
||||
}
|
||||
System.out.println("==========================================\n");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFastPathInitializationThreshold() {
|
||||
authentication.setPermissions(Collections.singletonList(permission1));
|
||||
authentication.setDimensions(Collections.singletonList(dimension1));
|
||||
|
||||
// 验证前7次访问不会初始化 fastPath
|
||||
for (int i = 0; i < 7; i++) {
|
||||
authentication.hasPermission("permission-1", Collections.singletonList("query"));
|
||||
}
|
||||
|
||||
// 第8次访问应该触发 fastPath 初始化
|
||||
long beforeInit = System.nanoTime();
|
||||
authentication.hasPermission("permission-1", Collections.singletonList("query"));
|
||||
long initTime = System.nanoTime() - beforeInit;
|
||||
|
||||
// 第9次及之后的访问应该使用 fastPath
|
||||
long afterInit = System.nanoTime();
|
||||
for (int i = 0; i < 100; i++) {
|
||||
authentication.hasPermission("permission-1", Collections.singletonList("query"));
|
||||
}
|
||||
long fastPathTime = System.nanoTime() - afterInit;
|
||||
|
||||
System.out.println("\n========== FastPath 初始化阈值测试 ==========");
|
||||
System.out.println("第8次访问耗时(包含初始化): " + (initTime / 1000) + " μs");
|
||||
System.out.println("后续100次访问总耗时: " + (fastPathTime / 1_000_000) + " ms");
|
||||
System.out.println("后续100次访问平均耗时: " + (fastPathTime / 100_000.0) + " μs");
|
||||
System.out.println("==========================================\n");
|
||||
|
||||
// 验证初始化后的访问确实更快
|
||||
assertTrue(fastPathTime / 100.0 < initTime * 10,
|
||||
"FastPath 初始化后的访问应该比初始化时更快");
|
||||
}
|
||||
}
|
||||
@@ -55,7 +55,7 @@ public class AuthorizingHandlerAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
public ReactiveAuthenticationManagerProvider embedAuthenticationManager(EmbedAuthenticationProperties properties) {
|
||||
return new EmbedReactiveAuthenticationManager(properties);
|
||||
return properties.getUsers().isEmpty() ? null : new EmbedReactiveAuthenticationManager(properties);
|
||||
}
|
||||
|
||||
@Bean
|
||||
|
||||
Reference in New Issue
Block a user