feat: 适配spring-boot3

This commit is contained in:
zhouhao
2023-12-25 11:51:41 +08:00
parent 92a0cb5f6e
commit 6a00714151
114 changed files with 519 additions and 2638 deletions

View File

@@ -3,33 +3,20 @@ package org.hswebframework.web.authorization.basic.configuration;
import org.hswebframework.web.authorization.AuthenticationManager;
import org.hswebframework.web.authorization.ReactiveAuthenticationManagerProvider;
import org.hswebframework.web.authorization.access.DataAccessController;
import org.hswebframework.web.authorization.access.DataAccessHandler;
import org.hswebframework.web.authorization.basic.aop.AopMethodAuthorizeDefinitionParser;
import org.hswebframework.web.authorization.basic.embed.EmbedAuthenticationProperties;
import org.hswebframework.web.authorization.basic.embed.EmbedReactiveAuthenticationManager;
import org.hswebframework.web.authorization.basic.handler.DefaultAuthorizingHandler;
import org.hswebframework.web.authorization.basic.handler.UserAllowPermissionHandler;
import org.hswebframework.web.authorization.basic.handler.access.DefaultDataAccessController;
import org.hswebframework.web.authorization.basic.twofactor.TwoFactorHandlerInterceptorAdapter;
import org.hswebframework.web.authorization.basic.web.*;
import org.hswebframework.web.authorization.token.UserTokenManager;
import org.hswebframework.web.authorization.twofactor.TwoFactorValidatorManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.*;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import javax.annotation.Nonnull;
import java.util.List;
/**
* 权限控制自动配置类
@@ -41,14 +28,10 @@ import java.util.List;
@EnableConfigurationProperties(EmbedAuthenticationProperties.class)
public class AuthorizingHandlerAutoConfiguration {
@Bean
public DefaultDataAccessController dataAccessController() {
return new DefaultDataAccessController();
}
@Bean
public DefaultAuthorizingHandler authorizingHandler(DataAccessController dataAccessController) {
return new DefaultAuthorizingHandler(dataAccessController);
public DefaultAuthorizingHandler authorizingHandler() {
return new DefaultAuthorizingHandler(null);
}
@@ -93,27 +76,6 @@ public class AuthorizingHandlerAutoConfiguration {
return new BearerTokenParser();
}
@Configuration
public static class DataAccessHandlerProcessor implements BeanPostProcessor {
@Autowired
private DefaultDataAccessController defaultDataAccessController;
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) {
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
if (bean instanceof DataAccessHandler) {
defaultDataAccessController.addHandler(((DataAccessHandler) bean));
}
return bean;
}
}
@Configuration
@ConditionalOnProperty(prefix = "hsweb.authorize", name = "basic-authorization", havingValue = "true")
@ConditionalOnClass(UserTokenForTypeParser.class)

View File

@@ -11,7 +11,7 @@ import org.hswebframework.web.authorization.token.UserToken;
import org.hswebframework.web.authorization.token.UserTokenManager;
import reactor.core.publisher.Mono;
import javax.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequest;
public class BasicAuthorizationTokenParser implements UserTokenForTypeParser {

View File

@@ -1,7 +1,6 @@
package org.hswebframework.web.authorization.basic.configuration;
import org.hswebframework.web.authorization.basic.aop.AopMethodAuthorizeDefinitionParser;
import org.hswebframework.web.authorization.basic.twofactor.TwoFactorHandlerInterceptorAdapter;
import org.hswebframework.web.authorization.basic.web.*;
import org.hswebframework.web.authorization.token.UserTokenManager;
import org.hswebframework.web.authorization.twofactor.TwoFactorValidatorManager;
@@ -15,7 +14,7 @@ import org.springframework.core.annotation.Order;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import javax.annotation.Nonnull;
import jakarta.annotation.Nonnull;
import java.util.List;
@AutoConfiguration
@@ -64,16 +63,4 @@ public class WebMvcAuthorizingConfiguration {
return new SessionIdUserTokenGenerator();
}
@Bean
@ConditionalOnProperty(prefix = "hsweb.authorize.two-factor", name = "enable", havingValue = "true")
@Order(100)
public WebMvcConfigurer twoFactorHandlerConfigurer(TwoFactorValidatorManager manager) {
return new WebMvcConfigurer() {
@Override
public void addInterceptors(@Nonnull InterceptorRegistry registry) {
registry.addInterceptor(new TwoFactorHandlerInterceptorAdapter(manager));
}
};
}
}

View File

@@ -11,10 +11,8 @@ import org.hswebframework.web.authorization.simple.builder.SimpleDataAccessConfi
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.util.StringUtils;
import reactor.core.publisher.Mono;
import org.springframework.util.ObjectUtils;
import javax.validation.ValidationException;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
@@ -36,10 +34,6 @@ import java.util.Optional;
* permissions:
* - id: user-manager
* actions: *
* dataAccesses:
* - action: query
* type: DENY_FIELDS
* fields: password,salt
* </pre>
*
* @author zhouhao
@@ -62,25 +56,9 @@ public class EmbedAuthenticationProperties implements InitializingBean {
@Override
public void afterPropertiesSet() {
users.forEach((id, properties) -> {
if (StringUtils.isEmpty(properties.getId())) {
if (ObjectUtils.isEmpty(properties.getId())) {
properties.setId(id);
}
for (EmbedAuthenticationInfo.PermissionInfo permissionInfo : properties.getPermissions()) {
for (Map<String, Object> objectMap : permissionInfo.getDataAccesses()) {
for (Map.Entry<String, Object> stringObjectEntry : objectMap.entrySet()) {
if (stringObjectEntry.getValue() instanceof Map) {
Map<?, ?> mapVal = ((Map) stringObjectEntry.getValue());
boolean maybeIsList = mapVal
.keySet()
.stream()
.allMatch(org.hswebframework.utils.StringUtils::isInt);
if (maybeIsList) {
stringObjectEntry.setValue(mapVal.values());
}
}
}
}
}
authentications.put(id, properties.toAuthentication(dataAccessConfigBuilderFactory));
});
}

View File

@@ -69,7 +69,6 @@ public class DefaultAuthorizingHandler implements AuthorizingHandler {
public void handleDataAccess(AuthorizingContext context) {
if (dataAccessController == null) {
log.warn("dataAccessController is null,skip result access control!");
return;
}
if (context.getDefinition().getResources() == null) {

View File

@@ -1,63 +0,0 @@
package org.hswebframework.web.authorization.basic.handler.access;
import lombok.Getter;
import lombok.Setter;
import org.hswebframework.ezorm.rdb.mapping.ReactiveRepository;
import org.hswebframework.utils.ClassUtils;
import org.hswebframework.web.aop.MethodInterceptorContext;
import org.hswebframework.web.authorization.Authentication;
import org.hswebframework.web.authorization.Dimension;
import org.hswebframework.web.authorization.DimensionType;
import org.hswebframework.web.authorization.define.AuthorizeDefinition;
import org.hswebframework.web.authorization.define.AuthorizingContext;
import org.hswebframework.web.crud.web.reactive.*;
import java.util.List;
@Getter
@Setter
public class DataAccessHandlerContext {
private Class<?> entityType;
private ReactiveRepository<?, Object> repository;
private Authentication authentication;
private List<Dimension> dimensions;
private MethodInterceptorContext paramContext;
private AuthorizeDefinition definition;
public static DataAccessHandlerContext of(AuthorizingContext context, String type) {
DataAccessHandlerContext requestContext = new DataAccessHandlerContext();
Authentication authentication = context.getAuthentication();
requestContext.setDimensions(authentication.getDimensions(type));
requestContext.setAuthentication(context.getAuthentication());
requestContext.setParamContext(context.getParamContext());
requestContext.setDefinition(context.getDefinition());
Object target = context.getParamContext().getTarget();
Class entityType = ClassUtils.getGenericType(org.springframework.util.ClassUtils.getUserClass(target));
if (entityType != Object.class) {
requestContext.setEntityType(entityType);
}
if (target instanceof ReactiveQueryController) {
requestContext.setRepository(((ReactiveQueryController) target).getRepository());
} else if (target instanceof ReactiveSaveController) {
requestContext.setRepository(((ReactiveSaveController) target).getRepository());
} else if (target instanceof ReactiveDeleteController) {
requestContext.setRepository(((ReactiveDeleteController) target).getRepository());
} else if (target instanceof ReactiveServiceQueryController) {
requestContext.setRepository(((ReactiveServiceQueryController) target).getService().getRepository());
} else if (target instanceof ReactiveServiceSaveController) {
requestContext.setRepository(((ReactiveServiceSaveController) target).getService().getRepository());
} else if (target instanceof ReactiveServiceDeleteController) {
requestContext.setRepository(((ReactiveServiceDeleteController) target).getService().getRepository());
}
// TODO: 2019-11-18 not reactive implements
return requestContext;
}
}

View File

@@ -1,59 +0,0 @@
package org.hswebframework.web.authorization.basic.handler.access;
import org.hswebframework.web.authorization.access.DataAccessConfig;
import org.hswebframework.web.authorization.access.DataAccessController;
import org.hswebframework.web.authorization.access.DataAccessHandler;
import org.hswebframework.web.authorization.define.AuthorizingContext;
import java.util.LinkedList;
import java.util.List;
/**
* 默认的行级权限控制.通过获取DataAccessHandler进行实际处理
*
* @author zhouhao
* @see DataAccessHandler
* @since 3.0
*/
public final class DefaultDataAccessController implements DataAccessController {
private DataAccessController parent;
private List<DataAccessHandler> handlers = new LinkedList<>();
public DefaultDataAccessController() {
this(null);
}
public DefaultDataAccessController(DataAccessController parent) {
if (parent == this) {
throw new UnsupportedOperationException();
}
this.parent = parent;
addHandler(new FieldFilterDataAccessHandler())
.addHandler(new DimensionDataAccessHandler());
}
@Override
public boolean doAccess(DataAccessConfig access, AuthorizingContext context) {
if (parent != null) {
parent.doAccess(access, context);
}
return handlers.stream()
.filter(handler -> handler.isSupport(access))
.allMatch(handler -> handler.handle(access, context));
}
public DefaultDataAccessController addHandler(DataAccessHandler handler) {
handlers.add(handler);
return this;
}
public void setHandlers(List<DataAccessHandler> handlers) {
this.handlers = handlers;
}
public List<DataAccessHandler> getHandlers() {
return handlers;
}
}

