This commit is contained in:
@@ -0,0 +1,8 @@
|
|||||||
|
# 缩小 docker build 上下文(Dockerfile 只需 target/cas.war、docker/*、etc/cas/config/log4j2.xml)
|
||||||
|
src
|
||||||
|
.gitea
|
||||||
|
.git
|
||||||
|
*.md
|
||||||
|
maven-settings.xml
|
||||||
|
docker/cas.war
|
||||||
|
docker/cas.war.bak
|
||||||
@@ -16,3 +16,10 @@ docker/diag2_out.txt
|
|||||||
# 前端由 CasSsoFront 仓库独立发布,本后端仓库只发纯后端(cas.war)
|
# 前端由 CasSsoFront 仓库独立发布,本后端仓库只发纯后端(cas.war)
|
||||||
src/main/webapp/front/
|
src/main/webapp/front/
|
||||||
*.iml
|
*.iml
|
||||||
|
# 构建产物与二进制不入库(CI 用 maven 从源码构建 cas.war)
|
||||||
|
target/
|
||||||
|
*.war
|
||||||
|
docker/*.war
|
||||||
|
docker/cas.war.bak
|
||||||
|
nacos-config-injector.jar
|
||||||
|
_injector_build/
|
||||||
|
|||||||
@@ -0,0 +1,187 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# 启动前从 Nacos 拉取 common.yaml + cas.yaml,生成 /etc/cas/config/cas.properties。
|
||||||
|
# 替代失效的 EnvironmentPostProcessor(CAS 5.3 + Spring Cloud bootstrap 下 EPP 不触发)。
|
||||||
|
# CAS 5.3 原生加载 /etc/cas/config/cas.properties(profile=standalone),优先级高于 classpath application.yml。
|
||||||
|
import os, sys, json, urllib.request, urllib.parse, yaml
|
||||||
|
|
||||||
|
ADDR = os.environ.get("NACOS_ADDR", "nacos.yunhaixingtong.com:8848")
|
||||||
|
NS = os.environ.get("NACOS_NAMESPACE", "dev")
|
||||||
|
USER = os.environ.get("NACOS_USER", "yh-reader")
|
||||||
|
PASS = os.environ.get("NACOS_PASS", "123456")
|
||||||
|
OUT = os.environ.get("CAS_PROPERTIES", "/etc/cas/config/cas.properties")
|
||||||
|
|
||||||
|
# 库名默认值(cas.yaml 的 cas.authn.jdbc.query[0].database 仅用于拼 jdbc url,不作为独立属性写入)
|
||||||
|
DEFAULT_DB = "bsch_userbase"
|
||||||
|
DEFAULT_PARAMS = "useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true"
|
||||||
|
|
||||||
|
# CAS 不识别的属性(写入会导致 RelaxedBinding 崩溃),从输出中排除
|
||||||
|
EXCLUDE_KEYS = {"cas.authn.jdbc.query[0].database", "cas.authn.jdbc.query[0].dialect"}
|
||||||
|
|
||||||
|
# 本地兜底配置(与运行环境无关,烤进 cas.properties)
|
||||||
|
LOCAL_CONFIG = """server.port: 8443
|
||||||
|
server.ssl.enabled: true
|
||||||
|
server.ssl.key-store: /etc/cas/cas.keystore
|
||||||
|
server.ssl.key-store-password: changeit
|
||||||
|
server.ssl.key-password: changeit
|
||||||
|
server.ssl.key-alias: cas
|
||||||
|
logging.config: file:/etc/cas/config/log4j2.xml
|
||||||
|
cas.adminPagesSecurity.ip=127\\.0\\.0\\.1
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def http_get(url, timeout=10):
|
||||||
|
try:
|
||||||
|
with urllib.request.urlopen(url, timeout=timeout) as r:
|
||||||
|
return r.read().decode("utf-8") if r.status == 200 else None
|
||||||
|
except Exception:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def http_post(url, data=b"", timeout=10):
|
||||||
|
try:
|
||||||
|
req = urllib.request.Request(url, data=data, method="POST")
|
||||||
|
with urllib.request.urlopen(req, timeout=timeout) as r:
|
||||||
|
return r.read().decode("utf-8") if r.status == 200 else None
|
||||||
|
except Exception:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def login():
|
||||||
|
url = "http://%s/nacos/v1/auth/login?username=%s&password=%s" % (
|
||||||
|
ADDR, urllib.parse.quote(USER), urllib.parse.quote(PASS))
|
||||||
|
resp = http_post(url)
|
||||||
|
if not resp:
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
return json.loads(resp).get("accessToken")
|
||||||
|
except Exception:
|
||||||
|
i = resp.find('"accessToken"')
|
||||||
|
if i < 0:
|
||||||
|
return None
|
||||||
|
s = resp.find('"', i + 14)
|
||||||
|
e = resp.find('"', s + 1)
|
||||||
|
return resp[s + 1:e] if s >= 0 and e > s else None
|
||||||
|
|
||||||
|
|
||||||
|
def resolve_ns(token):
|
||||||
|
if not NS:
|
||||||
|
return ""
|
||||||
|
url = "http://%s/nacos/v1/console/namespaces" % ADDR
|
||||||
|
if token:
|
||||||
|
url += "?accessToken=" + urllib.parse.quote(token)
|
||||||
|
body = http_get(url)
|
||||||
|
if not body:
|
||||||
|
return NS
|
||||||
|
try:
|
||||||
|
data = json.loads(body).get("data", [])
|
||||||
|
except Exception:
|
||||||
|
return NS
|
||||||
|
for item in data:
|
||||||
|
if NS == item.get("namespaceShowName") or NS == item.get("namespace"):
|
||||||
|
return item.get("namespace", "")
|
||||||
|
return NS
|
||||||
|
|
||||||
|
|
||||||
|
def fetch_yaml(data_id, group, nsid, token):
|
||||||
|
base = "http://%s/nacos/v1/cs/configs" % ADDR
|
||||||
|
qs = "dataId=%s&group=%s&tenant=%s" % (
|
||||||
|
urllib.parse.quote(data_id), urllib.parse.quote(group), urllib.parse.quote(nsid))
|
||||||
|
if token:
|
||||||
|
content = http_get("%s?%s&accessToken=%s" % (base, qs, urllib.parse.quote(token)))
|
||||||
|
if content and content.strip() and '"error"' not in content:
|
||||||
|
return content
|
||||||
|
return http_get("%s?%s" % (base, qs)) or ""
|
||||||
|
|
||||||
|
|
||||||
|
def flatten(prefix, value, out):
|
||||||
|
if isinstance(value, dict):
|
||||||
|
for k, v in value.items():
|
||||||
|
flatten(k if not prefix else prefix + "." + k, v, out)
|
||||||
|
elif isinstance(value, list):
|
||||||
|
for i, v in enumerate(value):
|
||||||
|
flatten("%s[%d]" % (prefix, i) if prefix else "[%d]" % i, v, out)
|
||||||
|
elif value is not None:
|
||||||
|
out[prefix] = value
|
||||||
|
|
||||||
|
|
||||||
|
def parse_yaml(content):
|
||||||
|
if not content or not content.strip():
|
||||||
|
return {}
|
||||||
|
data = yaml.safe_load(content) or {}
|
||||||
|
flat = {}
|
||||||
|
flatten("", data, flat)
|
||||||
|
return flat
|
||||||
|
|
||||||
|
|
||||||
|
def esc(v):
|
||||||
|
if isinstance(v, bool):
|
||||||
|
return "true" if v else "false"
|
||||||
|
s = str(v)
|
||||||
|
s = s.replace("\\", "\\\\")
|
||||||
|
return s
|
||||||
|
|
||||||
|
|
||||||
|
def build(common, cas):
|
||||||
|
merged = {}
|
||||||
|
# cas.yaml 透传(排除无效属性)
|
||||||
|
for k, v in cas.items():
|
||||||
|
if k not in EXCLUDE_KEYS:
|
||||||
|
merged[k] = v
|
||||||
|
# datasource -> jdbc url(与 EPP buildCasProperties 逻辑一致)
|
||||||
|
ds_host = common.get("spring.datasource.host") or common.get("mysql.host")
|
||||||
|
ds_port = common.get("spring.datasource.port") or common.get("mysql.port") or 3306
|
||||||
|
ds_user = common.get("spring.datasource.username") or common.get("mysql.user")
|
||||||
|
ds_pass = common.get("spring.datasource.password") or common.get("mysql.password")
|
||||||
|
ds_params = common.get("spring.datasource.params") or common.get("mysql.params") or DEFAULT_PARAMS
|
||||||
|
db_name = cas.get("cas.authn.jdbc.query[0].database", DEFAULT_DB)
|
||||||
|
if ds_host:
|
||||||
|
jdbc_url = "jdbc:mysql://%s:%s/%s?%s" % (ds_host, ds_port, db_name, ds_params)
|
||||||
|
merged["cas.authn.jdbc.query[0].url"] = jdbc_url
|
||||||
|
if ds_user:
|
||||||
|
merged["cas.authn.jdbc.query[0].user"] = ds_user
|
||||||
|
if ds_pass:
|
||||||
|
merged["cas.authn.jdbc.query[0].password"] = ds_pass
|
||||||
|
merged["cas.authn.jdbc.query[0].driverClass"] = "com.mysql.jdbc.Driver"
|
||||||
|
# 透传 redis / rabbitmq
|
||||||
|
for k, v in common.items():
|
||||||
|
if k.startswith("spring.redis.") or k.startswith("spring.rabbitmq."):
|
||||||
|
merged[k] = v
|
||||||
|
return merged
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
token = login()
|
||||||
|
nsid = resolve_ns(token)
|
||||||
|
common_raw = fetch_yaml("common.yaml", "DEFAULT_GROUP", nsid, token)
|
||||||
|
cas_raw = fetch_yaml("cas.yaml", "CAS", nsid, token)
|
||||||
|
common = parse_yaml(common_raw)
|
||||||
|
cas = parse_yaml(cas_raw)
|
||||||
|
if not common and not cas:
|
||||||
|
print("[nacos_config] WARN: 未拉到任何 Nacos 配置,保留现有 cas.properties", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
merged = build(common, cas)
|
||||||
|
|
||||||
|
lines = [
|
||||||
|
"# 由 nacos_config.py 从 Nacos 生成(勿手改)",
|
||||||
|
"# namespace=%s nsid=%s common_keys=%d cas_keys=%d" % (NS, nsid, len(common), len(cas)),
|
||||||
|
]
|
||||||
|
for k in sorted(merged.keys()):
|
||||||
|
lines.append("%s=%s" % (k, esc(merged[k])))
|
||||||
|
lines.append("")
|
||||||
|
lines.append("# === 本地兜底配置 ===")
|
||||||
|
lines.append(LOCAL_CONFIG.rstrip("\n"))
|
||||||
|
|
||||||
|
content = "\n".join(lines) + "\n"
|
||||||
|
os.makedirs(os.path.dirname(OUT), exist_ok=True)
|
||||||
|
with open(OUT, "w", encoding="utf-8") as f:
|
||||||
|
f.write(content)
|
||||||
|
print("[nacos_config] wrote %d props -> %s" % (len(merged), OUT))
|
||||||
|
print("[nacos_config] always-captcha=%s redis.host=%s jdbc.host=%s server.name=%s" % (
|
||||||
|
merged.get("custom.login.always-captcha"),
|
||||||
|
merged.get("spring.redis.host"),
|
||||||
|
common.get("spring.datasource.host"),
|
||||||
|
merged.get("cas.server.name")))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
<settings>
|
||||||
|
<!-- Maven 3.8+ 默认拦截 HTTP 仓库(maven-default-http-blocker)。
|
||||||
|
nexus maven-public 仅提供 HTTP,故在此覆盖该 blocker,放行内网 nexus。 -->
|
||||||
|
<mirrors>
|
||||||
|
<mirror>
|
||||||
|
<id>maven-default-http-blocker</id>
|
||||||
|
<mirrorOf>external:http:*</mirrorOf>
|
||||||
|
<name>nexus maven-public (allow http)</name>
|
||||||
|
<url>http://192.168.10.160:8081/repository/maven-public/</url>
|
||||||
|
<blocked>false</blocked>
|
||||||
|
</mirror>
|
||||||
|
</mirrors>
|
||||||
|
<profiles>
|
||||||
|
<profile>
|
||||||
|
<id>nexus</id>
|
||||||
|
<repositories>
|
||||||
|
<repository>
|
||||||
|
<id>central</id>
|
||||||
|
<url>http://192.168.10.160:8081/repository/maven-public/</url>
|
||||||
|
<releases><enabled>true</enabled></releases>
|
||||||
|
<snapshots><enabled>true</enabled></snapshots>
|
||||||
|
</repository>
|
||||||
|
</repositories>
|
||||||
|
<pluginRepositories>
|
||||||
|
<pluginRepository>
|
||||||
|
<id>central</id>
|
||||||
|
<url>http://192.168.10.160:8081/repository/maven-public/</url>
|
||||||
|
<releases><enabled>true</enabled></releases>
|
||||||
|
<snapshots><enabled>true</enabled></snapshots>
|
||||||
|
</pluginRepository>
|
||||||
|
</pluginRepositories>
|
||||||
|
</profile>
|
||||||
|
</profiles>
|
||||||
|
<activeProfiles>
|
||||||
|
<activeProfile>nexus</activeProfile>
|
||||||
|
</activeProfiles>
|
||||||
|
</settings>
|
||||||
@@ -360,6 +360,14 @@
|
|||||||
<version>4.2.10.RELEASE</version>
|
<version>4.2.10.RELEASE</version>
|
||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- SnakeYAML:解析 Nacos 下发的 YAML 配置(webapp 已自带 1.17,provided 仅用于编译期,运行期复用 webapp 版本避免冲突) -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.yaml</groupId>
|
||||||
|
<artifactId>snakeyaml</artifactId>
|
||||||
|
<version>1.17</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</profile>
|
</profile>
|
||||||
</profiles>
|
</profiles>
|
||||||
|
|||||||
Reference in New Issue
Block a user