You've already forked DataMate
feat(auth): 为数据管理和RAG服务增加资源访问控制
- 在DatasetApplicationService中注入ResourceAccessService并添加所有权验证 - 在KnowledgeSetApplicationService中注入ResourceAccessService并添加所有权验证 - 修改DatasetRepository接口和实现类,增加按创建者过滤的方法 - 修改KnowledgeSetRepository接口和实现类,增加按创建者过滤的方法 - 在RAG索引器服务中添加知识库访问权限检查和作用域过滤 - 更新实体元对象处理器以使用请求用户上下文获取当前用户 - 在前端设置页面添加用户权限管理功能和角色权限控制 - 为Python标注服务增加用户上下文和数据集访问权限验证
This commit is contained in:
@@ -0,0 +1,58 @@
|
||||
package com.datamate.common.auth.application;
|
||||
|
||||
import com.datamate.common.auth.infrastructure.context.RequestUserContextHolder;
|
||||
import com.datamate.common.infrastructure.exception.BusinessAssert;
|
||||
import com.datamate.common.infrastructure.exception.SystemErrorCode;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 资源访问控制服务(基于请求用户上下文)
|
||||
*/
|
||||
@Service
|
||||
public class ResourceAccessService {
|
||||
public static final String ADMIN_ROLE_CODE = "ROLE_ADMIN";
|
||||
|
||||
public boolean isAdmin() {
|
||||
return RequestUserContextHolder.hasRole(ADMIN_ROLE_CODE);
|
||||
}
|
||||
|
||||
public String getCurrentUserId() {
|
||||
return RequestUserContextHolder.getCurrentUserId();
|
||||
}
|
||||
|
||||
public String requireCurrentUserId() {
|
||||
String currentUserId = getCurrentUserId();
|
||||
BusinessAssert.isTrue(StringUtils.hasText(currentUserId), SystemErrorCode.INSUFFICIENT_PERMISSIONS);
|
||||
return currentUserId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 资源列表查询的 owner 过滤:
|
||||
* - 管理员返回 null(不过滤)
|
||||
* - 非管理员返回当前用户ID
|
||||
*/
|
||||
public String resolveOwnerFilterUserId() {
|
||||
if (isAdmin()) {
|
||||
return null;
|
||||
}
|
||||
return requireCurrentUserId();
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验当前用户是否可访问 owner 资源
|
||||
*/
|
||||
public void assertOwnerAccess(String ownerUserId) {
|
||||
if (isAdmin()) {
|
||||
return;
|
||||
}
|
||||
String currentUserId = requireCurrentUserId();
|
||||
BusinessAssert.isTrue(
|
||||
StringUtils.hasText(ownerUserId) && Objects.equals(ownerUserId, currentUserId),
|
||||
SystemErrorCode.INSUFFICIENT_PERMISSIONS
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
package com.datamate.common.auth.infrastructure.context;
|
||||
|
||||
import lombok.Getter;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 请求级用户上下文
|
||||
*/
|
||||
@Getter
|
||||
public class RequestUserContext {
|
||||
private final String userId;
|
||||
private final String username;
|
||||
private final List<String> roles;
|
||||
|
||||
private RequestUserContext(String userId, String username, List<String> roles) {
|
||||
this.userId = userId;
|
||||
this.username = username;
|
||||
this.roles = roles == null ? Collections.emptyList() : List.copyOf(roles);
|
||||
}
|
||||
|
||||
public static RequestUserContext of(String userId, String username, List<String> roles) {
|
||||
return new RequestUserContext(userId, username, roles);
|
||||
}
|
||||
|
||||
public static RequestUserContext empty() {
|
||||
return new RequestUserContext(null, null, Collections.emptyList());
|
||||
}
|
||||
|
||||
public boolean hasRole(String roleCode) {
|
||||
if (!StringUtils.hasText(roleCode)) {
|
||||
return false;
|
||||
}
|
||||
return roles.stream().anyMatch(role -> StringUtils.hasText(role) && Objects.equals(role.trim(), roleCode));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
package com.datamate.common.auth.infrastructure.context;
|
||||
|
||||
import org.springframework.core.NamedThreadLocal;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 请求级用户上下文持有器
|
||||
*/
|
||||
public final class RequestUserContextHolder {
|
||||
private static final ThreadLocal<RequestUserContext> USER_CONTEXT_HOLDER =
|
||||
new NamedThreadLocal<>("datamate-request-user-context");
|
||||
|
||||
private RequestUserContextHolder() {
|
||||
}
|
||||
|
||||
public static void set(RequestUserContext context) {
|
||||
USER_CONTEXT_HOLDER.set(context == null ? RequestUserContext.empty() : context);
|
||||
}
|
||||
|
||||
public static RequestUserContext get() {
|
||||
RequestUserContext context = USER_CONTEXT_HOLDER.get();
|
||||
return context == null ? RequestUserContext.empty() : context;
|
||||
}
|
||||
|
||||
public static String getCurrentUserId() {
|
||||
return get().getUserId();
|
||||
}
|
||||
|
||||
public static List<String> getCurrentRoles() {
|
||||
List<String> roles = get().getRoles();
|
||||
return roles == null ? Collections.emptyList() : roles;
|
||||
}
|
||||
|
||||
public static boolean hasRole(String roleCode) {
|
||||
if (!StringUtils.hasText(roleCode)) {
|
||||
return false;
|
||||
}
|
||||
return getCurrentRoles().stream()
|
||||
.anyMatch(role -> StringUtils.hasText(role) && roleCode.equalsIgnoreCase(role.trim()));
|
||||
}
|
||||
|
||||
public static void clear() {
|
||||
USER_CONTEXT_HOLDER.remove();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
package com.datamate.common.auth.infrastructure.context;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.servlet.HandlerInterceptor;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 从网关透传请求头中提取用户上下文
|
||||
*/
|
||||
@Component
|
||||
public class RequestUserContextInterceptor implements HandlerInterceptor {
|
||||
private static final String HEADER_USER_ID = "X-User-Id";
|
||||
private static final String HEADER_USER_NAME = "X-User-Name";
|
||||
private static final String HEADER_USER_ROLES = "X-User-Roles";
|
||||
|
||||
@Override
|
||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
|
||||
String userId = normalizeValue(request.getHeader(HEADER_USER_ID));
|
||||
String username = normalizeValue(request.getHeader(HEADER_USER_NAME));
|
||||
List<String> roleCodes = parseRoleCodes(request.getHeader(HEADER_USER_ROLES));
|
||||
RequestUserContextHolder.set(RequestUserContext.of(userId, username, roleCodes));
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
|
||||
RequestUserContextHolder.clear();
|
||||
}
|
||||
|
||||
private String normalizeValue(String value) {
|
||||
if (!StringUtils.hasText(value)) {
|
||||
return null;
|
||||
}
|
||||
return value.trim();
|
||||
}
|
||||
|
||||
private List<String> parseRoleCodes(String roleHeader) {
|
||||
if (!StringUtils.hasText(roleHeader)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return Arrays.stream(roleHeader.split(","))
|
||||
.map(String::trim)
|
||||
.filter(StringUtils::hasText)
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.datamate.common.auth.infrastructure.context;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
/**
|
||||
* 请求用户上下文拦截器注册
|
||||
*/
|
||||
@Configuration
|
||||
@RequiredArgsConstructor
|
||||
public class RequestUserContextWebMvcConfigurer implements WebMvcConfigurer {
|
||||
private final RequestUserContextInterceptor requestUserContextInterceptor;
|
||||
|
||||
@Override
|
||||
public void addInterceptors(InterceptorRegistry registry) {
|
||||
registry.addInterceptor(requestUserContextInterceptor).addPathPatterns("/**");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
package com.datamate.common.infrastructure.config;
|
||||
|
||||
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
|
||||
import com.datamate.common.auth.infrastructure.context.RequestUserContextHolder;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.ibatis.reflection.MetaObject;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@@ -44,17 +46,10 @@ public class EntityMetaObjectHandler implements MetaObjectHandler {
|
||||
* 获取当前用户(需要根据你的安全框架实现)
|
||||
*/
|
||||
private String getCurrentUser() {
|
||||
// todo 这里需要根据你的安全框架实现,例如Spring Security、Shiro等
|
||||
// 示例:返回默认用户或从SecurityContext获取
|
||||
try {
|
||||
// 如果是Spring Security
|
||||
// return SecurityContextHolder.getContext().getAuthentication().getName();
|
||||
|
||||
// 临时返回默认值,请根据实际情况修改
|
||||
return "system";
|
||||
} catch (Exception e) {
|
||||
log.error("Error getting current user", e);
|
||||
return "unknown";
|
||||
String currentUserId = RequestUserContextHolder.getCurrentUserId();
|
||||
if (StringUtils.hasText(currentUserId)) {
|
||||
return currentUserId;
|
||||
}
|
||||
return "system";
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user