View File

@@ -1,440 +0,0 @@
package org.hswebframework.web.authorization.basic.handler.access;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.hswebframework.ezorm.core.param.Param;
import org.hswebframework.ezorm.core.param.QueryParam;
import org.hswebframework.web.api.crud.entity.Entity;
import org.hswebframework.web.api.crud.entity.QueryParamEntity;
import org.hswebframework.web.authorization.Authentication;
import org.hswebframework.web.authorization.Dimension;
import org.hswebframework.web.authorization.Permission;
import org.hswebframework.web.authorization.access.DataAccessConfig;
import org.hswebframework.web.authorization.access.DataAccessHandler;
import org.hswebframework.web.authorization.annotation.DimensionDataAccess;
import org.hswebframework.web.authorization.define.AuthorizingContext;
import org.hswebframework.web.authorization.define.Phased;
import org.hswebframework.web.authorization.exception.AccessDenyException;
import org.hswebframework.web.authorization.simple.DimensionDataAccessConfig;
import org.hswebframework.web.bean.FastBeanCopier;
import org.reactivestreams.Publisher;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@Slf4j
public class DimensionDataAccessHandler implements DataAccessHandler {
@Override
public boolean isSupport(DataAccessConfig access) {
return access instanceof DimensionDataAccessConfig;
}
@Override
public boolean handle(DataAccessConfig access, AuthorizingContext context) {
DimensionDataAccessConfig config = ((DimensionDataAccessConfig) access);
DataAccessHandlerContext requestContext = DataAccessHandlerContext.of(context, config.getScopeType());
if (!checkSupported(config, requestContext)) {
return false;
}
switch (access.getAction()) {
case Permission.ACTION_QUERY:
case Permission.ACTION_GET:
return doHandleQuery(config, requestContext);
case Permission.ACTION_ADD:
case Permission.ACTION_SAVE:
case Permission.ACTION_UPDATE:
return doHandleUpdate(config, requestContext);
case Permission.ACTION_DELETE:
return doHandleDelete(config, requestContext);
default:
if (log.isDebugEnabled()) {
log.debug("data access [{}] not support for {}", config.getType().getId(), access.getAction());
}
return true;
}
}
@SneakyThrows
protected String getProperty(DimensionDataAccessConfig cfg,
DataAccessHandlerContext ct) {
return Optional.ofNullable(
getMappingInfo(ct).get(cfg.getScopeType()))
.map(MappingInfo::getProperty)
.orElseGet(() -> {
log.warn("{} not supported dimension data access", ct.getParamContext().getMethod());
return null;
});
}
protected boolean checkSupported(DimensionDataAccessConfig cfg, DataAccessHandlerContext ctx) {
Authentication authentication = ctx.getAuthentication();
/*
DataAccessHelper.assert()
*/
if (CollectionUtils.isEmpty(ctx.getDimensions())) {
log.warn("user:[{}] dimension not setup", authentication.getUser().getId());
return false;
}
if (!getMappingInfo(ctx).containsKey(cfg.getScopeType())) {
log.warn("{} not supported dimension data access.see annotation: @DimensionDataAccess", ctx.getParamContext().getMethod());
return false;
}
return true;
}
protected boolean doHandleDelete(DimensionDataAccessConfig cfg,
DataAccessHandlerContext context) {
// TODO: 2019-11-18
return doHandleUpdate(cfg, context);
}
@SuppressWarnings("all")
protected Object handleById(DimensionDataAccessConfig config,
DataAccessHandlerContext context,
MappingInfo mappingInfo,
Object id) {
if (id instanceof Param || id instanceof Entity) {
applyQueryParam(config, context, id);
return id;
}
List<Dimension> dimensions = context.getDimensions();
Set<Object> scope = CollectionUtils.isNotEmpty(config.getScope()) ?
config.getScope() :
dimensions
.stream()
.map(Dimension::getId)
.collect(Collectors.toSet());
Function<Collection<Object>, Mono<Void>> reactiveCheck = obj -> context
.getRepository()
.findById(obj)
.doOnNext(r -> {
Object val = FastBeanCopier.copy(r, new HashMap<>(), FastBeanCopier.include(mappingInfo.getProperty()))
.get(mappingInfo.getProperty());
if (!StringUtils.isEmpty(val)
&& !scope.contains(val)) {
throw new AccessDenyException();
}
})
.then();
if (id instanceof Publisher) {
if (id instanceof Mono) {
return ((Mono) id)
.flatMap(r -> {
if (r instanceof Param) {
applyQueryParam(config, context, r);
return Mono.just(r);
}
return reactiveCheck.apply(r instanceof Collection ? ((Collection) r) : Collections.singleton(r));
})
.then((Mono) id);
}
if (id instanceof Flux) {
return ((Flux) id)
.filter(v -> {
if (v instanceof Param) {
applyQueryParam(config, context, v);
return false;
}
return true;
})
.collectList()
.flatMap(reactiveCheck)
.thenMany((Flux) id);
}
}
Collection<Object> idVal = id instanceof Collection ? ((Collection) id) : Collections.singleton(id);
Object result = context.getParamContext().getInvokeResult();
if (result instanceof Mono) {
context.getParamContext()
.setInvokeResult(reactiveCheck.apply(idVal).then(((Mono) result)));
} else if (result instanceof Flux) {
context.getParamContext()
.setInvokeResult(reactiveCheck.apply(idVal).thenMany(((Flux) result)));
} else {
// TODO: 2019-11-19 非响应式处理
log.warn("unsupported handle data access by id :{}", context.getParamContext().getMethod());
}
return id;
}
protected boolean doHandleUpdate(DimensionDataAccessConfig cfg,
DataAccessHandlerContext context) {
MappingInfo info = getMappingInfo(context).get(cfg.getScopeType());
if (info != null) {
if (info.idParamIndex != -1) {
Object param = context.getParamContext().getArguments()[info.idParamIndex];
context.getParamContext().getArguments()[info.idParamIndex] = handleById(cfg, context, info, param);
return true;
}
} else {
return true;
}
boolean reactive = context.getParamContext()
.handleReactiveArguments(publisher -> {
if (publisher instanceof Mono) {
return Mono.from(publisher)
.flatMap(payload -> applyReactiveUpdatePayload(cfg, info, Collections.singleton(payload), context)
.thenReturn(payload));
}
if (publisher instanceof Flux) {
return Flux.from(publisher)
.collectList()
.flatMapMany(list ->
applyReactiveUpdatePayload(cfg, info, list, context)
.flatMapIterable(v -> list));
}
return publisher;
});
if (!reactive) {
applyUpdatePayload(cfg, info, Arrays
.stream(context.getParamContext().getArguments())
.flatMap(obj -> {
if (obj instanceof Collection) {
return ((Collection<?>) obj).stream();
}
return Stream.of(obj);
})
.filter(Entity.class::isInstance)
.collect(Collectors.toSet()), context);
return true;
}
return true;
}
protected void applyUpdatePayload(DimensionDataAccessConfig config,
MappingInfo mappingInfo,
Collection<?> payloads,
DataAccessHandlerContext context) {
List<Dimension> dimensions = context.getDimensions();
Set<Object> scope = CollectionUtils.isNotEmpty(config.getScope()) ?
config.getScope() :
dimensions
.stream()
.map(Dimension::getId)
.collect(Collectors.toSet());
for (Object payload : payloads) {
if (!(payload instanceof Entity)) {
continue;
}
if (payload instanceof Param) {
applyQueryParam(config, context, ((Param) payload));
continue;
}
String property = mappingInfo.getProperty();
Map<String, Object> map = FastBeanCopier.copy(payload, new HashMap<>(), FastBeanCopier.include(property));
Object value = map.get(property);
if (StringUtils.isEmpty(value)) {
if (dimensions.size() == 1) {
map.put(property, dimensions.get(0).getId());
FastBeanCopier.copy(map, payload, property);
}
continue;
}
if (CollectionUtils.isNotEmpty(scope)) {
if (!scope.contains(value)) {
throw new AccessDenyException();
}
}
}
}
protected Mono<Void> applyReactiveUpdatePayload(DimensionDataAccessConfig config,
MappingInfo info,
Collection<?> payloads,
DataAccessHandlerContext context) {
return Mono.fromRunnable(() -> applyUpdatePayload(config, info, payloads, context));
}
protected boolean hasAccessByProperty(Set<Object> scope, String property, Object payload) {
Map<String, Object> values = FastBeanCopier.copy(payload, new HashMap<>(), FastBeanCopier.include(property));
Object val = values.get(property);
return val == null || scope.contains(val);
}
@SuppressWarnings("all")
protected boolean doHandleQuery(DimensionDataAccessConfig cfg, DataAccessHandlerContext context) {
MappingInfo mappingInfo = getMappingInfo(context).get(cfg.getScopeType());
//根据结果控制
if (context.getDefinition().getResources().getPhased() == Phased.after) {
Object result = context.getParamContext().getInvokeResult();
Set<Object> scope = CollectionUtils.isNotEmpty(cfg.getScope()) ?
cfg.getScope() :
context.getDimensions()
.stream()
.map(Dimension::getId)
.collect(Collectors.toSet());
String property = mappingInfo.getProperty();
if (result instanceof Mono) {
context.getParamContext()
.setInvokeResult(((Mono) result).
filter(data -> hasAccessByProperty(scope, property, data)));
return true;
} else if (result instanceof Flux) {
context.getParamContext()
.setInvokeResult(((Flux) result).
filter(data -> hasAccessByProperty(scope, property, data)));
return true;
}
return hasAccessByProperty(scope, property, result);
}
//根据id控制
if (mappingInfo.getIdParamIndex() >= 0) {
Object param = context.getParamContext().getArguments()[mappingInfo.idParamIndex];
context.getParamContext().getArguments()[mappingInfo.idParamIndex] = handleById(cfg, context, mappingInfo, param);
return true;
}
//根据查询条件控制
boolean reactive = context.getParamContext().handleReactiveArguments(publisher -> {
if (publisher instanceof Mono) {
return Mono
.from(publisher)
.flatMap(param -> this
.applyReactiveQueryParam(cfg, context, param)
.thenReturn(param));
}
return publisher;
});
if (!reactive) {
Object[] args = context.getParamContext().getArguments();
this.applyQueryParam(cfg, context, args);
}
return true;
}
protected String getTermType(DimensionDataAccessConfig cfg) {
return "in";
}
protected void applyQueryParam(DimensionDataAccessConfig cfg,
DataAccessHandlerContext requestContext,
Param param) {
Set<Object> scope = CollectionUtils.isNotEmpty(cfg.getScope()) ?
cfg.getScope() :
requestContext.getDimensions()
.stream()
.map(Dimension::getId)
.collect(Collectors.toSet());
QueryParamEntity entity = new QueryParamEntity();
entity.setTerms(new ArrayList<>(param.getTerms()));
entity.toNestQuery(query ->
query.where(
getProperty(cfg, requestContext),
getTermType(cfg),
scope));
param.setTerms(entity.getTerms());
}
protected void applyQueryParam(DimensionDataAccessConfig cfg,
DataAccessHandlerContext requestContext,
Object... params) {
for (Object param : params) {
if (param instanceof QueryParam) {
applyQueryParam(cfg, requestContext, (QueryParam) param);
}
}
}
protected Mono<Void> applyReactiveQueryParam(DimensionDataAccessConfig cfg,
DataAccessHandlerContext requestContext,
Object... param) {
return Mono.fromRunnable(() -> applyQueryParam(cfg, requestContext, param));
}
private Map<Method, Map<String, MappingInfo>> cache = new ConcurrentHashMap<>();
public Map<String, MappingInfo> getMappingInfo(DataAccessHandlerContext context) {
return getMappingInfo(ClassUtils.getUserClass(context.getParamContext().getTarget()), context.getParamContext().getMethod());
}
private Set<Class<? extends Annotation>> ann = new HashSet<>(Arrays.asList(DimensionDataAccess.class, DimensionDataAccess.Mapping.class));
private Map<String, MappingInfo> getMappingInfo(Class target, Method method) {
return cache.computeIfAbsent(method, m -> {
Set<Annotation> methodAnnotation = AnnotatedElementUtils.findAllMergedAnnotations(method, ann);
Set<Annotation> classAnnotation = AnnotatedElementUtils.findAllMergedAnnotations(target, ann);
List<Annotation> all = new ArrayList<>(classAnnotation);
all.addAll(methodAnnotation);
if (CollectionUtils.isEmpty(all)) {
return Collections.emptyMap();
}
Map<String, MappingInfo> mappingInfoMap = new HashMap<>();
for (Annotation annotation : all) {
if (annotation instanceof DimensionDataAccess) {
for (DimensionDataAccess.Mapping mapping : ((DimensionDataAccess) annotation).mapping()) {
mappingInfoMap.put(mapping.dimensionType(), MappingInfo.of(mapping));
}
}
if (annotation instanceof DimensionDataAccess.Mapping) {
mappingInfoMap.put(((DimensionDataAccess.Mapping) annotation).dimensionType(), MappingInfo.of(((DimensionDataAccess.Mapping) annotation)));
}
}
return mappingInfoMap;
});
}
@Getter
@Setter
@AllArgsConstructor
static class MappingInfo {
String dimension;
String property;
int idParamIndex;
static MappingInfo of(DimensionDataAccess.Mapping mapping) {
return new MappingInfo(mapping.dimensionType(), mapping.property(), mapping.idParamIndex());
}
}
}

