AuthzService.java
6.57 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
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)。
*
* <p><b>身份唯一来源 = {@link #resolveIdentity}</b>:透传的用户 token 经 ERP {@code /ai/whoami}
* **服务端内省**换取真实的 userId/租户/用户类型——客户端自报的 userid/brandsid/usertype 一概不信。
* token 缺失或无效时:仅本地开发({@code erp.dev-login.enabled=true})回落 dev 身份;
* 生产一律返回 null,调用方必须拒绝(fail-closed,绝不降级成 dev/管理员)。
*
* <p>授权 = 表单级白名单:后端逐用户表单权限被关(只验登录 + 租户 + 行级),所以在 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<String> 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<String> granted = grantedIds(uid, devUserType, devBrand, devSub);
return new com.xly.agent.AgentIdentity(null, uid, devBrand, granted);
}
/** null = 全部(管理员);否则 = 有权的 id 集合。 */
private Set<String> grantedIds(String userId, String userType, String brandsId, String subsidiaryId) {
if (isAdmin(userType)) {
return null; // 超管全部权限
}
Set<String> ids = new HashSet<>();
if (userId == null) {
return ids;
}
List<Map<String, Object>> 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<String> gl = Arrays.asList(groups.split(","));
String in = gl.stream().map(g -> "?").collect(Collectors.joining(","));
List<Object> 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<String, Object> 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<Map<String, Object>> 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;
}
}