package com.xly.service; import org.springframework.beans.factory.annotation.Value; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Service; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; /** * xlyAi 侧身份与授权层(架构 §7)。 * *

身份唯一来源 = {@link #resolveIdentity}:透传的用户 token 经 ERP {@code /ai/whoami} * **服务端内省**换取真实的 userId/租户/用户类型——客户端自报的 userid/brandsid/usertype 一概不信。 * token 缺失或无效时:仅本地开发({@code erp.dev-login.enabled=true})回落 dev 身份; * 生产一律返回 null,调用方必须拒绝(fail-closed,绝不降级成 dev/管理员)。 * *

授权 = 表单级白名单:后端逐用户表单权限被关(只验登录 + 租户 + 行级),所以在 agent 侧按用户 * 实际授权(`sysjurisdiction`,即 `getsAuthsIdNew` 的逻辑)限制可碰的表单/菜单。 * 管理员(sysadmin/admin) = 全部权限(返回 null);Read / Write 共用此边界。 */ @Service public class AuthzService { private final JdbcTemplate jdbc; private final ErpClient erp; @Value("${erp.dev-login.username:}") private String devUserNo; @Value("${erp.dev-login.brand:}") private String devBrand; @Value("${erp.dev-login.subsidiary:}") private String devSub; @Value("${erp.dev-login.usertype:sysadmin}") private String devUserType; @Value("${erp.dev-login.userid:}") private String devUserIdOverride; public AuthzService(JdbcTemplate jdbc, ErpClient erp) { this.jdbc = jdbc; this.erp = erp; } /** * 服务端身份解析(所有端点的唯一入口)。token 有效 → 内省出的真实身份; * token 无效/过期 → null(**不**回落 dev,防止过期用户被静默提权); * token 缺失 → dev-login 开启时 dev 身份,否则 null。 */ public com.xly.agent.AgentIdentity resolveIdentity(String authorization) { String token = authorization == null ? "" : authorization.trim(); if (!token.isBlank()) { com.fasterxml.jackson.databind.JsonNode w = erp.whoami(token); if (w == null) { return null; } String userId = w.path("sId").asText(""); String brandsId = w.path("sBrandsId").asText(""); String subsidiaryId = w.path("sSubsidiaryId").asText(""); String userType = w.path("sType").asText(""); Set granted = grantedIds(userId, userType, brandsId, subsidiaryId); return new com.xly.agent.AgentIdentity(token, userId, brandsId, granted); } return erp.devLoginEnabled() ? devIdentity() : null; } /** * dev-login(仅本地开发)身份:token 为空 → ErpClient 回退 dev-login;权限集按 dev 账号解析 * (admin → null = 全部)。生产不可达(resolveIdentity 已按开关拦截)。 */ public com.xly.agent.AgentIdentity devIdentity() { String uid = devUserIdOverride != null && !devUserIdOverride.isBlank() ? devUserIdOverride : resolveDevUserId(); Set granted = grantedIds(uid, devUserType, devBrand, devSub); return new com.xly.agent.AgentIdentity(null, uid, devBrand, granted); } /** null = 全部(管理员);否则 = 有权的 id 集合。 */ private Set grantedIds(String userId, String userType, String brandsId, String subsidiaryId) { if (isAdmin(userType)) { return null; // 超管全部权限 } Set ids = new HashSet<>(); if (userId == null) { return ids; } List> rows; Integer direct = safeCount( "SELECT COUNT(*) FROM sysjurisdiction WHERE sBrandsId=? AND sSubsidiaryId=? AND sUserId=?", brandsId, subsidiaryId, userId); if (direct != null && direct > 0) { rows = jdbc.queryForList( "SELECT J.sKey AS sKey FROM sysjurisdiction J WHERE J.sBrandsId=? AND J.sSubsidiaryId=? AND J.sUserId=?", brandsId, subsidiaryId, userId); } else { String groups = queryStr( "SELECT GROUP_CONCAT(sJurisdictionClassifyId) FROM sftlogininfojurisdictiongroup " + "WHERE sBrandsId=? AND sSubsidiaryId=? AND sParentId=? " + "AND sJurisdictionClassifyId IN (SELECT sId FROM sisjurisdictionclassify WHERE bLogininfoShow=0)", brandsId, subsidiaryId, userId); if (groups == null || groups.isBlank()) { return ids; } List gl = Arrays.asList(groups.split(",")); String in = gl.stream().map(g -> "?").collect(Collectors.joining(",")); List args = new ArrayList<>(); args.add(brandsId); args.add(subsidiaryId); args.addAll(gl); rows = jdbc.queryForList( "SELECT J.sKey AS sKey FROM sysjurisdiction J WHERE J.sBrandsId=? AND J.sSubsidiaryId=? " + "AND J.sJurisdictionClassifyId IN (" + in + ")", args.toArray()); } for (Map r : rows) { Object k = r.get("sKey"); if (k != null) { for (String part : k.toString().split("-")) { if (!part.isBlank()) { ids.add(part); } } } } return ids; } private String resolveDevUserId() { return queryStr("SELECT sId FROM gdslogininfo WHERE sUserNo=? AND sBrandsId=? LIMIT 1", devUserNo, devBrand); } private boolean isAdmin(String userType) { return userType != null && (userType.equalsIgnoreCase("sysadmin") || userType.equalsIgnoreCase("admin")); } private Integer safeCount(String sql, Object... args) { try { return jdbc.queryForObject(sql, Integer.class, args); } catch (Exception e) { return 0; } } private String queryStr(String sql, Object... args) { try { List> r = jdbc.queryForList(sql, args); if (!r.isEmpty()) { Object v = r.get(0).values().iterator().next(); return v == null ? null : v.toString(); } } catch (Exception ignore) { } return null; } }