权限控制
Xiuxian 模块实现了基于角色的工具访问控制,确保 AI 只能执行用户权限范围内的操作。
身份体系
XiuxianRole 枚举
java
public enum XiuxianRole {
/**
* 宗主 - 最高权限
*/
SECT_MASTER("宗主", "宗门之主,拥有最高权限,可管理全宗门事务", "P001"),
/**
* 峰主 - 峰管理权限
*/
PEAK_MASTER("峰主", "一峰之主,可管理本峰弟子,查看本峰数据", "P004"),
/**
* 弟子 - 基础权限
*/
DISCIPLE("弟子", "普通弟子,只能查看和操作自己的信息", "");
private final String name; // 显示名称
private final String description; // 身份说明
private final String positionId; // 职位 ID 映射
}权限层级
权限控制方式
1. 注解方式
使用 @RequiresRole 注解标记工具所需权限:
java
@Tool(description = "查询修士详细信息(含敏感信息)")
@RequiresRole(value = XiuxianRole.PEAK_MASTER, message = "查看他人敏感信息需要峰主及以上权限")
public String queryUserDetail(String userName) {
// ...
}2. 注解定义
java
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RequiresRole {
XiuxianRole value(); // 所需角色
String message() default "权限不足"; // 权限不足提示
}3. 权限提取
在工具注册时提取权限要求:
java
private XiuxianRole extractRequiredRole(ToolCallback callback, List<Object> domainTools) {
String toolName = callback.getToolDefinition().name();
for (Object toolBean : domainTools) {
for (Method method : toolBean.getClass().getDeclaredMethods()) {
Tool toolAnnotation = method.getAnnotation(Tool.class);
if (toolAnnotation != null && toolAnnotation.name().equals(toolName)) {
RequiresRole roleAnnotation = method.getAnnotation(RequiresRole.class);
if (roleAnnotation != null) {
return roleAnnotation.value();
}
return XiuxianRole.DISCIPLE; // 默认最低权限
}
}
}
return XiuxianRole.DISCIPLE;
}权限检查流程
1. 工具列表过滤
根据用户身份过滤可用工具:
java
public String getToolDescriptions(XiuxianRole role) {
StringBuilder sb = new StringBuilder();
toolDefinitions.values().stream()
.sorted(Comparator.comparing(ToolDefinition::name))
.forEach(def -> {
String toolName = def.name();
XiuxianRole requiredRole = toolRoles.getOrDefault(toolName, XiuxianRole.DISCIPLE);
// 权限检查
if (!hasRolePermission(role, requiredRole)) {
return; // 跳过无权限的工具
}
sb.append("- `").append(toolName).append("` - ")
.append(def.description());
if (requiredRole != XiuxianRole.DISCIPLE) {
sb.append(" 【需要").append(requiredRole.getName()).append("权限】");
}
sb.append("\n");
});
return sb.toString();
}2. 权限判断逻辑
java
private boolean hasRolePermission(XiuxianRole userRole, XiuxianRole requiredRole) {
return switch (requiredRole) {
case DISCIPLE -> true; // 所有身份都能访问
case PEAK_MASTER -> userRole == XiuxianRole.PEAK_MASTER
|| userRole == XiuxianRole.SECT_MASTER;
case SECT_MASTER -> userRole == XiuxianRole.SECT_MASTER;
};
}3. 执行时校验
在 executeTool 中再次校验权限:
java
public String executeTool(String toolName, Map<String, Object> parameters) {
// 获取工具所需权限
XiuxianRole requiredRole = toolRoles.get(toolName);
// 获取当前用户权限
AgentContext ctx = AgentContextHolder.getContext();
XiuxianRole userRole = ctx.getRole();
// 校验权限
if (!hasRolePermission(userRole, requiredRole)) {
return "权限不足:" + requiredRole.getName() + " 才能执行此操作";
}
// 执行工具
ToolCallback callback = toolCallbacks.get(toolName);
return callback.call(JSONUtils.toJsonString(parameters));
}数据权限
峰主数据范围
峰主只能操作本峰弟子,通过 checkPeakPermission 方法检查:
java
protected String checkPeakPermission(XiuxianUserEntity user, String actionDescription) {
AgentContext ctx = AgentContextHolder.getContext();
if (ctx != null && ctx.getRole() == XiuxianRole.PEAK_MASTER) {
String userPeakId = user.getPeakId();
String ctxPeakId = ctx.getPeakId();
if (userPeakId != null && !userPeakId.equals(ctxPeakId)) {
return "权限不足:您只能" + actionDescription + "本峰弟子,【"
+ user.getName() + "】属于其他峰";
}
}
return null; // null 表示通过
}使用示例
java
@Tool(description = "调整修士灵石")
@RequiresRole(XiuxianRole.PEAK_MASTER)
public String adjustSpiritStones(String userName, Long amount) {
XiuxianUserEntity user = resolveUserByName(userName);
if (user == null) {
return error("未找到修士");
}
// 检查数据权限
String permissionError = checkPeakPermission(user, "调整灵石");
if (permissionError != null) {
return error(permissionError);
}
// 执行调整...
}身份识别
从 PositionId 解析
java
public static XiuxianRole fromPositionId(String positionId) {
if (SECT_MASTER.getPositionId().equals(positionId)) return SECT_MASTER;
if (PEAK_MASTER.getPositionId().equals(positionId)) return PEAK_MASTER;
return DISCIPLE;
}构建上下文
java
public SseEmitter chatSse(String conversationId, String message, String mockUserId) {
// 获取用户信息
XiuxianUserEntity user = userService.getById(mockUserId);
// 解析身份
XiuxianRole role = XiuxianRole.fromPositionId(user.getPositionId());
// 构建上下文
AgentContext context = AgentContext.builder()
.conversationId(conversationId)
.userId(mockUserId)
.role(role)
.xiuxianName(user.getName())
.peakId(user.getPeakId())
.build();
// ...
}System Prompt 中的权限提示
java
private String buildUserInfo(AgentContext context, XiuxianUserEntity user) {
XiuxianRole role = context.getRole();
String managementScope = role == XiuxianRole.PEAK_MASTER && user.getPeakId() != null
? "管理范围:仅限本峰弟子\n"
: "";
return """
当前用户身份:**%s**
身份说明:%s
修士姓名:%s
%s
**重要提示**:
- 你只能执行当前身份权限范围内的操作
- 涉及其他峰或更高权限的操作会被拒绝
- 查询他人敏感信息(灵石余额等)需要相应权限
""".formatted(
role.getName(),
role.getDescription(),
user.getName(),
managementScope
);
}权限不足的处理
工具列表中隐藏
无权限的工具不会出现在 System Prompt 的工具列表中,LLM 不知道这些工具的存在。
执行时拒绝
即使 LLM 尝试调用无权限的工具,执行时也会被拒绝:
java
if (!hasRolePermission(userRole, requiredRole)) {
return "权限不足:需要 " + requiredRole.getName() + " 权限才能执行此操作";
}友好提示
LLM 会用修仙世界的语气告知用户:
AI: 抱歉,查看他人敏感信息需要峰主及以上权限。您目前是弟子身份,
只能查看自己的信息。如需查看他人信息,请联系您的峰主或宗主。最佳实践
1. 最小权限原则
工具默认不需要权限,敏感操作才添加权限注解:
java
// 查询基本信息 - 无需权限
@Tool(description = "查询修士基本信息")
public String queryUserBasic(String userName) { ... }
// 查询详细信息 - 需要峰主权限
@Tool(description = "查询修士详细信息")
@RequiresRole(XiuxianRole.PEAK_MASTER)
public String queryUserDetail(String userName) { ... }2. 数据权限检查
除了角色权限,还要检查数据权限:
java
@RequiresRole(XiuxianRole.PEAK_MASTER)
public String manageUser(ManageUserDto dto) {
XiuxianUserEntity user = userService.getById(dto.getUserId());
// 峰主只能管理本峰弟子
String error = checkPeakPermission(user, "管理");
if (error != null) {
return error(error);
}
// 执行管理操作...
}3. 明确的错误提示
权限不足时,告诉用户如何获取权限:
java
@RequiresRole(value = XiuxianRole.SECT_MASTER,
message = "删除修士需要宗主权限,请联系宗主处理")
public String deleteUser(String userName) { ... }4. 双重保障
- System Prompt 层面:不告诉 LLM 无权限工具的存在
- 执行层面:再次校验权限,防止绕过
下一步
- 工具开发 - 学习工具开发
- Meta-Tool 模式 - 理解工具调用机制