refactor: 优化权限控制

This commit is contained in:
zhouhao
2025-08-05 09:33:28 +08:00
parent 3f7e040c44
commit 4dda9eb683
16 changed files with 333 additions and 135 deletions

View File

@@ -111,9 +111,7 @@ public class AopAuthorizingController extends StaticMethodMatcherPointcutAdvisor
MethodInterceptorContext paramContext = holder.createParamContext();
AuthorizeDefinition definition = aopMethodAuthorizeDefinitionParser
.parse(methodInvocation
.getThis()
.getClass(),
.parse(methodInvocation.getThis().getClass(),
methodInvocation.getMethod(),
paramContext);
Object result = null;
@@ -131,43 +129,26 @@ public class AopAuthorizingController extends StaticMethodMatcherPointcutAdvisor
Authentication authentication = Authentication
.current()
.orElseThrow(UnAuthorizedException.NoStackTrace::new);
.orElse(null);
if (authentication == null) {
// 允许匿名访问
if (definition.allowAnonymous()) {
return methodInvocation.proceed();
}
return new UnAuthorizedException.NoStackTrace();
}
context.setAuthentication(authentication);
isControl = true;
Phased dataAccessPhased = definition.getResources().getPhased();
if (definition.getPhased() == Phased.before) {
//RDAC before
authorizingHandler.handRBAC(context);
//方法调用前验证数据权限
if (dataAccessPhased == Phased.before) {
authorizingHandler.handleDataAccess(context);
}
result = methodInvocation.proceed();
//方法调用后验证数据权限
if (dataAccessPhased == Phased.after) {
context.setParamContext(holder.createParamContext(result));
authorizingHandler.handleDataAccess(context);
}
} else {
//方法调用前验证数据权限
if (dataAccessPhased == Phased.before) {
authorizingHandler.handleDataAccess(context);
}
result = methodInvocation.proceed();
context.setParamContext(holder.createParamContext(result));
authorizingHandler.handRBAC(context);
//方法调用后验证数据权限
if (dataAccessPhased == Phased.after) {
authorizingHandler.handleDataAccess(context);
}
}
}
if (!isControl) {

View File

@@ -92,7 +92,7 @@ public class DefaultAopMethodAuthorizeDefinitionParser implements AopMethodAutho
}
}
public CacheKey buildCacheKey(Class<?> target, Method method) {
CacheKey buildCacheKey(Class<?> target, Method method) {
return new CacheKey(ClassUtils.getUserClass(target), method);
}

View File

@@ -8,6 +8,8 @@ import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.util.CollectionUtils;
import java.lang.annotation.Annotation;
import java.lang.annotation.Repeatable;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import java.util.*;
import java.util.function.Function;
@@ -20,7 +22,8 @@ public class AopAuthorizeDefinitionParser {
Authorize.class,
Dimension.class,
Resource.class,
ResourceAction.class
ResourceAction.class,
Dimensions.class
));
private final Set<Annotation> methodAnnotation;
@@ -37,10 +40,8 @@ public class AopAuthorizeDefinitionParser {
definition = new DefaultBasicAuthorizeDefinition();
definition.setTargetClass(targetClass);
definition.setTargetMethod(method);
methodAnnotation = AnnotatedElementUtils.findAllMergedAnnotations(method, types);
classAnnotation = AnnotatedElementUtils.findAllMergedAnnotations(targetClass, types);
methodAnnotation = loadAnnotations(method);
classAnnotation = loadAnnotations(targetClass);
classAnnotationGroup = classAnnotation
.stream()
@@ -51,6 +52,23 @@ public class AopAuthorizeDefinitionParser {
.collect(Collectors.groupingBy(Annotation::annotationType));
}
private Set<Annotation> loadAnnotations(AnnotatedElement element) {
return types
.stream()
.flatMap(s -> {
if (s.isAnnotationPresent(Repeatable.class)) {
return AnnotatedElementUtils
.findMergedRepeatableAnnotations(element, s)
.stream();
}
return AnnotatedElementUtils
.findAllMergedAnnotations(element, s)
.stream();
})
.filter(Objects::nonNull)
.collect(Collectors.toSet());
}
private void initClassAnnotation() {
for (Annotation annotation : classAnnotation) {
if (annotation instanceof Authorize) {
@@ -73,12 +91,16 @@ public class AopAuthorizeDefinitionParser {
if (annotation instanceof Dimension) {
definition.putAnnotation(((Dimension) annotation));
}
if (annotation instanceof Dimensions) {
definition.putAnnotation(((Dimensions) annotation));
}
if (annotation instanceof ResourceAction) {
getAnnotationByType(Resource.class)
.map(res -> definition.getResources().getResource(res.id()).orElse(null))
.filter(Objects::nonNull)
.forEach(res -> {
definition.putAnnotation(res, (ResourceAction) annotation);
definition.putAnnotation(res, (ResourceAction) annotation);
});
}
}

View File

@@ -61,10 +61,17 @@ public class DefaultBasicAuthorizeDefinition implements AopAuthorizeDefinition {
return parser.parse();
}
public void putAnnotation(Dimensions ann) {
dimensions.setLogical(ann.logical());
if (ann.description().length > 0) {
dimensions.setDescription(String.join("", ann.description()));
}
}
public void putAnnotation(Authorize ann) {
if (!ann.merge()) {
getResources().getResources().clear();
getDimensions().getDimensions().clear();
getResources().clear();
getDimensions().clear();
}
setPhased(ann.phased());
getResources().setPhased(ann.phased());
@@ -81,7 +88,7 @@ public class DefaultBasicAuthorizeDefinition implements AopAuthorizeDefinition {
public void putAnnotation(Dimension ann) {
if (ann.ignore()) {
getDimensions().getDimensions().clear();
getDimensions().clear();
return;
}
DimensionDefinition definition = new DimensionDefinition();

View File

@@ -5,10 +5,8 @@ import lombok.extern.slf4j.Slf4j;
import org.hswebframework.web.authorization.Authentication;
import org.hswebframework.web.authorization.Permission;
import org.hswebframework.web.authorization.access.DataAccessController;
import org.hswebframework.web.authorization.define.AuthorizeDefinition;
import org.hswebframework.web.authorization.define.AuthorizingContext;
import org.hswebframework.web.authorization.define.HandleType;
import org.hswebframework.web.authorization.define.ResourcesDefinition;
import org.hswebframework.web.authorization.annotation.Logical;
import org.hswebframework.web.authorization.define.*;
import org.hswebframework.web.authorization.events.AuthorizingHandleBeforeEvent;
import org.hswebframework.web.authorization.exception.AccessDenyException;
import org.slf4j.Logger;
@@ -17,6 +15,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import reactor.core.publisher.Mono;
import java.util.Set;
import java.util.concurrent.TimeUnit;
/**
@@ -93,10 +92,7 @@ public class DefaultAuthorizingHandler implements AuthorizingHandler {
AuthorizingHandleBeforeEvent event = new AuthorizingHandleBeforeEvent(context, type);
eventPublisher.publishEvent(event);
if (event.hasListener()) {
event
.getAsync()
.toFuture()
.get(10, TimeUnit.SECONDS);
event.getAsync().block();
}
if (!event.isExecute()) {
if (event.isAllow()) {
@@ -109,51 +105,39 @@ public class DefaultAuthorizingHandler implements AuthorizingHandler {
return false;
}
@Deprecated
public void handleDataAccess(AuthorizingContext context) {
if (dataAccessController == null) {
return;
}
if (context.getDefinition().getResources() == null) {
return;
}
if (handleEvent(context, HandleType.DATA)) {
return;
}
DataAccessController finalAccessController = dataAccessController;
Authentication autz = context.getAuthentication();
boolean isAccess = context
.getDefinition()
.getResources()
.getDataAccessResources()
.stream()
.allMatch(resource -> {
Permission permission = autz
.getPermission(resource.getId())
.orElseThrow(AccessDenyException.NoStackTrace::new);
return resource
.getDataAccessAction()
.stream()
.allMatch(act -> permission
.getDataAccesses(act.getId())
.stream()
.allMatch(dataAccessConfig -> finalAccessController.doAccess(dataAccessConfig, context)));
});
if (!isAccess) {
throw new AccessDenyException.NoStackTrace(context.getDefinition().getMessage());
}
}
protected void handleRBAC(Authentication authentication, AuthorizeDefinition definition) {
ResourcesDefinition resources = definition.getResources();
// 判断权限
if (!resources.hasPermission(authentication)) {
throw new AccessDenyException.NoStackTrace(definition.getMessage(), definition.getDescription());
}
DimensionsDefinition dd = definition.getDimensions();
// 判断维度
if (dd != null && !dd.isEmpty()) {
if (!dd.hasDimension(
(type, logical, dimensionIds) ->
hasDimensions(authentication, type, logical, dimensionIds))) {
throw new AccessDenyException
.NoStackTrace(definition.getMessage(), definition.getDimensions().toString());
}
}
}
private boolean hasDimensions(Authentication auth, String type, Logical logical, Set<String> dimensionIds) {
if (logical == Logical.AND) {
return auth.hasAllDimension(type, dimensionIds);
}
return auth.hasAnyDimension(type, dimensionIds);
}
}

View File

@@ -16,25 +16,27 @@ public class DefaultBasicAuthorizeDefinitionTest {
@SneakyThrows
public void testCustomAnn() {
AopAuthorizeDefinition definition =
DefaultBasicAuthorizeDefinition.from(TestController.class, TestController.class.getMethod("test"));
DefaultBasicAuthorizeDefinition.from(TestController.class, TestController.class.getMethod("test"));
ResourceDefinition resource = definition.getResources()
.getResource("test").orElseThrow(NullPointerException::new);
ResourceDefinition resource = definition
.getResources()
.getResource("test").orElseThrow(NullPointerException::new);
Assert.assertNotNull(resource);
Assert.assertTrue(resource.hasAction(Arrays.asList("add")));
System.out.println(definition.getDimensions());
Assert.assertFalse(definition.getDimensions().isEmpty());
Assert.assertEquals(1, definition.getDimensions().getDimensions().size());
Assert.assertTrue(resource.getAction("add")
.map(act->act.getDataAccess().getType("user_own_data"))
.isPresent());
}
@Test
@SneakyThrows
public void testNoMerge() {
AopAuthorizeDefinition definition =
DefaultBasicAuthorizeDefinition.from(TestController.class, TestController.class.getMethod("noMerge"));
DefaultBasicAuthorizeDefinition.from(TestController.class, TestController.class.getMethod("noMerge"));
Assert.assertTrue(definition.getResources().isEmpty());
}
@@ -43,17 +45,19 @@ public class DefaultBasicAuthorizeDefinitionTest {
public class TestController implements GenericController {
@Authorize(merge = false)
public void noMerge(){
public void noMerge() {
}
}
public interface GenericController {
@CreateAction
@UserOwnData
default void test(){
@RequiresRoles("test")
@RequiresRoles("test2")
default void test() {
}
}