Merge remote-tracking branch 'origin/4.0.x' into 4.0.x

# Conflicts:
#	hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/annotation/DataAccess.java
This commit is contained in:
zhou-hao
2019-11-18 09:42:25 +08:00
21 changed files with 374 additions and 168 deletions

View File

@@ -1,5 +1,6 @@
package org.hswebframework.web.authorization.basic.aop;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
@@ -11,10 +12,12 @@ import org.hswebframework.web.authorization.basic.handler.AuthorizingHandler;
import org.hswebframework.web.authorization.define.*;
import org.hswebframework.web.authorization.exception.UnAuthorizedException;
import org.hswebframework.web.utils.AnnotationUtils;
import org.reactivestreams.Publisher;
import org.springframework.aop.support.StaticMethodMatcherPointcutAdvisor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.core.ReactiveAdapterRegistry;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;
@@ -22,6 +25,10 @@ import reactor.core.publisher.Mono;
import java.lang.reflect.Method;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;
/**
@@ -51,81 +58,48 @@ public class AopAuthorizingController extends StaticMethodMatcherPointcutAdvisor
this.autoParse = autoParse;
}
protected Mono<?> handleReactive(AuthorizeDefinition definition, MethodInterceptorHolder holder, AuthorizingContext context, Mono<?> mono) {
return Authentication.currentReactive()
.switchIfEmpty(Mono.error(new UnAuthorizedException()))
.flatMap(auth -> {
ResourcesDefinition resources = definition.getResources();
context.setAuthentication(auth);
if (definition.getPhased() == Phased.before) {
authorizingHandler.handRBAC(context);
if (resources != null && resources.getPhased() == Phased.before) {
authorizingHandler.handleDataAccess(context);
} else {
return mono.doOnNext(res -> {
context.setParamContext(holder.createParamContext(res));
authorizingHandler.handleDataAccess(context);
});
}
} else {
if (resources != null && resources.getPhased() == Phased.before) {
authorizingHandler.handleDataAccess(context);
return mono.doOnNext(res -> {
context.setParamContext(holder.createParamContext(res));
authorizingHandler.handRBAC(context);
});
} else {
return mono.doOnNext(res -> {
context.setParamContext(holder.createParamContext(res));
authorizingHandler.handle(context);
});
}
}
return mono;
});
}
protected Flux<?> handleReactive(AuthorizeDefinition definition, MethodInterceptorHolder holder, AuthorizingContext context, Flux<?> flux) {
protected Publisher<?> handleReactive0(AuthorizeDefinition definition,
MethodInterceptorHolder holder,
AuthorizingContext context,
Supplier<? extends Publisher<?>> invoker) {
return Authentication.currentReactive()
.switchIfEmpty(Mono.error(new UnAuthorizedException()))
.flatMapMany(auth -> {
ResourcesDefinition resources = definition.getResources();
context.setAuthentication(auth);
if (definition.getPhased() == Phased.before) {
Function<Runnable, Publisher> afterRuner = runnable -> {
MethodInterceptorContext interceptorContext = holder.createParamContext(invoker.get());
runnable.run();
return (Publisher<?>) interceptorContext.getInvokeResult();
};
if (context.getDefinition().getPhased() != Phased.after) {
authorizingHandler.handRBAC(context);
if (resources != null && resources.getPhased() == Phased.before) {
if (context.getDefinition().getResources().getPhased() != Phased.after) {
authorizingHandler.handleDataAccess(context);
return invoker.get();
} else {
return flux.doOnNext(res -> {
context.setParamContext(holder.createParamContext(res));
return afterRuner.apply(() -> authorizingHandler.handleDataAccess(context));
}
} else {
if (context.getDefinition().getResources().getPhased() != Phased.after) {
authorizingHandler.handleDataAccess(context);
return invoker.get();
} else {
return afterRuner.apply(() -> {
authorizingHandler.handRBAC(context);
authorizingHandler.handleDataAccess(context);
});
}
} else {
if (resources != null && resources.getPhased() == Phased.before) {
authorizingHandler.handleDataAccess(context);
return flux.doOnNext(res -> {
context.setParamContext(holder.createParamContext(res));
authorizingHandler.handRBAC(context);
});
} else {
return flux.doOnNext(res -> {
context.setParamContext(holder.createParamContext(res));
authorizingHandler.handle(context);
});
}
}
return flux;
});
}
@SneakyThrows
private <T> T doProceed(MethodInvocation invocation) {
return (T) invocation.proceed();
}
@Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
@@ -136,17 +110,20 @@ public class AopAuthorizingController extends StaticMethodMatcherPointcutAdvisor
AuthorizeDefinition definition = aopMethodAuthorizeDefinitionParser.parse(methodInvocation.getThis().getClass(), methodInvocation.getMethod(), paramContext);
Object result = null;
boolean isControl = false;
if (null != definition) {
if (null != definition && !definition.isEmpty()) {
AuthorizingContext context = new AuthorizingContext();
context.setDefinition(definition);
context.setParamContext(paramContext);
Class<?> returnType = methodInvocation.getMethod().getReturnType();
//handle reactive method
if (Mono.class.isAssignableFrom(returnType)) {
return handleReactive(definition, holder, context, ((Mono<?>) methodInvocation.proceed()));
} else if (Flux.class.isAssignableFrom(returnType)) {
return handleReactive(definition, holder, context, ((Flux<?>) methodInvocation.proceed()));
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);
}
}
Authentication authentication = Authentication.current().orElseThrow(UnAuthorizedException::new);
@@ -224,7 +201,7 @@ public class AopAuthorizingController extends StaticMethodMatcherPointcutAdvisor
log.info("publish AuthorizeDefinitionInitializedEvent,definition size:{}", definitions.size());
eventPublisher.publishEvent(new AuthorizeDefinitionInitializedEvent(definitions));
// defaultParser.destroy();
// defaultParser.destroy();
}
}

View File

@@ -30,7 +30,6 @@ public final class DefaultDataAccessController implements DataAccessController {
throw new UnsupportedOperationException();
}
this.parent = parent;
addHandler(new FieldFilterDataAccessHandler());
}

View File

@@ -8,10 +8,13 @@ import org.hswebframework.web.authorization.access.DataAccessHandler;
import org.hswebframework.web.authorization.access.FieldFilterDataAccessConfig;
import org.hswebframework.web.authorization.define.AuthorizingContext;
import org.hswebframework.web.authorization.define.Phased;
import org.reactivestreams.Publisher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
/**
@@ -46,6 +49,22 @@ public class FieldFilterDataAccessHandler implements DataAccessHandler {
}
}
protected void applyUpdateParam(FieldFilterDataAccessConfig config, Object... parameter) {
for (Object data : parameter) {
for (String field : config.getFields()) {
try {
//设置值为null,跳过修改
BeanUtilsBean.getInstance()
.getPropertyUtils()
.setProperty(data, field, null);
} catch (Exception e) {
logger.warn("can't set {} null", field, e);
}
}
}
}
/**
* @param accesses 不可操作的字段
* @param params 参数上下文
@@ -54,52 +73,80 @@ public class FieldFilterDataAccessHandler implements DataAccessHandler {
* @see org.apache.commons.beanutils.PropertyUtilsBean
*/
protected boolean doUpdateAccess(FieldFilterDataAccessConfig accesses, AuthorizingContext params) {
Map<String, Object> paramsMap = params.getParamContext().getParams();
Object supportParam = paramsMap.size() == 1 ?
paramsMap.values().iterator().next() :
paramsMap.values().stream()
// .filter(param -> (param instanceof Entity) || (param instanceof Model) || (param instanceof Map))
.findAny()
.orElse(null);
if (null != supportParam) {
for (String field : accesses.getFields()) {
try {
//设置值为null,跳过修改
BeanUtilsBean.getInstance()
.getPropertyUtils()
.setProperty(supportParam, field, null);
} catch (Exception e) {
logger.warn("can't set {} null", field, e);
}
boolean reactive = params.getParamContext().handleReactiveArguments(publisher -> {
if (publisher instanceof Mono) {
return Mono.from(publisher)
.doOnNext(data -> applyUpdateParam(accesses, data));
}
} else {
logger.warn("doUpdateAccess skip ,because can not found any support entity in param!");
if (publisher instanceof Flux) {
return Flux.from(publisher)
.doOnNext(data -> applyUpdateParam(accesses, data));
}
return publisher;
});
if (reactive) {
return true;
}
applyUpdateParam(accesses, params.getParamContext().getArguments());
return true;
}
@SuppressWarnings("all")
protected void applyQueryParam(FieldFilterDataAccessConfig config, Object param) {
if (param instanceof QueryParam) {
Set<String> denyFields = config.getFields();
((QueryParam) param).excludes(denyFields.toArray(new String[0]));
return;
}
Object r = InvokeResultUtils.convertRealResult(param);
if (r instanceof Collection) {
((Collection) r).forEach(o -> setObjectPropertyNull(o, config.getFields()));
} else {
setObjectPropertyNull(r, config.getFields());
}
}
@SuppressWarnings("all")
protected boolean doQueryAccess(FieldFilterDataAccessConfig access, AuthorizingContext context) {
if (context.getDefinition().getResources().getPhased() == Phased.before) {
QueryParam entity = context.getParamContext().getParams()
.values().stream()
.filter(QueryParam.class::isInstance)
.map(QueryParam.class::cast)
.findAny().orElse(null);
if (entity == null) {
logger.warn("try validate query access, but query entity is null or not instance of org.hswebframework.web.commons.entity.Entity");
boolean reactive = context
.getParamContext()
.handleReactiveArguments(publisher -> {
if (publisher instanceof Mono) {
return Mono.from(publisher)
.doOnNext(param -> {
applyQueryParam(access, param);
});
}
return publisher;
});
if (reactive) {
return true;
}
Set<String> denyFields = access.getFields();
entity.excludes(denyFields.toArray(new String[denyFields.size()]));
} else {
Object result = InvokeResultUtils.convertRealResult(context.getParamContext().getInvokeResult());
if (result instanceof Collection) {
((Collection) result).forEach(o -> setObjectPropertyNull(o, access.getFields()));
} else {
setObjectPropertyNull(result, access.getFields());
for (Object argument : context.getParamContext().getArguments()) {
applyQueryParam(access, argument);
}
} else {
if (context.getParamContext().getInvokeResult() instanceof Publisher) {
context.getParamContext().setInvokeResult(
Flux.from((Publisher<?>) context.getParamContext().getInvokeResult())
.doOnNext(result -> {
applyQueryParam(access, result);
})
);
return true;
}
applyQueryParam(access, context.getParamContext().getInvokeResult());
}
return true;
}

View File

@@ -1,13 +1,17 @@
package org.hswebframework.web.authorization.basic.aop;
import org.hswebframework.ezorm.core.param.Param;
import org.hswebframework.ezorm.core.param.QueryParam;
import org.hswebframework.web.authorization.Authentication;
import org.hswebframework.web.authorization.ReactiveAuthenticationHolder;
import org.hswebframework.web.authorization.ReactiveAuthenticationSupplier;
import org.hswebframework.web.authorization.User;
import org.hswebframework.web.authorization.basic.handler.access.FieldFilterDataAccessHandler;
import org.hswebframework.web.authorization.basic.web.ReactiveUserTokenParser;
import org.hswebframework.web.authorization.exception.AccessDenyException;
import org.hswebframework.web.authorization.exception.UnAuthorizedException;
import org.hswebframework.web.authorization.simple.SimpleAuthentication;
import org.hswebframework.web.authorization.simple.SimpleFieldFilterDataAccessConfig;
import org.hswebframework.web.authorization.simple.SimplePermission;
import org.hswebframework.web.authorization.simple.SimpleUser;
import org.hswebframework.web.authorization.token.ParsedToken;
@@ -25,6 +29,7 @@ import reactor.test.StepVerifier;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import static org.junit.Assert.*;
@@ -69,4 +74,43 @@ 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.addSupplier(new ReactiveAuthenticationSupplier() {
@Override
public Mono<Authentication> get(String userId) {
return Mono.empty();
}
@Override
public Mono<Authentication> 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();
}
}

View File

@@ -1,10 +1,10 @@
package org.hswebframework.web.authorization.basic.aop;
import org.hswebframework.ezorm.core.param.QueryParam;
import org.hswebframework.web.authorization.Authentication;
import org.hswebframework.web.authorization.User;
import org.hswebframework.web.authorization.annotation.Authorize;
import org.hswebframework.web.authorization.annotation.QueryAction;
import org.hswebframework.web.authorization.annotation.Resource;
import org.hswebframework.web.authorization.access.DataAccessConfig;
import org.hswebframework.web.authorization.annotation.*;
import org.hswebframework.web.authorization.define.Phased;
import org.hswebframework.web.authorization.exception.UnAuthorizedException;
import org.springframework.web.bind.annotation.RestController;
@@ -27,4 +27,16 @@ public class TestController {
.switchIfEmpty(Mono.error(new UnAuthorizedException()))
.map(Authentication::getUser);
}
@QueryAction(dataAccess = @DataAccess(type = @DataAccessType(id= DataAccessConfig.DefaultType.DENY_FIELDS,name = "禁止访问字段")))
public Mono<QueryParam> queryUser(QueryParam queryParam) {
return Mono.just(queryParam);
}
@QueryAction(dataAccess = @DataAccess(type = @DataAccessType(id= DataAccessConfig.DefaultType.DENY_FIELDS,name = "禁止访问字段")))
public Mono<QueryParam> queryUser(Mono<QueryParam> queryParam) {
return queryParam;
}
}