fix(仪表板): 分享-token数据安全

This commit is contained in:
fit2cloud-chenyw 2024-11-06 18:01:55 +08:00
parent f78ec4b4fa
commit 340734510e
11 changed files with 210 additions and 3 deletions

View File

@ -5,6 +5,7 @@ import io.dataease.api.chart.ChartDataApi;
import io.dataease.api.chart.dto.ViewDetailField;
import io.dataease.api.chart.request.ChartExcelRequest;
import io.dataease.api.chart.request.ChartExcelRequestInner;
import io.dataease.auth.DeLinkPermit;
import io.dataease.chart.constant.ChartConstants;
import io.dataease.chart.manage.ChartDataManage;
import io.dataease.constant.AuthConstant;
@ -76,6 +77,7 @@ public class ChartDataServer implements ChartDataApi {
return Math.toIntExact(Math.min(f2CLicLimitedManage.checkDatasetLimit(), limit));
}
@DeLinkPermit("#p0.sceneId")
@Override
public ChartViewDTO getData(ChartViewDTO chartViewDTO) throws Exception {
try {

View File

@ -1,7 +1,10 @@
package io.dataease.config;
import io.dataease.constant.AuthConstant;
import io.dataease.share.interceptor.LinkInterceptor;
import jakarta.annotation.Resource;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@ -11,6 +14,8 @@ import static io.dataease.utils.StaticResourceUtils.ensureSuffix;
@Configuration
public class DeMvcConfig implements WebMvcConfigurer {
@Resource
private LinkInterceptor linkInterceptor;
/**
* Configuring static resource path
@ -33,4 +38,9 @@ public class DeMvcConfig implements WebMvcConfigurer {
registry.addResourceHandler(geoUrlPattern).addResourceLocations(geoDir);
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(linkInterceptor).addPathPatterns("/**");
}
}

View File

@ -0,0 +1,110 @@
package io.dataease.share.interceptor;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.auth0.jwt.interfaces.Verification;
import io.dataease.auth.DeLinkPermit;
import io.dataease.constant.AuthConstant;
import io.dataease.exception.DEException;
import io.dataease.share.manage.XpackShareManage;
import io.dataease.share.util.LinkTokenUtil;
import io.dataease.utils.LogUtil;
import io.dataease.utils.ServletUtils;
import jakarta.annotation.Resource;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.util.Objects;
@Aspect
@Component
public class DeLinkAop {
private static final String PARAM_VARIABLE_PREFIX = "p";
private static final String SPRING_EL_FLAG = "#";
private final ExpressionParser parser = new SpelExpressionParser();
@Resource
private XpackShareManage xpackShareManage;
@Around(value = "@annotation(io.dataease.auth.DeLinkPermit)")
public Object logAround(ProceedingJoinPoint point) throws Throwable {
Object[] params = point.getArgs();
String linkToken = ServletUtils.getHead(AuthConstant.LINK_TOKEN_KEY);
if (StringUtils.isNotBlank(linkToken)) {
MethodSignature ms = (MethodSignature) point.getSignature();
Method method = ms.getMethod();
DeLinkPermit deLinkPermit = method.getAnnotation(DeLinkPermit.class);
String value = deLinkPermit.value();
if (StringUtils.isBlank(value)) {
value = SPRING_EL_FLAG + PARAM_VARIABLE_PREFIX + "0";
}
Long id = getExpression(params, value);
DecodedJWT jwt = JWT.decode(linkToken);
Long resourceId = jwt.getClaim("resourceId").asLong();
if (!id.equals(resourceId)) {
DEException.throwException("link token invalid");
return false;
}
Long uid = jwt.getClaim("uid").asLong();
String secret = xpackShareManage.queryPwd(resourceId, uid);
if (StringUtils.isBlank(secret)) {
secret = LinkTokenUtil.defaultPwd;
}
Algorithm algorithm = Algorithm.HMAC256(secret);
Verification verification = JWT.require(algorithm);
JWTVerifier verifier = verification.build();
DecodedJWT decode = JWT.decode(linkToken);
algorithm.verify(decode);
verifier.verify(linkToken);
}
try {
return point.proceed(params);
} catch (Exception e) {
LogUtil.error(e.getMessage());
throw e;
}
}
public Long getExpression(Object[] params, String expression) {
StandardEvaluationContext context = buildContext(params);
Object o = resolveValue(expression, context);
if (ObjectUtils.isNotEmpty(o)) return Long.parseLong(o.toString());
return null;
}
private StandardEvaluationContext buildContext(Object[] params) {
StandardEvaluationContext context = new StandardEvaluationContext();
if (params != null && params.length == 1) {
context.setRootObject(params[0]);
}
for (int i = 0; i < Objects.requireNonNull(params).length; i++) {
Object paramValue = params[i];
context.setVariable(PARAM_VARIABLE_PREFIX + i, paramValue);
}
return context;
}
private Object resolveValue(String exp, EvaluationContext context) {
if (StringUtils.contains(exp, SPRING_EL_FLAG)) {
Expression expression = parser.parseExpression(exp);
return expression.getValue(context);
}
return exp;
}
}

View File

@ -0,0 +1,54 @@
package io.dataease.share.interceptor;
import io.dataease.auth.DeLinkPermit;
import io.dataease.constant.AuthConstant;
import io.dataease.exception.DEException;
import io.dataease.utils.ServletUtils;
import io.dataease.utils.WhitelistUtils;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import java.util.Arrays;
import java.util.List;
@Component
public class LinkInterceptor implements HandlerInterceptor {
private final static String whiteListText = "/user/ipInfo, /apisix/check";
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String linkToken = ServletUtils.getHead(AuthConstant.LINK_TOKEN_KEY);
if (linkToken == null) {
return true;
}
if (handler instanceof HandlerMethod handlerMethod) {
DeLinkPermit deLinkPermit = handlerMethod.getMethodAnnotation(DeLinkPermit.class);
if (deLinkPermit == null) {
List<String> whiteList = Arrays.stream(StringUtils.split(whiteListText, ",")).map(String::trim).toList();
String requestURI = ServletUtils.request().getRequestURI();
if (StringUtils.startsWith(requestURI, WhitelistUtils.getContextPath())) {
requestURI = requestURI.replaceFirst(WhitelistUtils.getContextPath(), "");
}
if (StringUtils.startsWith(requestURI, AuthConstant.DE_API_PREFIX)) {
requestURI = requestURI.replaceFirst(AuthConstant.DE_API_PREFIX, "");
}
boolean valid = whiteList.contains(requestURI);
if (!valid) {
DEException.throwException("分享链接Token不支持访问当前url[" + requestURI + "]");
}
return true;
}
}
return true;
}
}

View File

@ -64,6 +64,15 @@ public class XpackShareManage {
return xpackShareMapper.selectOne(queryWrapper);
}
public String queryPwd(Long resourceId, Long userId) {
QueryWrapper<XpackShare> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("creator", userId);
queryWrapper.eq("resource_id", resourceId);
XpackShare xpackShare = xpackShareMapper.selectOne(queryWrapper);
if (ObjectUtils.isEmpty(xpackShare)) return null;
return xpackShare.getPwd();
}
@Transactional
public void switcher(Long resourceId) {
XpackShare originData = queryByResource(resourceId);

View File

@ -9,7 +9,7 @@ import org.apache.commons.lang3.StringUtils;
import java.util.Date;
public class LinkTokenUtil {
private static final String defaultPwd = "link-pwd-fit2cloud";
public static final String defaultPwd = "link-pwd-fit2cloud";
public static String generate(Long uid, Long resourceId, Long exp, String pwd, Long oid) {
pwd = StringUtils.isBlank(pwd) ? defaultPwd : pwd;
Algorithm algorithm = Algorithm.HMAC256(pwd);

View File

@ -11,6 +11,7 @@ import io.dataease.api.visualization.request.DataVisualizationBaseRequest;
import io.dataease.api.visualization.request.VisualizationAppExportRequest;
import io.dataease.api.visualization.request.VisualizationWorkbranchQueryRequest;
import io.dataease.api.visualization.vo.*;
import io.dataease.auth.DeLinkPermit;
import io.dataease.chart.dao.auto.entity.CoreChartView;
import io.dataease.chart.dao.auto.mapper.CoreChartViewMapper;
import io.dataease.chart.manage.ChartDataManage;
@ -142,6 +143,7 @@ public class DataVisualizationServer implements DataVisualizationApi {
}
}
@DeLinkPermit("#p0.id")
@DeLog(id = "#p0.id", ot = LogOT.READ, stExp = "#p0.busiFlag")
@Override
@XpackInteract(value = "dataVisualizationServer", original = true)

View File

@ -7,6 +7,7 @@ import io.dataease.api.visualization.dto.VisualizationLinkJumpInfoDTO;
import io.dataease.api.visualization.request.VisualizationLinkJumpBaseRequest;
import io.dataease.api.visualization.response.VisualizationLinkJumpBaseResponse;
import io.dataease.api.visualization.vo.VisualizationViewTableVO;
import io.dataease.auth.DeLinkPermit;
import io.dataease.chart.dao.auto.entity.CoreChartView;
import io.dataease.chart.dao.auto.mapper.CoreChartViewMapper;
import io.dataease.extensions.datasource.dto.DatasetTableFieldDTO;
@ -67,6 +68,7 @@ public class VisualizationLinkJumpService implements VisualizationLinkJumpApi {
return extVisualizationLinkageMapper.queryTableFieldWithViewId(viewId);
}
@DeLinkPermit
//获取仪表板的跳转信息
@Override
public VisualizationLinkJumpBaseResponse queryVisualizationJumpInfo(Long dvId) {

View File

@ -6,6 +6,7 @@ import io.dataease.api.visualization.dto.LinkageInfoDTO;
import io.dataease.api.visualization.dto.VisualizationLinkageDTO;
import io.dataease.api.visualization.request.VisualizationLinkageRequest;
import io.dataease.api.visualization.vo.VisualizationLinkageFieldVO;
import io.dataease.auth.DeLinkPermit;
import io.dataease.chart.dao.auto.entity.CoreChartView;
import io.dataease.chart.dao.auto.mapper.CoreChartViewMapper;
import io.dataease.utils.BeanUtils;
@ -108,6 +109,7 @@ public class VisualizationLinkageService implements VisualizationLinkageApi {
return new BaseRspModel();
}
@DeLinkPermit
@Override
public Map<String, List<String>> getVisualizationAllLinkageInfo(Long dvId) {
List<LinkageInfoDTO> info = extVisualizationLinkageMapper.getPanelAllLinkageInfo(dvId);

View File

@ -2,13 +2,13 @@ package io.dataease.visualization.server;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.fasterxml.jackson.core.type.TypeReference;
import com.ibm.icu.impl.coll.CollationLoader;
import io.dataease.api.dataset.vo.CoreDatasetGroupVO;
import io.dataease.api.dataset.vo.CoreDatasetTableFieldVO;
import io.dataease.api.visualization.VisualizationOuterParamsApi;
import io.dataease.api.visualization.dto.VisualizationOuterParamsDTO;
import io.dataease.api.visualization.dto.VisualizationOuterParamsInfoDTO;
import io.dataease.api.visualization.response.VisualizationOuterParamsBaseResponse;
import io.dataease.auth.DeLinkPermit;
import io.dataease.dataset.dao.auto.entity.CoreDatasetTable;
import io.dataease.dataset.dao.auto.mapper.CoreDatasetTableMapper;
import io.dataease.engine.constant.DeTypeConstants;
@ -29,7 +29,10 @@ import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import java.util.stream.Collectors;
/**
@ -98,6 +101,7 @@ public class VisualizationOuterParamsService implements VisualizationOuterParams
}
@DeLinkPermit
@Override
public VisualizationOuterParamsBaseResponse getOuterParamsInfo(String visualizationId) {
List<VisualizationOuterParamsInfoDTO> result = extOuterParamsMapper.getVisualizationOuterParamsInfo(visualizationId);

View File

@ -0,0 +1,12 @@
package io.dataease.auth;
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DeLinkPermit {
String value() default "";
}