- 删 SignKeyInternalController(/oapi/internal/sign-key/{appId},返回 appSecret)、
SignKeyInternalIpFilter、SignKeyInternalProperties 三文件
- application.yml 删 yh.sign-key-internal 块(其中 192.168.10.0/200 本就是非法前缀,永不匹配)
- OpenApiAuthEnforceConfiguration:删 IP 白名单过滤器与 SignKeyInternalProperties 注册;
**保留 /api/open 与 /oapi/* 的强制签名**(② oapi 整段对外代理依赖它)
- OapiLogAspect:删对 SignKeyInternalController 的排除死代码
- SignKeyCacheService 契约固化:验签密钥以共享 Redis sign:app:* 为单一真源、UserBase 唯一写入方、
读写走密钥链专用库(缺省 db0)、读侧无兜底(Redis 不可用即 fail-closed)
- 实测线上该端点在容器内匿名访问为 403(IP 白名单生效)、公网无 Caddy 路由;
删除后既无密钥外泄面,也使 /userbase/oapi/* 整段代理安全
验证:mvn compile 通过;本模块单测 124 项中 5 项 MySQL 连接失败为环境性(已用 git stash 在
原始树复跑证明同样失败)。
This commit is contained in:
@@ -40,7 +40,6 @@ import java.util.Map;
|
|||||||
* 拦截 {@code controller.oapi} 包下所有方法,规则:<br>
|
* 拦截 {@code controller.oapi} 包下所有方法,规则:<br>
|
||||||
* - POST 写操作:全量自动记录(无需注解),级别/类型由 OperationLogResolver 关键词判定;<br>
|
* - POST 写操作:全量自动记录(无需注解),级别/类型由 OperationLogResolver 关键词判定;<br>
|
||||||
* - GET 查询:默认不记录(高频调用,防刷爆表),满足任一条件则记录:方法标注 {@code @OapiLog}、或配置 oapi.log-get=true;<br>
|
* - GET 查询:默认不记录(高频调用,防刷爆表),满足任一条件则记录:方法标注 {@code @OapiLog}、或配置 oapi.log-get=true;<br>
|
||||||
* - 排除 {@code SignKeyInternalController}(/oapi/internal,响应含 appSecret 明文,绝不落库);<br>
|
|
||||||
* - 请求参数与响应均经 {@link SensitiveDataMasker} 脱敏后入库;<br>
|
* - 请求参数与响应均经 {@link SensitiveDataMasker} 脱敏后入库;<br>
|
||||||
* - finally 中异步落库(有界池 oapiLogExecutor),失败不影响主业务。<br>
|
* - finally 中异步落库(有界池 oapiLogExecutor),失败不影响主业务。<br>
|
||||||
*
|
*
|
||||||
@@ -52,9 +51,6 @@ import java.util.Map;
|
|||||||
@Component
|
@Component
|
||||||
public class OapiLogAspect {
|
public class OapiLogAspect {
|
||||||
|
|
||||||
/** 内部密钥端点控制器,排除(响应含 appSecret 明文)。 */
|
|
||||||
private static final String INTERNAL_CONTROLLER = "com.yunhai.userbase.controller.oapi.SignKeyInternalController";
|
|
||||||
|
|
||||||
/** GET 默认不记录,开启本配置后全量记录。 */
|
/** GET 默认不记录,开启本配置后全量记录。 */
|
||||||
@Value("${oapi.log-get:false}")
|
@Value("${oapi.log-get:false}")
|
||||||
private boolean logGet;
|
private boolean logGet;
|
||||||
@@ -67,10 +63,6 @@ public class OapiLogAspect {
|
|||||||
|
|
||||||
@Around("execution(* com.yunhai.userbase.controller.oapi..*.*(..))")
|
@Around("execution(* com.yunhai.userbase.controller.oapi..*.*(..))")
|
||||||
public Object around(ProceedingJoinPoint point) throws Throwable {
|
public Object around(ProceedingJoinPoint point) throws Throwable {
|
||||||
if (INTERNAL_CONTROLLER.equals(point.getSignature().getDeclaringTypeName())) {
|
|
||||||
return point.proceed();
|
|
||||||
}
|
|
||||||
|
|
||||||
HttpServletRequest request = getRequest();
|
HttpServletRequest request = getRequest();
|
||||||
String httpMethod = request != null ? request.getMethod() : "";
|
String httpMethod = request != null ? request.getMethod() : "";
|
||||||
boolean isGet = "GET".equalsIgnoreCase(httpMethod);
|
boolean isGet = "GET".equalsIgnoreCase(httpMethod);
|
||||||
|
|||||||
@@ -25,8 +25,8 @@ import java.io.IOException;
|
|||||||
* 强制开放接口必须带应用签名 + UserBase 自身 CAS 认证兜底的 UserDetailsProvider。<br>
|
* 强制开放接口必须带应用签名 + UserBase 自身 CAS 认证兜底的 UserDetailsProvider。<br>
|
||||||
* <br>
|
* <br>
|
||||||
* yh-app-auth 的 {@link AppAuthFilter} 设计为「无签名头则放行」(以兼容浏览器 CAS 流),
|
* yh-app-auth 的 {@link AppAuthFilter} 设计为「无签名头则放行」(以兼容浏览器 CAS 流),
|
||||||
* 但 UserBase 作为用户中心的开放接口({/open/api/**})与内部密钥兜底端点({/oapi/internal/**})
|
* 但 UserBase 作为用户中心的开放接口({/open/api/**} 与 {/oapi/**})绝不允许匿名访问。
|
||||||
* 绝不允许匿名访问。本过滤器在 {@code AppAuthFilter} 之后运行,校验其写入的请求属性:
|
* 本过滤器在 {@code AppAuthFilter} 之后运行,校验其写入的请求属性:
|
||||||
* 已验签(属性存在)则放行,否则返回 401。<br>
|
* 已验签(属性存在)则放行,否则返回 401。<br>
|
||||||
* 顺序须晚于 {@code AppAuthFilter}(HIGHEST_PRECEDENCE+10),故置为 +11。<br>
|
* 顺序须晚于 {@code AppAuthFilter}(HIGHEST_PRECEDENCE+10),故置为 +11。<br>
|
||||||
* <br>
|
* <br>
|
||||||
@@ -39,7 +39,7 @@ import java.io.IOException;
|
|||||||
* 导致其 @ConditionalOnMissingBean 抢先注册 NoOp,PathBasedAuthInterceptor 注入后首次请求即抛错。
|
* 导致其 @ConditionalOnMissingBean 抢先注册 NoOp,PathBasedAuthInterceptor 注入后首次请求即抛错。
|
||||||
*/
|
*/
|
||||||
@Configuration
|
@Configuration
|
||||||
@EnableConfigurationProperties({ SignKeyInternalProperties.class, AppAuthProperties.class })
|
@EnableConfigurationProperties({ AppAuthProperties.class })
|
||||||
public class OpenApiAuthEnforceConfiguration {
|
public class OpenApiAuthEnforceConfiguration {
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@@ -51,21 +51,6 @@ public class OpenApiAuthEnforceConfiguration {
|
|||||||
return bean;
|
return bean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 内部开放接口(/oapi/internal/*)来源 IP 白名单:
|
|
||||||
* 在验签过滤器之前先执行(HIGHEST_PRECEDENCE + 9),先卡掉公网来源再进行后续处理。
|
|
||||||
* 配置项 yh.sign-key-internal.ips / enabled 可由 Nacos 下发。
|
|
||||||
*/
|
|
||||||
@Bean
|
|
||||||
public FilterRegistrationBean<SignKeyInternalIpFilter> signKeyInternalIpFilter(
|
|
||||||
SignKeyInternalProperties properties) {
|
|
||||||
FilterRegistrationBean<SignKeyInternalIpFilter> bean = new FilterRegistrationBean<>();
|
|
||||||
bean.setFilter(new SignKeyInternalIpFilter(properties));
|
|
||||||
bean.addUrlPatterns("/oapi/internal/*");
|
|
||||||
bean.setOrder(Ordered.HIGHEST_PRECEDENCE + 9);
|
|
||||||
return bean;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* UserBase 本地 CAS 登录用户提供者:按登录名/手机号查 UserBase 自身库返回 BoaUser。<br>
|
* UserBase 本地 CAS 登录用户提供者:按登录名/手机号查 UserBase 自身库返回 BoaUser。<br>
|
||||||
* 加 {@code @Primary} 稳定覆盖 yh-auth 默认 NoOp 实现;bean 名固定=userDetailsProvider,
|
* 加 {@code @Primary} 稳定覆盖 yh-auth 默认 NoOp 实现;bean 名固定=userDetailsProvider,
|
||||||
|
|||||||
@@ -1,124 +0,0 @@
|
|||||||
package com.yunhai.userbase.config;
|
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.util.CollectionUtils;
|
|
||||||
import org.springframework.web.filter.OncePerRequestFilter;
|
|
||||||
|
|
||||||
import javax.servlet.FilterChain;
|
|
||||||
import javax.servlet.ServletException;
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import javax.servlet.http.HttpServletResponse;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.net.Inet6Address;
|
|
||||||
import java.net.InetAddress;
|
|
||||||
import java.net.UnknownHostException;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 内部开放接口(/oapi/internal/*)来源 IP 白名单过滤器。<p>
|
|
||||||
* 防止密钥兜底端点(/oapi/internal/sign-key/{appId})被公网直接枚举调用;
|
|
||||||
* 只允许内网 / 显式配置的白名单来源通过,未命中返回 403。<p>
|
|
||||||
* 支持 X-Forwarded-For / X-Real-IP 等常见代理头解析,取最左侧真实来源 IP 做校验。
|
|
||||||
*/
|
|
||||||
public class SignKeyInternalIpFilter extends OncePerRequestFilter {
|
|
||||||
|
|
||||||
private static final Logger log = LoggerFactory.getLogger(SignKeyInternalIpFilter.class);
|
|
||||||
|
|
||||||
private final SignKeyInternalProperties properties;
|
|
||||||
|
|
||||||
public SignKeyInternalIpFilter(SignKeyInternalProperties properties) {
|
|
||||||
this.properties = properties;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
|
|
||||||
throws ServletException, IOException {
|
|
||||||
if (!properties.isEnabled()) {
|
|
||||||
filterChain.doFilter(request, response);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
String sourceIp = resolveSourceIp(request);
|
|
||||||
List<String> effectiveIps = properties.getEffectiveIps();
|
|
||||||
if (CollectionUtils.isEmpty(effectiveIps) || matchAny(sourceIp, effectiveIps)) {
|
|
||||||
filterChain.doFilter(request, response);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
log.warn("[SignKeyInternal] 拒绝非白名单来源访问内部接口: uri={}, sourceIp={}, whitelist={}",
|
|
||||||
request.getRequestURI(), sourceIp, effectiveIps);
|
|
||||||
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
|
|
||||||
response.setContentType("application/json;charset=UTF-8");
|
|
||||||
response.getWriter().write("{\"code\":403,\"msg\":\"内部接口不允许从该来源访问\",\"data\":null}");
|
|
||||||
response.getWriter().flush();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String resolveSourceIp(HttpServletRequest request) {
|
|
||||||
String[] headers = new String[] {
|
|
||||||
"X-Forwarded-For",
|
|
||||||
"X-Real-IP",
|
|
||||||
"Proxy-Client-IP",
|
|
||||||
"WL-Proxy-Client-IP",
|
|
||||||
"HTTP_CLIENT_IP",
|
|
||||||
"HTTP_X_FORWARDED_FOR"
|
|
||||||
};
|
|
||||||
for (String header : headers) {
|
|
||||||
String raw = request.getHeader(header);
|
|
||||||
if (raw == null || raw.isEmpty() || "unknown".equalsIgnoreCase(raw)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// X-Forwarded-For 可能是 多个IP逗号分隔,取最左侧
|
|
||||||
String first = raw.split(",")[0].trim();
|
|
||||||
if (!first.isEmpty()) {
|
|
||||||
return first;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return request.getRemoteAddr();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean matchAny(String ip, List<String> cidrs) {
|
|
||||||
for (String cidr : cidrs) {
|
|
||||||
if (matchCidr(ip, cidr)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean matchCidr(String ip, String cidr) {
|
|
||||||
if (ip == null || cidr == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
int prefixLen;
|
|
||||||
String network;
|
|
||||||
if (cidr.contains("/")) {
|
|
||||||
String[] parts = cidr.split("/");
|
|
||||||
network = parts[0].trim();
|
|
||||||
prefixLen = Integer.parseInt(parts[1].trim());
|
|
||||||
} else {
|
|
||||||
network = cidr.trim();
|
|
||||||
// 判断地址族决定默认前缀长度
|
|
||||||
InetAddress na = InetAddress.getByName(network);
|
|
||||||
prefixLen = (na instanceof Inet6Address) ? 128 : 32;
|
|
||||||
}
|
|
||||||
InetAddress ipAddr = InetAddress.getByName(ip);
|
|
||||||
InetAddress netAddr = InetAddress.getByName(network);
|
|
||||||
if (ipAddr.getClass() != netAddr.getClass()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
byte[] ipBytes = ipAddr.getAddress();
|
|
||||||
byte[] netBytes = netAddr.getAddress();
|
|
||||||
int bits = prefixLen;
|
|
||||||
for (int i = 0; i < ipBytes.length && bits > 0; i++) {
|
|
||||||
int mask = bits >= 8 ? 0xFF : (0xFF << (8 - bits));
|
|
||||||
if ((ipBytes[i] & mask) != (netBytes[i] & mask)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
bits -= 8;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
} catch (UnknownHostException | NumberFormatException ignored) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
package com.yunhai.userbase.config;
|
|
||||||
|
|
||||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 内部开放接口(/oapi/internal/*)访问白名单 IP 配置。<br>
|
|
||||||
* 支持单 IP、CIDR(如 10.0.0.0/8、172.16.0.0/12、192.168.0.0/16、127.0.0.1/32)。<br>
|
|
||||||
* <p>
|
|
||||||
* 可在 Nacos 上配置,例如:<pre>
|
|
||||||
* yh:
|
|
||||||
* sign-key-internal:
|
|
||||||
* ips:
|
|
||||||
* - 10.0.0.0/8
|
|
||||||
* - 172.16.0.0/12
|
|
||||||
* - 192.168.0.0/16
|
|
||||||
* - 127.0.0.1/32
|
|
||||||
* - 172.20.10.5/32 # 某台固定机器
|
|
||||||
* </pre>
|
|
||||||
* 未配置时,默认允许 10/172.16-31/192.168 私有段 + 127.0.0.1(方便本地开发/内网部署)。
|
|
||||||
*/
|
|
||||||
@ConfigurationProperties(prefix = "yh.sign-key-internal")
|
|
||||||
public class SignKeyInternalProperties {
|
|
||||||
|
|
||||||
/** 显式允许的来源 IP(CIDR 或 单 IP)。为空时使用默认私有段规则。 */
|
|
||||||
private List<String> ips = new ArrayList<>();
|
|
||||||
|
|
||||||
/** 是否启用白名单校验;设置 false 可临时关闭(生产不建议)。 */
|
|
||||||
private boolean enabled = true;
|
|
||||||
|
|
||||||
public List<String> getIps() { return ips; }
|
|
||||||
|
|
||||||
public void setIps(List<String> ips) { this.ips = ips; }
|
|
||||||
|
|
||||||
public boolean isEnabled() { return enabled; }
|
|
||||||
|
|
||||||
public void setEnabled(boolean enabled) { this.enabled = enabled; }
|
|
||||||
|
|
||||||
/** 未配置 ips 时返回默认私有段 + localhost 规则。 */
|
|
||||||
public List<String> getEffectiveIps() {
|
|
||||||
if (ips != null && !ips.isEmpty()) {
|
|
||||||
return ips;
|
|
||||||
}
|
|
||||||
return Arrays.asList(
|
|
||||||
"10.0.0.0/8",
|
|
||||||
"172.16.0.0/12",
|
|
||||||
"192.168.0.0/16",
|
|
||||||
"127.0.0.1/32",
|
|
||||||
"0:0:0:0:0:0:0:1/128"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
package com.yunhai.userbase.controller.oapi;
|
|
||||||
|
|
||||||
import com.yunhai.platform.appauth.AppAuthProperties;
|
|
||||||
import com.yunhai.userbase.config.Config;
|
|
||||||
import com.yunhai.userbase.entity.ApplicationInfo;
|
|
||||||
import com.yunhai.userbase.repository.ApplicationInfoRepository;
|
|
||||||
import org.springframework.http.ResponseEntity;
|
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
|
||||||
import org.springframework.web.bind.annotation.PathVariable;
|
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
|
||||||
|
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 验签兜底端点:供其他业务服务在「共享 Redis 宕机且本地缓存也未命中」时直连获取某应用的密钥。<br><br>
|
|
||||||
* 直接从 MySQL 读(绕过可能同宕的共享 Redis),是密钥的权威源;写入侧仍由
|
|
||||||
* {@code SignKeyCacheService} 同步到 Redis。返回形状与 BasePlatform 读侧解析契约一致:<br>
|
|
||||||
* {@code {"appKey":"100616","secret":"...","enabled":true}}。<br><br>
|
|
||||||
* 解析优先级:<br>
|
|
||||||
* 1) 本库 application_info(覆盖全部「已颁发」应用的实时密钥);<br>
|
|
||||||
* 2) 配置式受信应用 {@code yh.app-auth.trusted-apps}(平台自身默认密钥,如 应用中心 100616,
|
|
||||||
* 即便尚未落库也可解析,避免「默认 appKey 未建行」时此处返回 404 导致验签链路失败);<br>
|
|
||||||
* 两者皆无 → 404。<br><br>
|
|
||||||
* 注意:此端点返回的是应用密钥,必须仅内网可达(由部署网络隔离保障),切勿暴露到公网。
|
|
||||||
*/
|
|
||||||
@RestController
|
|
||||||
@RequestMapping(Config.OPEN_API_PATH + "internal")
|
|
||||||
public class SignKeyInternalController {
|
|
||||||
|
|
||||||
private final ApplicationInfoRepository applicationInfoRepository;
|
|
||||||
private final AppAuthProperties appAuthProperties;
|
|
||||||
|
|
||||||
public SignKeyInternalController(ApplicationInfoRepository applicationInfoRepository,
|
|
||||||
AppAuthProperties appAuthProperties) {
|
|
||||||
this.applicationInfoRepository = applicationInfoRepository;
|
|
||||||
this.appAuthProperties = appAuthProperties;
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping("sign-key/{appId}")
|
|
||||||
public ResponseEntity<Map<String, Object>> signKey(@PathVariable String appId) {
|
|
||||||
Integer key;
|
|
||||||
try {
|
|
||||||
key = Integer.parseInt(appId.trim());
|
|
||||||
} catch (NumberFormatException e) {
|
|
||||||
return ResponseEntity.notFound().build();
|
|
||||||
}
|
|
||||||
ApplicationInfo info = applicationInfoRepository.findByAppKey(key);
|
|
||||||
if (info != null && info.getAppSecret() != null && !info.getAppSecret().trim().isEmpty()) {
|
|
||||||
return ok(info.getAppKey() == null ? appId : String.valueOf(info.getAppKey()),
|
|
||||||
info.getAppSecret());
|
|
||||||
}
|
|
||||||
// 库内无此应用密钥:回退到配置式受信应用(平台自身默认密钥 100616 等),
|
|
||||||
// 避免「应用中心默认 appKey 未落库」时兜底查询返回 404。
|
|
||||||
String cfgSecret = appAuthProperties.getTrustedApps().get(appId);
|
|
||||||
if (cfgSecret != null && !cfgSecret.trim().isEmpty()) {
|
|
||||||
return ok(appId, cfgSecret);
|
|
||||||
}
|
|
||||||
return ResponseEntity.notFound().build();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ResponseEntity<Map<String, Object>> ok(String appKey, String secret) {
|
|
||||||
Map<String, Object> body = new LinkedHashMap<>();
|
|
||||||
body.put("appKey", appKey);
|
|
||||||
body.put("secret", secret);
|
|
||||||
body.put("enabled", true);
|
|
||||||
return ResponseEntity.ok(body);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -25,16 +25,21 @@ import java.util.Map;
|
|||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 应用签名密钥的 Redis 桥接服务。<br>
|
* 应用签名密钥的 Redis 桥接服务——<b>验签密钥以共享 Redis {@code sign:app:*} 为单一真源</b>。<br>
|
||||||
* <br>
|
* <br>
|
||||||
* 职责:把 {@code application_info} 表的 appKey/appSecret 同步到 Redis,供各业务服务的
|
* 职责:把 {@code application_info} 表的 appKey/appSecret 同步到 Redis,供各业务服务的
|
||||||
* common-sign-starter(SignKeyResolver)读取做验签——业务服务无需直连 UserBase 数据库。<br>
|
* yh-app-auth starter(SignKeyResolver)读取做验签——业务服务无需直连 UserBase 数据库。<br>
|
||||||
* <br>
|
* <br>
|
||||||
* 约定(读侧 starter 必须严格一致):<br>
|
* 契约(读侧 starter 必须严格一致):<br>
|
||||||
* 1) key 前缀 {@code sign:app:},后接 appKey,例 {@code sign:app:100616};<br>
|
* 1) key 前缀 {@code sign:app:},后接 appKey,例 {@code sign:app:100616};<br>
|
||||||
* 2) value 为 fastjson 序列化的 JSON
|
* 2) value 为 fastjson 序列化的 JSON
|
||||||
* 字符串:{@code {"appKey":"100616","secret":"...","enabled":true}};<br>
|
* 字符串:{@code {"appKey":"100616","secret":"...","enabled":true}};<br>
|
||||||
* 3) 不设过期时间,依赖 Redis 持久化(AOF/RDB)保证重启不丢;删除应用时直接删 key(等价于禁用)。<br>
|
* 3) 不设过期时间,依赖 Redis 持久化(AOF/RDB)保证重启不丢;删除应用时直接删 key(等价于禁用)。<br>
|
||||||
|
* 4) <b>UserBase 是唯一写入方</b>(启动 {@link #refreshAll()} + CRUD 事务提交后同步 + 每日对账),
|
||||||
|
* 各提供方只读;读写都走密钥链专用库({@code yh.app-auth.sign-redis.database},缺省 db0),
|
||||||
|
* 与应用主库解耦——否则写入方与读取方分落不同库,链路静默 401。<br>
|
||||||
|
* 5) 读侧 fail-closed:Redis 不可用即拒绝(已无本地缓存与远端兜底端点)。
|
||||||
|
* 恢复梯级:AOF 回放 → 必要时重启 UserBase 触发 {@link #refreshAll()} 全量重刷 → 每日对账兜底。<br>
|
||||||
* <br>
|
* <br>
|
||||||
* <b>变更同步说明(IDEA 灰色=正常):</b>本类的 {@code onCacheChanged(...)} 由 Spring 容器在运行时通过
|
* <b>变更同步说明(IDEA 灰色=正常):</b>本类的 {@code onCacheChanged(...)} 由 Spring 容器在运行时通过
|
||||||
* 反射注册为事务监听器({@code @TransactionalEventListener}),不会在 IDE 的静态调用链中直接显示
|
* 反射注册为事务监听器({@code @TransactionalEventListener}),不会在 IDE 的静态调用链中直接显示
|
||||||
@@ -58,9 +63,9 @@ public class SignKeyCacheService implements ApplicationRunner, ApplicationContex
|
|||||||
private StringRedisTemplate stringRedisTemplate;
|
private StringRedisTemplate stringRedisTemplate;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 密钥链专用模板(yh.app-auth.sign-redis.database 配置时由 yh-app-auth starter 注册)。
|
* 密钥链专用模板(yh-app-auth starter 缺省即注册,落 db0)。密钥写入走专用库——
|
||||||
* 存在则密钥写入走专用库——sign:app:* 是全平台共享数据,读取方(各提供方 SignKeyResolver)
|
* {@code sign:app:*} 是全平台共享数据,读取方(各提供方 SignKeyResolver)同库才能看到;
|
||||||
* 配置同一库号才能看到;未配置(bean 不存在)= 现状,跟应用主库。
|
* 若某消费方显式配了其它库号,此处须配同一值(默认无需任何配置)。
|
||||||
*/
|
*/
|
||||||
@Autowired(required = false)
|
@Autowired(required = false)
|
||||||
@org.springframework.beans.factory.annotation.Qualifier("signKeyStringRedisTemplate")
|
@org.springframework.beans.factory.annotation.Qualifier("signKeyStringRedisTemplate")
|
||||||
|
|||||||
@@ -71,12 +71,6 @@ yh:
|
|||||||
identity:
|
identity:
|
||||||
# 允许调用 oapi/identity/switch 的应用 appKey 白名单(逗号分隔;100001=UserHome,后期会新增)
|
# 允许调用 oapi/identity/switch 的应用 appKey 白名单(逗号分隔;100001=UserHome,后期会新增)
|
||||||
switch-allowed-app-keys: 100001,100002
|
switch-allowed-app-keys: 100001,100002
|
||||||
sign-key-internal:
|
|
||||||
enabled: true
|
|
||||||
ips:
|
|
||||||
- 10.0.0.0/8
|
|
||||||
- 172.16.0.0/12
|
|
||||||
- 192.168.10.0/200
|
|
||||||
|
|
||||||
# 接口文档(springdoc):分组见 SwaggerConfig(oapi=开放接口契约权威源 / api=业务接口)
|
# 接口文档(springdoc):分组见 SwaggerConfig(oapi=开放接口契约权威源 / api=业务接口)
|
||||||
# 契约快照导出:GET /v3/api-docs/oapi
|
# 契约快照导出:GET /v3/api-docs/oapi
|
||||||
|
|||||||
Reference in New Issue
Block a user