02 - 认证机制与账号配置
维度:深入
loadBuildConfig的账号读取逻辑、三个配置源的对比、正确配置方式与安全注意 核心问题:为什么账号配在local.properties会导致 401
一、认证链全貌
flowchart LR A[loadBuildConfig<br/>build.gradle:164] --> B{"env 有<br/>artifactory_user?"} B -->|有| E[rootProject.ext.artifactory_user] B -->|无| C{"versionName env<br/>有?(Jenkins?)"} C -->|无,本地构建| D1[读 project.properties] C -->|有,Jenkins| D2[回写 project.properties] D1 --> D1a{"project.properties<br/>有账号?"} D1a -->|有| E D1a -->|无| F[artifactory_user = null] D2 --> E E --> G["根 artifactory repository<br/>username/password = ext"] G --> H[Artifactory 上传] F --> G L["local.properties<br/>(❌ 账号不被读取<br/>只读 autoBindDisable)"] -.->|不被读| A style F fill:#ffe1e1,stroke:#cc0000 style L fill:#ffe1e1,stroke:#cc0000 style H fill:#e1f5e1,stroke:#008800
二、loadBuildConfig 逐段解读(build.gradle:164-222)
2.1 第一步:读环境变量(:165-172)
def env = System.getenv()
def productName = env['PRODUCT_NAME']
def versionName = env['GLOBAL_APP_VERSION_NAME']
def versionCodeStr = env['GLOBAL_APP_VERSION_CODE']
def artifactory_user = env['artifactory_user'] // ← 第一优先:环境变量
def artifactory_password = env['artifactory_password']
print("loadBuildConfig: ..., artifactory_user=${artifactory_user}") // ⚠️ 打印的是 env 值⚠️ print 误导点:这行打印的是环境变量的值,在所有 properties 读取之前。即使后续从 project.properties 读到了账号,这里仍打印 null。排查时不能据此判断最终账号是否为空。
2.2 第二步:本地 vs Jenkins 分支(:174-206)
if (versionName == null || versionCodeStr == null || versionName.isEmpty() || versionCodeStr.isEmpty()) {
// ===== 本地构建分支(env 没有 versionName)=====
Properties projectProperties = ... project.properties
if (artifactory_user == null || artifactory_password == null || ...) {
artifactory_user = projectProperties.get("artifactory_user") // ← 第二优先:project.properties
artifactory_password = projectProperties.get("artifactory_password")
}
versionName = projectProperties.get("versionName")
versionCodeStr = projectProperties.get("versionCode")
} else {
// ===== Jenkins 分支(env 有 versionName)=====
// 把 Jenkins 输入的版本号回写 project.properties(后续 commit 记录版本历史)
projectProperties.setProperty("versionName", versionName)
projectProperties.setProperty("versionCode", versionCodeStr)
projectProperties.store(...)
}2.3 第三步:写出 ext + 读 local.properties(:209-221)
rootProject.ext.versionName = versionName
rootProject.ext.versionCode = versionCode
rootProject.ext.artifactory_user = artifactory_user // ← 最终账号
rootProject.ext.artifactory_password = artifactory_password
// use local.properties, for change build config
Properties properties = ... local.properties
String autoBindDisable = properties.get("autoBindDisable") // ← 只读 autoBindDisable!
rootProject.ext."autoBindDisable" = Boolean.parseBoolean(autoBindDisable)关键:local.properties 在这里被读,但只取
autoBindDisable,完全不读artifactory_user/password。
2.4 第四步:artifactory 插件用 ext 账号(build.gradle:148-149)
artifactory {
publish {
repository {
username = rootProject.ext.find("artifactory_user") // ← 用 loadBuildConfig 写入的账号
password = rootProject.ext.find("artifactory_password")
}
}
}三、三源对比
| 来源 | loadBuildConfig 读取? | git 跟踪 | 安全放密码? | 备注 |
|---|---|---|---|---|
环境变量 artifactory_user/password | ✅ 第一优先(:169-170) | — | ✅ 安全 | Jenkins 用;本地 export 也可 |
| project.properties | ✅ 第二优先(:185-186) | ❌ 被跟踪 | ⚠️ 放密码会泄露进仓库 | 当前存 versionName/versionCode |
| local.properties | ❌ 完全不读账号 | ✅ .gitignore | ✅ 安全(但读了没用) | 只读 autoBindDisable |
app/upload.gradle 声称读 local.properties | ❌ 死代码,没 apply | — | — | 误导根源 |
四、正确配置方式(三选一)
方式 A:环境变量(推荐,最安全)
export artifactory_user='你的工号'
export artifactory_password='你的密码或Token'
./gradlew updateApkAssembleXcdRelease- ✅ 不入库、不泄露
- ⚠️ shell 重启失效;CI(Jenkins)天然走这条路
- 注:每次 gradlew 调用要在同一个 shell里 export
方式 B:project.properties(可行但有风险)
在 project.properties 加:
artifactory_user=xxx
artifactory_password=xxx- ⚠️ project.properties 被 git 跟踪(
git ls-files可见)→ commit 前务必确认不要把密码推上去,或加到.gitignore(但会丢版本号管理) - 适合:临时本地用,且确保不 commit
方式 C:改代码回退读 local.properties(一劳永逸)
改 build.gradle:183-187,在 project.properties 读不到时回退 local.properties:
if (artifactory_user == null || artifactory_password == null
|| artifactory_user.isEmpty() || artifactory_password.isEmpty()) {
artifactory_user = projectProperties.get("artifactory_user")
artifactory_password = projectProperties.get("artifactory_password")
}
// ↓ 新增:回退 local.properties(敏感凭据的标准位置)
if (artifactory_user == null || artifactory_password == null
|| artifactory_user.isEmpty() || artifactory_password.isEmpty()) {
Properties localProps = new Properties()
File localFile = rootProject.file("local.properties")
if (localFile.exists()) {
localProps.load(localFile.newReader("UTF-8"))
}
if (artifactory_user == null || artifactory_user.isEmpty())
artifactory_user = localProps.get("artifactory_user")
if (artifactory_password == null || artifactory_password.isEmpty())
artifactory_password = localProps.get("artifactory_password")
}- ✅ 配合 local.properties(.gitignore),安全且符合直觉
- ⚠️ 改构建脚本,影响所有人;需团队确认
- 这其实就是死代码
upload.gradle原本的逻辑,搬到生效的 loadBuildConfig 里
五、如何验证账号是否被读到
构建日志里 loadBuildConfig: ... artifactory_user=xxx 这行不可靠(打印的是 env 值)。可靠验证方式:
- 看上传结果:
Deploying artifact ...后无401→ 认证成功;有401 Unauthorized→ 账号没读到/错误。 - 临时加日志:在
build.gradle:212后加println "final artifactory_user=${artifactory_user?.length() > 0 ? 'SET' : 'NULL'}"(只打印是否为空,不泄露值)。
六、账号权限注意
即使账号正确,还需对目标仓库有写权限:
- 目标仓库:
releases(build.gradle:147) - 可能的错误:
401 Unauthorized→ 账号/密码错 或 账号没读到(null)403 Forbidden→ 账号对但无releases仓库写权限409 Conflict→releases不允许覆盖已存在版本(版本号要递增)
七、关键源码行号索引
| 内容 | 文件:行号 |
|---|---|
| loadBuildConfig 入口 | build.gradle:164 |
| 读环境变量账号 | build.gradle:169-170 |
| print(误导点) | build.gradle:171-172 |
| 本地分支读 project.properties 账号 | build.gradle:183-187 |
| 写出 ext 账号 | build.gradle:211-212 |
| 读 local.properties(只 autoBindDisable) | build.gradle:214-221 |
| artifactory 用 ext 账号 | build.gradle:148-149 |
| 死代码 upload.gradle 读 local.properties | app/upload.gradle:31-48 |