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 值)。可靠验证方式:

  1. 看上传结果:Deploying artifact ... 后无 401 → 认证成功;有 401 Unauthorized → 账号没读到/错误。
  2. 临时加日志:在 build.gradle:212 后加 println "final artifactory_user=${artifactory_user?.length() > 0 ? 'SET' : 'NULL'}"(只打印是否为空,不泄露值)。

六、账号权限注意

即使账号正确,还需对目标仓库有写权限:

  • 目标仓库:releases(build.gradle:147)
  • 可能的错误:
    • 401 Unauthorized → 账号/密码错 或 账号没读到(null)
    • 403 Forbidden → 账号对但无 releases 仓库写权限
    • 409 Conflictreleases 不允许覆盖已存在版本(版本号要递增)

七、关键源码行号索引

内容文件:行号
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.propertiesapp/upload.gradle:31-48