From 17b4cbd608cc38d5c50ae9ab6894f4a28972d257 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=80=81=E5=91=A8?= Date: Wed, 28 Aug 2024 13:54:24 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E5=BC=83=E7=94=A8=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E6=9D=83=E9=99=90=E6=8E=A7=E5=88=B6,AuthorizingHandle?= =?UTF-8?q?BeforeEvent=E5=A2=9E=E5=8A=A0=E5=BC=82=E6=AD=A5=E6=94=AF?= =?UTF-8?q?=E6=8C=81.=20(#298)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../authorization/annotation/Authorize.java | 2 +- .../authorization/annotation/DataAccess.java | 4 +- .../events/AuthorizingHandleBeforeEvent.java | 16 +- .../basic/aop/AopAuthorizingController.java | 81 ++++--- .../basic/handler/AuthorizingHandler.java | 8 + .../handler/DefaultAuthorizingHandler.java | 78 +++++-- .../aop/AopAuthorizingControllerTest.java | 220 +++++++++--------- 7 files changed, 234 insertions(+), 175 deletions(-) diff --git a/hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/annotation/Authorize.java b/hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/annotation/Authorize.java index b2dfd8edd..6921ea32c 100644 --- a/hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/annotation/Authorize.java +++ b/hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/annotation/Authorize.java @@ -66,7 +66,7 @@ public @interface Authorize { Logical logical() default Logical.DEFAULT; /** - * @return 验证时机,在方法调用前还是调用后s + * @return 验证时机,在方法调用前还是调用后 */ Phased phased() default Phased.before; diff --git a/hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/annotation/DataAccess.java b/hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/annotation/DataAccess.java index 6fd9b696a..ba0b4852f 100644 --- a/hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/annotation/DataAccess.java +++ b/hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/annotation/DataAccess.java @@ -31,10 +31,12 @@ import java.lang.annotation.*; * @see DataAccessController * @see ResourceAction#dataAccess() * @since 3.0 + * @deprecated 已弃用, 4.1中移除 */ -@Target({ElementType.ANNOTATION_TYPE,ElementType.METHOD}) +@Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented +@Deprecated public @interface DataAccess { DataAccessType[] type() default {}; diff --git a/hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/events/AuthorizingHandleBeforeEvent.java b/hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/events/AuthorizingHandleBeforeEvent.java index d2d189f26..680646cb5 100644 --- a/hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/events/AuthorizingHandleBeforeEvent.java +++ b/hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/events/AuthorizingHandleBeforeEvent.java @@ -2,6 +2,7 @@ package org.hswebframework.web.authorization.events; import org.hswebframework.web.authorization.define.AuthorizingContext; import org.hswebframework.web.authorization.define.HandleType; +import org.hswebframework.web.event.DefaultAsyncEvent; import org.springframework.context.ApplicationEvent; /** @@ -19,10 +20,7 @@ import org.springframework.context.ApplicationEvent; * @author zhouhao * @since 4.0 */ -// TODO: 2021/12/21 Reactive支持 -public class AuthorizingHandleBeforeEvent extends ApplicationEvent implements AuthorizationEvent { - - private static final long serialVersionUID = -1095765748533721998L; +public class AuthorizingHandleBeforeEvent extends DefaultAsyncEvent implements AuthorizationEvent { private boolean allow = false; @@ -30,15 +28,21 @@ public class AuthorizingHandleBeforeEvent extends ApplicationEvent implements Au private String message; + private final AuthorizingContext context; + + /** + * @deprecated 数据权限控制已取消,4.1版本后移除 + */ + @Deprecated private final HandleType handleType; public AuthorizingHandleBeforeEvent(AuthorizingContext context, HandleType handleType) { - super(context); + this.context = context; this.handleType = handleType; } public AuthorizingContext getContext() { - return ((AuthorizingContext) getSource()); + return context; } public boolean isExecute() { diff --git a/hsweb-authorization/hsweb-authorization-basic/src/main/java/org/hswebframework/web/authorization/basic/aop/AopAuthorizingController.java b/hsweb-authorization/hsweb-authorization-basic/src/main/java/org/hswebframework/web/authorization/basic/aop/AopAuthorizingController.java index d8b9c9e78..32a6cf48b 100644 --- a/hsweb-authorization/hsweb-authorization-basic/src/main/java/org/hswebframework/web/authorization/basic/aop/AopAuthorizingController.java +++ b/hsweb-authorization/hsweb-authorization-basic/src/main/java/org/hswebframework/web/authorization/basic/aop/AopAuthorizingController.java @@ -64,43 +64,41 @@ public class AopAuthorizingController extends StaticMethodMatcherPointcutAdvisor MethodInterceptorHolder holder, AuthorizingContext context, Supplier> invoker) { + MethodInterceptorContext interceptorContext = holder.createParamContext(invoker.get()); + context.setParamContext(interceptorContext); + return this + .invokeReactive( + Authentication + .currentReactive() + .switchIfEmpty(Mono.error(UnAuthorizedException.NoStackTrace::new)) + .flatMap(auth -> { + context.setAuthentication(auth); + //响应式不再支持数据权限控制 + return authorizingHandler.handRBACAsync(context); + }), + (Publisher) interceptorContext.getInvokeResult()); + } - return Authentication - .currentReactive() - .switchIfEmpty(Mono.error(UnAuthorizedException::new)) - .flatMapMany(auth -> { - context.setAuthentication(auth); - Function afterRuner = runnable -> { - MethodInterceptorContext interceptorContext = holder.createParamContext(invoker.get()); - context.setParamContext(interceptorContext); - runnable.run(); - return (Publisher) interceptorContext.getInvokeResult(); - }; - if (context.getDefinition().getPhased() != Phased.after) { - authorizingHandler.handRBAC(context); - if (context.getDefinition().getResources().getPhased() != Phased.after) { - authorizingHandler.handleDataAccess(context); - return invoker.get(); - } else { - return afterRuner.apply(() -> authorizingHandler.handleDataAccess(context)); - } + private Publisher invokeReactive(Mono before, Publisher source) { + if (source instanceof Mono) { + return before.then((Mono) source); + } + return before.thenMany(source); + } - } else { - if (context.getDefinition().getResources().getPhased() != Phased.after) { - authorizingHandler.handleDataAccess(context); - return invoker.get(); - } else { - return afterRuner.apply(() -> { - authorizingHandler.handRBAC(context); - authorizingHandler.handleDataAccess(context); - }); - } - } - }); + private T invokeReactive(MethodInvocation invocation) { + if (Mono.class.isAssignableFrom(invocation.getMethod().getReturnType())) { + return (T) Mono.defer(() -> doProceed(invocation)); + } + if (Flux.class.isAssignableFrom(invocation.getMethod().getReturnType())) { + return (T) Flux.defer(() -> doProceed(invocation)); + } + return doProceed(invocation); } @SneakyThrows private T doProceed(MethodInvocation invocation) { + return (T) invocation.proceed(); } @@ -110,9 +108,12 @@ public class AopAuthorizingController extends StaticMethodMatcherPointcutAdvisor MethodInterceptorContext paramContext = holder.createParamContext(); - AuthorizeDefinition definition = aopMethodAuthorizeDefinitionParser.parse(methodInvocation - .getThis() - .getClass(), methodInvocation.getMethod(), paramContext); + AuthorizeDefinition definition = aopMethodAuthorizeDefinitionParser + .parse(methodInvocation + .getThis() + .getClass(), + methodInvocation.getMethod(), + paramContext); Object result = null; boolean isControl = false; if (null != definition && !definition.isEmpty()) { @@ -123,16 +124,12 @@ public class AopAuthorizingController extends StaticMethodMatcherPointcutAdvisor Class returnType = methodInvocation.getMethod().getReturnType(); //handle reactive method if (Publisher.class.isAssignableFrom(returnType)) { - Publisher publisher = handleReactive0(definition, holder, context, () -> doProceed(methodInvocation)); - if (Mono.class.isAssignableFrom(returnType)) { - return Mono.from(publisher); - } else if (Flux.class.isAssignableFrom(returnType)) { - return Flux.from(publisher); - } - throw new UnsupportedOperationException("unsupported reactive type:" + returnType); + return handleReactive0(definition, holder, context, () -> invokeReactive(methodInvocation)); } - Authentication authentication = Authentication.current().orElseThrow(UnAuthorizedException::new); + Authentication authentication = Authentication + .current() + .orElseThrow(UnAuthorizedException.NoStackTrace::new); context.setAuthentication(authentication); isControl = true; diff --git a/hsweb-authorization/hsweb-authorization-basic/src/main/java/org/hswebframework/web/authorization/basic/handler/AuthorizingHandler.java b/hsweb-authorization/hsweb-authorization-basic/src/main/java/org/hswebframework/web/authorization/basic/handler/AuthorizingHandler.java index 8d35de635..8a630a303 100644 --- a/hsweb-authorization/hsweb-authorization-basic/src/main/java/org/hswebframework/web/authorization/basic/handler/AuthorizingHandler.java +++ b/hsweb-authorization/hsweb-authorization-basic/src/main/java/org/hswebframework/web/authorization/basic/handler/AuthorizingHandler.java @@ -1,6 +1,7 @@ package org.hswebframework.web.authorization.basic.handler; import org.hswebframework.web.authorization.define.AuthorizingContext; +import reactor.core.publisher.Mono; /** * aop方式权限控制处理器 @@ -8,10 +9,17 @@ import org.hswebframework.web.authorization.define.AuthorizingContext; * @author zhouhao */ public interface AuthorizingHandler { + void handRBAC(AuthorizingContext context); + default Mono handRBACAsync(AuthorizingContext context) { + return Mono.fromRunnable(() -> handRBAC(context)); + } + + @Deprecated void handleDataAccess(AuthorizingContext context); + @Deprecated default void handle(AuthorizingContext context) { handRBAC(context); handleDataAccess(context); diff --git a/hsweb-authorization/hsweb-authorization-basic/src/main/java/org/hswebframework/web/authorization/basic/handler/DefaultAuthorizingHandler.java b/hsweb-authorization/hsweb-authorization-basic/src/main/java/org/hswebframework/web/authorization/basic/handler/DefaultAuthorizingHandler.java index 1c957e70e..bfac89095 100644 --- a/hsweb-authorization/hsweb-authorization-basic/src/main/java/org/hswebframework/web/authorization/basic/handler/DefaultAuthorizingHandler.java +++ b/hsweb-authorization/hsweb-authorization-basic/src/main/java/org/hswebframework/web/authorization/basic/handler/DefaultAuthorizingHandler.java @@ -1,5 +1,6 @@ package org.hswebframework.web.authorization.basic.handler; +import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import org.hswebframework.web.authorization.Authentication; import org.hswebframework.web.authorization.Permission; @@ -14,6 +15,9 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationEventPublisher; +import reactor.core.publisher.Mono; + +import java.util.concurrent.TimeUnit; /** * @author zhouhao @@ -51,15 +55,54 @@ public class DefaultAuthorizingHandler implements AuthorizingHandler { } + @Override + public Mono handRBACAsync(AuthorizingContext context) { + return this + .handleEventAsync(context, HandleType.RBAC) + .doOnNext(handled -> { + //没有自定义事件处理 + if (!handled) { + handleRBAC(context.getAuthentication(), context.getDefinition()); + } + }) + .then(); + } + + private Mono handleEventAsync(AuthorizingContext context, HandleType type) { + if (null != eventPublisher) { + AuthorizingHandleBeforeEvent event = new AuthorizingHandleBeforeEvent(context, type); + return event + .publish(eventPublisher) + .then(Mono.fromCallable(() -> { + if (!event.isExecute()) { + if (event.isAllow()) { + return true; + } else { + throw new AccessDenyException.NoStackTrace(event.getMessage()); + } + } + return false; + })); + } + return Mono.just(false); + } + + @SneakyThrows private boolean handleEvent(AuthorizingContext context, HandleType type) { if (null != eventPublisher) { AuthorizingHandleBeforeEvent event = new AuthorizingHandleBeforeEvent(context, type); eventPublisher.publishEvent(event); + if (event.hasListener()) { + event + .getAsync() + .toFuture() + .get(10, TimeUnit.SECONDS); + } if (!event.isExecute()) { if (event.isAllow()) { return true; } else { - throw new AccessDenyException(event.getMessage()); + throw new AccessDenyException.NoStackTrace(event.getMessage()); } } } @@ -82,21 +125,26 @@ public class DefaultAuthorizingHandler implements AuthorizingHandler { DataAccessController finalAccessController = dataAccessController; Authentication autz = context.getAuthentication(); - boolean isAccess = context.getDefinition() - .getResources() - .getDataAccessResources() - .stream() - .allMatch(resource -> { - Permission permission = autz.getPermission(resource.getId()).orElseThrow(AccessDenyException::new); - return resource.getDataAccessAction() - .stream() - .allMatch(act -> permission.getDataAccesses(act.getId()) - .stream() - .allMatch(dataAccessConfig -> finalAccessController.doAccess(dataAccessConfig, context))); + 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(context.getDefinition().getMessage()); + throw new AccessDenyException.NoStackTrace(context.getDefinition().getMessage()); } } @@ -106,7 +154,7 @@ public class DefaultAuthorizingHandler implements AuthorizingHandler { ResourcesDefinition resources = definition.getResources(); if (!resources.hasPermission(authentication)) { - throw new AccessDenyException(definition.getMessage(),definition.getDescription()); + throw new AccessDenyException.NoStackTrace(definition.getMessage(), definition.getDescription()); } } } diff --git a/hsweb-authorization/hsweb-authorization-basic/src/test/java/org/hswebframework/web/authorization/basic/aop/AopAuthorizingControllerTest.java b/hsweb-authorization/hsweb-authorization-basic/src/test/java/org/hswebframework/web/authorization/basic/aop/AopAuthorizingControllerTest.java index f6f9c1949..5c930e119 100644 --- a/hsweb-authorization/hsweb-authorization-basic/src/test/java/org/hswebframework/web/authorization/basic/aop/AopAuthorizingControllerTest.java +++ b/hsweb-authorization/hsweb-authorization-basic/src/test/java/org/hswebframework/web/authorization/basic/aop/AopAuthorizingControllerTest.java @@ -60,114 +60,114 @@ public class AopAuthorizingControllerTest { .expectNext("403") .verifyComplete(); } - - @Test - public void testFiledDeny() { - SimpleAuthentication authentication = new SimpleAuthentication(); - - SimpleFieldFilterDataAccessConfig config = new SimpleFieldFilterDataAccessConfig(); - config.setAction("query"); - config.setFields(new HashSet<>(Arrays.asList("name"))); - - authentication.setUser(SimpleUser.builder().id("test").username("test").build()); - authentication.setPermissions(Arrays.asList(SimplePermission.builder() - .actions(Collections.singleton("query")) - .dataAccesses(Collections.singleton(config)) - .id("test").build())); - - ReactiveAuthenticationHolder.setSupplier(new ReactiveAuthenticationSupplier() { - @Override - public Mono get(String userId) { - return Mono.empty(); - } - - @Override - public Mono get() { - return Mono.just(authentication); - } - }); - - testController.queryUser(new QueryParam()) - .map(Param::getExcludes) - .as(StepVerifier::create) - .expectNextMatches(f -> f.contains("name")) - .verifyComplete(); - - testController.queryUser(Mono.just(new QueryParam())) - .map(Param::getExcludes) - .as(StepVerifier::create) - .expectNextMatches(f -> f.contains("name")) - .verifyComplete(); - } - - @Test - public void testDimensionDataAccess() { - SimpleAuthentication authentication = new SimpleAuthentication(); - - DimensionDataAccessConfig config = new DimensionDataAccessConfig(); - config.setAction("query"); - config.setScopeType("role"); - - DimensionDataAccessConfig config2 = new DimensionDataAccessConfig(); - config2.setAction("save"); - config2.setScopeType("role"); - ReactiveAuthenticationHolder.setSupplier(new ReactiveAuthenticationSupplier() { - @Override - public Mono get(String userId) { - return Mono.empty(); - } - - @Override - public Mono get() { - return Mono.just(authentication); - } - }); - - authentication.setUser(SimpleUser.builder().id("test").username("test").build()); - authentication.setPermissions(Arrays.asList(SimplePermission.builder() - .actions(new HashSet<>(Arrays.asList("query", "save"))) - .dataAccesses(new HashSet<>(Arrays.asList(config, config2))) - .id("test").build())); - authentication.setDimensions(Collections.singletonList(Dimension.of("test", "test", DefaultDimensionType.role))); - - testController.queryUserByDimension(Mono.just(new QueryParam())) - .map(Param::getTerms) - .flatMapIterable(Function.identity()) - .next() - .map(Term::getValue) - .map(CastUtil::>cast) - .flatMapIterable(Function.identity()) - .next() - .as(StepVerifier::create) - .expectNextMatches("test"::equals) - .verifyComplete(); - - TestEntity testEntity = new TestEntity(); - testEntity.setRoleId("123"); - - testController.save(Mono.just(testEntity)) - .as(StepVerifier::create) - .expectError(AccessDenyException.class) - .verify(); - - testController.add(Mono.just(testEntity)) - .as(StepVerifier::create) - .expectNextCount(1) - .verifyComplete(); - - testController.update(testEntity.getId(),Mono.just(testEntity)) - .as(StepVerifier::create) - .expectError(AccessDenyException.class) - .verify(); - - testEntity = new TestEntity(); - testEntity.setRoleId("test"); - - testController.save(Mono.just(testEntity)) - .as(StepVerifier::create) - .expectNextCount(1) - .verifyComplete(); - - - } +// +// @Test +// public void testFiledDeny() { +// SimpleAuthentication authentication = new SimpleAuthentication(); +// +// SimpleFieldFilterDataAccessConfig config = new SimpleFieldFilterDataAccessConfig(); +// config.setAction("query"); +// config.setFields(new HashSet<>(Arrays.asList("name"))); +// +// authentication.setUser(SimpleUser.builder().id("test").username("test").build()); +// authentication.setPermissions(Arrays.asList(SimplePermission.builder() +// .actions(Collections.singleton("query")) +// .dataAccesses(Collections.singleton(config)) +// .id("test").build())); +// +// ReactiveAuthenticationHolder.setSupplier(new ReactiveAuthenticationSupplier() { +// @Override +// public Mono get(String userId) { +// return Mono.empty(); +// } +// +// @Override +// public Mono get() { +// return Mono.just(authentication); +// } +// }); +// +// testController.queryUser(new QueryParam()) +// .map(Param::getExcludes) +// .as(StepVerifier::create) +// .expectNextMatches(f -> f.contains("name")) +// .verifyComplete(); +// +// testController.queryUser(Mono.just(new QueryParam())) +// .map(Param::getExcludes) +// .as(StepVerifier::create) +// .expectNextMatches(f -> f.contains("name")) +// .verifyComplete(); +// } +// +// @Test +// public void testDimensionDataAccess() { +// SimpleAuthentication authentication = new SimpleAuthentication(); +// +// DimensionDataAccessConfig config = new DimensionDataAccessConfig(); +// config.setAction("query"); +// config.setScopeType("role"); +// +// DimensionDataAccessConfig config2 = new DimensionDataAccessConfig(); +// config2.setAction("save"); +// config2.setScopeType("role"); +// ReactiveAuthenticationHolder.setSupplier(new ReactiveAuthenticationSupplier() { +// @Override +// public Mono get(String userId) { +// return Mono.empty(); +// } +// +// @Override +// public Mono get() { +// return Mono.just(authentication); +// } +// }); +// +// authentication.setUser(SimpleUser.builder().id("test").username("test").build()); +// authentication.setPermissions(Arrays.asList(SimplePermission.builder() +// .actions(new HashSet<>(Arrays.asList("query", "save"))) +// .dataAccesses(new HashSet<>(Arrays.asList(config, config2))) +// .id("test").build())); +// authentication.setDimensions(Collections.singletonList(Dimension.of("test", "test", DefaultDimensionType.role))); +// +// testController.queryUserByDimension(Mono.just(new QueryParam())) +// .map(Param::getTerms) +// .flatMapIterable(Function.identity()) +// .next() +// .map(Term::getValue) +// .map(CastUtil::>cast) +// .flatMapIterable(Function.identity()) +// .next() +// .as(StepVerifier::create) +// .expectNextMatches("test"::equals) +// .verifyComplete(); +// +// TestEntity testEntity = new TestEntity(); +// testEntity.setRoleId("123"); +// +// testController.save(Mono.just(testEntity)) +// .as(StepVerifier::create) +// .expectError(AccessDenyException.class) +// .verify(); +// +// testController.add(Mono.just(testEntity)) +// .as(StepVerifier::create) +// .expectNextCount(1) +// .verifyComplete(); +// +// testController.update(testEntity.getId(),Mono.just(testEntity)) +// .as(StepVerifier::create) +// .expectError(AccessDenyException.class) +// .verify(); +// +// testEntity = new TestEntity(); +// testEntity.setRoleId("test"); +// +// testController.save(Mono.just(testEntity)) +// .as(StepVerifier::create) +// .expectNextCount(1) +// .verifyComplete(); +// +// +// } } \ No newline at end of file