View File

@@ -1,167 +0,0 @@
package org.hswebframework.web.authorization.basic.handler.access;
import org.apache.commons.beanutils.BeanUtilsBean;
import org.hswebframework.ezorm.core.param.QueryParam;
import org.hswebframework.web.authorization.Permission;
import org.hswebframework.web.authorization.access.DataAccessConfig;
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.Set;
/**
* 数据权限字段过滤处理,目前仅支持deny. {@link DataAccessConfig.DefaultType#DENY_FIELDS}
*
* @author zhouhao
*/
public class FieldFilterDataAccessHandler implements DataAccessHandler {
private Logger logger = LoggerFactory.getLogger(this.getClass());
@Override
public boolean isSupport(DataAccessConfig access) {
return access instanceof FieldFilterDataAccessConfig;
}
@Override
public boolean handle(DataAccessConfig access, AuthorizingContext context) {
FieldFilterDataAccessConfig filterDataAccessConfig = ((FieldFilterDataAccessConfig) access);
switch (access.getAction()) {
case Permission.ACTION_QUERY:
case Permission.ACTION_GET:
return doQueryAccess(filterDataAccessConfig, context);
case Permission.ACTION_ADD:
case Permission.ACTION_SAVE:
case Permission.ACTION_UPDATE:
return doUpdateAccess(filterDataAccessConfig, context);
default:
if (logger.isDebugEnabled()) {
logger.debug("field filter not support for {}", access.getAction());
}
return true;
}
}
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 参数上下文
* @return true
* @see BeanUtilsBean
* @see org.apache.commons.beanutils.PropertyUtilsBean
*/
protected boolean doUpdateAccess(FieldFilterDataAccessConfig accesses, AuthorizingContext params) {
boolean reactive = params.getParamContext().handleReactiveArguments(publisher -> {
if (publisher instanceof Mono) {
return Mono.from(publisher)
.doOnNext(data -> applyUpdateParam(accesses, data));
}
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) {
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;
}
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;
}
protected void setObjectPropertyNull(Object obj, Set<String> fields) {
if (null == obj) {
return;
}
for (String field : fields) {
try {
BeanUtilsBean.getInstance().getPropertyUtils().setProperty(obj, field, null);
} catch (Exception ignore) {
}
}
}
}

View File

@@ -1,18 +0,0 @@
package org.hswebframework.web.authorization.basic.handler.access;
import org.springframework.http.ResponseEntity;
public class InvokeResultUtils {
public static Object convertRealResult(Object result) {
if (result instanceof ResponseEntity) {
result = ((ResponseEntity) result).getBody();
}
// if (result instanceof ResponseMessage) {
// result = ((ResponseMessage) result).getResult();
// }
// if (result instanceof PagerResult) {
// result = ((PagerResult) result).getData();
// }
return result;
}
}

View File

@@ -1,54 +0,0 @@
package org.hswebframework.web.authorization.basic.twofactor;
import lombok.AllArgsConstructor;
import org.hswebframework.web.authorization.Authentication;
import org.hswebframework.web.authorization.User;
import org.hswebframework.web.authorization.annotation.TwoFactor;
import org.hswebframework.web.authorization.exception.NeedTwoFactorException;
import org.hswebframework.web.authorization.twofactor.TwoFactorValidator;
import org.hswebframework.web.authorization.twofactor.TwoFactorValidatorManager;
import org.springframework.util.StringUtils;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @author zhouhao
* @since 3.0.4
*/
@AllArgsConstructor
public class TwoFactorHandlerInterceptorAdapter extends HandlerInterceptorAdapter {
private final TwoFactorValidatorManager validatorManager;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (handler instanceof HandlerMethod) {
HandlerMethod method = ((HandlerMethod) handler);
TwoFactor factor = method.getMethodAnnotation(TwoFactor.class);
if (factor == null || factor.ignore()) {
return true;
}
String userId = Authentication.current()
.map(Authentication::getUser)
.map(User::getId)
.orElse(null);
TwoFactorValidator validator = validatorManager.getValidator(userId, factor.value(), factor.provider());
if (!validator.expired()) {
return true;
}
String code = request.getParameter(factor.parameter());
if (code == null) {
code = request.getHeader(factor.parameter());
}
if (StringUtils.isEmpty(code)) {
throw new NeedTwoFactorException("validation.need_two_factor_verify", factor.provider());
} else if (!validator.verify(code, factor.timeout())) {
throw new NeedTwoFactorException(factor.message(), factor.provider());
}
}
return super.preHandle(request, response, handler);
}
}

