GitHub Workflows中Gradle发布自托管仓库报403 Forbidden(仅jar.sha1上传失败)
GitHub Workflows中Gradle发布自托管仓库报403 Forbidden(仅jar.sha1上传失败)
这问题确实够诡异的——明明本地发布一切正常,CI里jar能传上去,偏偏sha1文件报403还连服务端日志都没记录,换谁都会头大。我先帮你梳理下核心矛盾点,再给你一些针对性的排查和解决建议:
核心问题梳理
你遇到的关键异常点结合起来指向了非典型的权限问题:
- .jar文件上传完全正常:服务端返回201,Nexus/Nginx日志能明确看到请求记录
- .jar.sha1文件上传失败:报403 Forbidden,但服务端完全没收到这个请求
- 本地对比正常:用相同凭证、相同Gradle任务执行,所有 artifacts(包括sha1文件)都能正常发布
可能的原因分析
结合你的描述,我把可能性按优先级排序:
- 403来自中间层而非Nexus:因为服务端没收到请求,这个403大概率是GitHub Actions出站代理、或者你这边的防火墙/WAF返回的,不是Nexus本身的权限拦截
- Gradle在CI环境下的发布逻辑异常:比如CI环境的Gradle版本、配置和本地不一致,导致sha1文件的请求发送逻辑出问题
- Gradle发布任务的“dirty”配置触发了CI专属异常:你提到发布任务比较“dirty”,可能存在只在CI环境下触发的逻辑漏洞
具体排查与解决建议
一、先确认403的实际来源
既然服务端没收到请求,第一步要搞清楚这个403是谁返回的:
在你的Workflow的Publish Artifacts步骤之前,添加一个手动测试sha1文件上传的步骤,用curl直接发送请求:
- name: Test SHA1 File Upload run: | # 创建一个测试用的sha1文件 echo "test-sha1-content" > test.jar.sha1 # 手动发送PUT请求到Nexus,替换成你实际的路径前缀 curl -v -u ${{ secrets.REPOSITORY_USER }}:${{ secrets.REPOSITORY_PASSWORD }} -T test.jar.sha1 "https://repository.atlasworld.fr/repository/maven-snapshots/fr/atlasworld/fresco-core/test-version/test.jar.sha1"
- 如果这个curl请求也报403且服务端无日志:说明是网络中间层(GitHub出站策略、你的防火墙/WAF)拦截了sha1后缀的请求,需要针对性调整网络规则
- 如果curl请求成功:说明是Gradle在CI环境下的发布逻辑有问题,继续往下排查
二、排查Gradle在CI环境下的行为
开启Gradle详细日志
修改Workflow的发布步骤,加上--info参数查看详细日志,重点关注sha1文件的发布请求细节:- name: Publish Artifacts run: ./gradlew publish --info env: REPO_USERNAME: ${{ secrets.REPOSITORY_USER }} REPO_PASSWORD: ${{ secrets.REPOSITORY_PASSWORD }} RELEASE: false VERSION: ${{ steps.version.outputs.version }}查看日志中关于
fresco-core-xxx.jar.sha1的请求构建、发送过程,有没有连接异常、凭证未传递等信息对齐本地与CI的Gradle版本
CI中gradle/actions/setup-gradle@v3会自动安装Gradle wrapper指定的版本,但可以先确认本地和CI的版本是否一致:- name: Check Gradle Version run: ./gradlew --version对比本地的
./gradlew --version输出,如果版本不一致,尝试在CI中指定和本地相同的Gradle版本测试简化Gradle发布配置
你提到发布任务比较“dirty”,可以先简化配置测试,比如暂时注释掉metadataSources,或者手动指定要发布的artifacts:
修改build.gradle.kts的发布配置:publishing { publications { create<MavenPublication>("core") { // 手动指定要发布的文件,绕过默认组件的可能问题 artifact(tasks.jar.get()) artifact(tasks.javadocJar.get()) // 手动添加sha1文件 val jarFile = tasks.jar.get().outputs.files.singleFile artifact(File(jarFile.absolutePath + ".sha1")) { classifier = null extension = "jar.sha1" } } } repositories { mavenLocal() maven { name = "AtlasWorld-Repository" url = URI.create("https://repository.atlasworld.fr/repository/maven-snapshot/") credentials { username = System.getenv("REPO_USERNAME") password = System.getenv("REPO_PASSWORD") } // 暂时注释掉metadataSources测试 // metadataSources { // gradleMetadata() // } } } }
三、网络与防火墙排查
- 检查你的Nexus服务器的防火墙/WAF日志,看是否有来自GitHub Actions IP段的请求被拦截(尤其是针对
.sha1后缀的文件) - 可以尝试在本地搭建一个Ubuntu容器(模拟GitHub Actions的
ubuntu-latest环境),在容器内执行完整的发布流程,看是否能复现问题,排除GitHub Actions本身的网络问题
补充:你的Workflow与Build配置参考
为了方便其他人参考,这里整理了你提供的关键配置:
Workflow文件(/.github/workflows/publish-snapshot.yml)
name: Publish - Snapshot on: push jobs: publish: strategy: matrix: java: [ 21 ] os: [ ubuntu-latest ] runs-on: ${{ matrix.os }} steps: - name: Checkout Repository uses: actions/checkout@v4 - name: Setup Java uses: actions/setup-java@v4 with: distribution: temurin java-version: ${{ matrix.java }} - name: Make Gradle Executable run: chmod +x ./gradlew - name: Setup Gradle uses: gradle/actions/setup-gradle@v3 - name: Determine Version id: version run: | GIT_BRANCH=$(git rev-parse --abbrev-ref HEAD) GIT_HASH=$(git rev-parse HEAD) VERSION="${GIT_BRANCH}-${GIT_HASH:0:7}" echo "version=$VERSION" >> "$GITHUB_OUTPUT" echo "Version: ${VERSION}" >> $GITHUB_STEP_SUMMARY - name: Publish Artifacts run: ./gradlew publish env: REPO_USERNAME: ${{ secrets.REPOSITORY_USER }} REPO_PASSWORD: ${{ secrets.REPOSITORY_PASSWORD }} RELEASE: false VERSION: ${{ steps.version.outputs.version }}
Build配置(/build.gradle.kts)
import java.net.URI plugins { id("java-library") id("maven-publish") } java { withJavadocJar() } dependencies { } publishing { publications { create<MavenPublication>("core") { from(components["java"]) } } repositories { mavenLocal() maven { name = "AtlasWorld-Repository" url = URI.create("https://repository.atlasworld.fr/repository/maven-snapshot/") credentials { username = System.getenv("REPO_USERNAME") password = System.getenv("REPO_PASSWORD") } metadataSources { gradleMetadata() } } } }
内容来源于stack exchange
相关产品推荐
相关产品推荐