View File

@@ -7,7 +7,7 @@ import org.hswebframework.web.authorization.token.ParsedToken;
import org.hswebframework.web.id.IDGenerator;
import org.springframework.util.StringUtils;
import javax.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequest;
import java.util.Collections;
import java.util.Map;
import java.util.Optional;

View File

@@ -3,7 +3,7 @@ package org.hswebframework.web.authorization.basic.web;
import org.hswebframework.web.authorization.Authentication;
import org.hswebframework.web.utils.WebUtils;
import javax.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequest;
import java.io.Serializable;
import java.util.Collections;
import java.util.Map;

View File

@@ -5,8 +5,8 @@ import org.hswebframework.web.authorization.token.UserToken;
import org.hswebframework.web.authorization.token.UserTokenManager;
import org.springframework.beans.factory.annotation.Autowired;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpSession;
import static org.hswebframework.web.authorization.basic.web.UserTokenGenerator.TOKEN_TYPE_SESSION_ID;

View File

@@ -2,7 +2,7 @@ package org.hswebframework.web.authorization.basic.web;
import org.hswebframework.web.authorization.token.ParsedToken;
import javax.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequest;
/**
* 令牌解析器,用于在接受到请求到时候,从请求中获取令牌

View File

@@ -7,10 +7,11 @@ import org.hswebframework.web.authorization.token.UserToken;
import org.hswebframework.web.authorization.token.UserTokenHolder;
import org.hswebframework.web.authorization.token.UserTokenManager;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.HandlerInterceptor;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
@@ -20,7 +21,7 @@ import java.util.stream.Collectors;
*
* @author zhouhao
*/
public class WebUserTokenInterceptor extends HandlerInterceptorAdapter {
public class WebUserTokenInterceptor implements HandlerInterceptor {
private final UserTokenManager userTokenManager;

View File

@@ -0,0 +1,34 @@
module hsweb.authorization.basic {
requires spring.core;
requires hsweb.core;
requires hsweb.authorization.api;
requires hsweb.access.logging.api;
requires spring.beans;
requires spring.boot.autoconfigure;
requires spring.context;
requires spring.boot;
requires reactor.core;
requires static lombok;
requires fastjson;
requires commons.collections;
requires com.fasterxml.jackson.annotation;
requires jakarta.annotation;
requires org.slf4j;
requires spring.aop;
requires org.reactivestreams;
requires spring.web;
requires org.apache.commons.collections4;
requires jakarta.validation;
requires io.swagger.v3.oas.annotations;
requires spring.webmvc;
requires jakarta.servlet;
requires org.apache.commons.codec;
exports org.hswebframework.web.authorization.basic.web;
exports org.hswebframework.web.authorization.basic.aop;
exports org.hswebframework.web.authorization.basic.configuration;
exports org.hswebframework.web.authorization.basic.define;
opens org.hswebframework.web.authorization.basic.aop;
opens org.hswebframework.web.authorization.basic.configuration;
}

View File

@@ -61,113 +61,4 @@ public class AopAuthorizingControllerTest {
.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<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();
}
@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<Authentication> get(String userId) {
return Mono.empty();
}
@Override
public Mono<Authentication> 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::<Collection<Object>>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();
}
}