Nexus 私服部署 + HTTPS + 看门鹅 SSO 对接完整指南(Nexus 3.77 实战)

系统管理员/2026-09-18 05:0315 阅读0 赞0 评论
后端NexusMavenSSOOIDCDevOps

Nexus 私服安装部署完整文档(Nexus 3.77 + HTTPS + 看门鹅 SSO)

2026-09-18 终版 · 实战验证通过 · 适用:Debian VM + Docker + OpenVPS 环境
本文档覆盖从零安装 → HTTPS → SSO 全流程,按顺序执行即可复现整套部署。

架构总览(最终形态)

浏览器/Maven ──▶ maven.xzsoft.com(DNS → 线路机公网 IP)
                 ├─ 面板规则 http(80)  → VM:80  nginx(ACME 证书验证 + 301)
                 └─ 面板规则 https(443,SNI直通) → VM:443 nginx(Let's Encrypt 证书终结)
                      ├─ /repository/**      ─▶ 直通 Nexus(匿名拉包 / Basic 发布,不走 SSO)
                      ├─ /service/**(带凭据) ─▶ 直通 Nexus(管理 API / API Token)
                      ├─ /service/rapture/session(DELETE) ─▶ 拦截:清双 cookie(Sign out)
                      ├─ /logout             ─▶ 清本站 cookie → 看门鹅中心登出 → 登录页
                      ├─ /oauth2/**          ─▶ oauth2-proxy(OIDC 登录/回调)
                      └─ 其余(浏览器)        ─▶ oauth2-proxy ─▶ Nexus 容器
                             未登录 302 → auth.xzsoft.com 授权(每次带 force=要求重新认证)
                             已登录注入 X-Forwarded-* → 插件 OAuth2ProxyRealm 认证 + 自动建用户

选型依据:Nexus OSS 无官方 OIDC/SAML(Pro 付费功能);社区插件 nexus-oauth2-proxy-plugin
是 OSS 唯一接近原生体验的 SSO 方案,但 3.78 起官方封禁自定义插件(Karaf→Spring Boot 迁移,
无解)——3.77.2 是最后可用版本,禁止升级到 3.78+

第一部分:安装 Nexus

1.1 前置(OpenVPS 面板开 VM)

  • 参考《OpenVPS虚拟机部署服务手册.md》:面板 API 创建 VM(debian-13 镜像、注入公钥、记下
    一次性 root 密码);octet 决定端口段(如 87 → 8700-8799)
  • SSH:ssh -J root@<宿主机IP> root@172.16.0.<octet>;同 IP 重建后先 ssh-keygen -R 清旧 host key

1.2 Docker

⚠️ 新 VM 的 cloud-init 初始化会占用 apt 锁几分钟,先等锁释放再装:
while fuser /var/lib/dpkg/lock-frontend >/dev/null 2>&1; do sleep 5; done

apt-get update -qq && apt-get install -y ca-certificates curl
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc
echo "deb [arch=amd64 signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/debian trixie stable" > /etc/apt/sources.list.d/docker.list
apt-get update -qq && apt-get install -y docker-ce docker-ce-cli containerd.io

1.3 Nexus 容器(⚠️ 三个关键点:属主 / 监听 / 版本)

mkdir -p /data/nexus/deploy
chown -R 200:200 /data/nexus        # ⚠️ Nexus 容器进程 UID=200,root 属主会启动失败
docker network create nxnet

docker run -d --name nexus --restart always --network nxnet \
  -p 127.0.0.1:8701:8081 \
  -v /data/nexus:/nexus-data \
  sonatype/nexus3:3.77.2            # ⚠️ 必须钉死 3.77.2,禁止 latest(3.78+ 插件失效)

# 等就绪(约 1-2 分钟)
until [ "$(curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:8701/service/rest/v1/status)" = "200" ]; do sleep 10; done
cat /data/nexus/admin.password       # 初始密码,仅此一次

-p 127.0.0.1:8701:Nexus 绝不直接暴露公网(header 认证模式下外部可伪造 X-Forwarded-*
冒充任意用户;唯一入口必须是 nginx)。

1.4 初始化(REST API,全脚本化)

NA="admin:<自定管理密码>"; NX=http://127.0.0.1:8701/service/rest/v1

# ① 改 admin 密码
curl -s -u "admin:<初始密码>" -X PUT "$NX/security/users/admin/change-password" \
  -H 'Content-Type: text/plain' -d "<自定管理密码>"

# ② ⚠️ 接受 EULA(3.71+ 强制;不做则全站 403,连 admin 都写不进!disclaimer 必须与 GET 响应逐字一致)
curl -s -u $NA "$NX/system/eula" -o /tmp/e.json
curl -s -u $NA -X POST "$NX/system/eula" -H 'Content-Type: application/json' \
  -d "$(python3 -c 'import json;d=json.load(open("/tmp/e.json"));print(json.dumps({"accepted":True,"disclaimer":d["disclaimer"]}))')"

# ③ 启用匿名访问(mvn 拉包免认证)
curl -s -u $NA -X PUT "$NX/security/anonymous" -H 'Content-Type: application/json' \
  -d '{"enabled":true,"userId":"anonymous","realmName":"NexusAuthorizingRealm"}'

# ④ 仓库:⚠️ Nexus 3 新装**默认已预置** maven-central(proxy)/maven-releases/snapshots(maven-public group)
#    四件套,直接可用!以下创建命令仅在需要自定义时执行(对默认仓库名执行会报 "Duplicate key" 400)。
#    可选自定义示例(writePolicy ⚠️ 3.77 枚举是 ALLOW_ONCE,不是 3.96 的 DISABLE_ONCE):
if false; then
curl -s -u $NA -X POST "$NX/repositories/maven/proxy" -H 'Content-Type: application/json' -d '{
  "name":"maven-central","online":true,"storage":{"blobStoreName":"default","strictContentTypeValidation":true},
  "proxy":{"remoteUrl":"https://repo1.maven.org/maven2/","contentMaxAge":-1,"metadataMaxAge":1440},
  "negativeCache":{"enabled":true,"timeToLive":1440},"httpClient":{"blocked":false,"autoBlock":true},
  "maven":{"versionPolicy":"MIXED","layoutPolicy":"PERMISSIVE"}}'
curl -s -u $NA -X POST "$NX/repositories/maven/hosted" -H 'Content-Type: application/json' -d '{
  "name":"maven-releases","online":true,"storage":{"blobStoreName":"default","strictContentTypeValidation":true,"writePolicy":"ALLOW_ONCE"},
  "maven":{"versionPolicy":"RELEASE","layoutPolicy":"STRICT"}}'
curl -s -u $NA -X POST "$NX/repositories/maven/hosted" -H 'Content-Type: application/json' -d '{
  "name":"maven-snapshots","online":true,"storage":{"blobStoreName":"default","strictContentTypeValidation":true,"writePolicy":"ALLOW"},
  "maven":{"versionPolicy":"SNAPSHOT","layoutPolicy":"STRICT"}}'
curl -s -u $NA -X POST "$NX/repositories/maven/group" -H 'Content-Type: application/json' -d '{
  "name":"maven-public","online":true,"storage":{"blobStoreName":"default","strictContentTypeValidation":true},
  "group":{"memberNames":["maven-releases","maven-snapshots","maven-central"]},
  "maven":{"versionPolicy":"MIXED","layoutPolicy":"PERMISSIVE"}}'
fi
# 验证四件套存在:
curl -s -u $NA "$NX/repositories" | grep -oE '"name" : "maven-[a-z]+"' | sort -u

# ⑤ CI 发布账号
curl -s -u $NA -X POST "$NX/security/users" -H 'Content-Type: application/json' \
  -d '{"userId":"deploy","firstName":"CI","lastName":"Deploy","emailAddress":"deploy@example.com",
       "password":"<强随机>","status":"active","roles":["nx-admin"]}'

Maven 客户端(settings.xml):

<mirror><id>nexus</id><mirrorOf>*</mirrorOf>
  <url>https://maven.xzsoft.com/repository/maven-public/</url></mirror>

1.5 可选:关闭首页公告(Outreach)

新装实例首页会显示 Sonatype 推送的广告性内容(如 "Known Issue for Azure Blob Store Users",
与本地部署无关)和欢迎页推广卡片——由 Outreach: Management capability 下发,禁用即消失:

# ① 找到 Outreach capability 的 id
curl -s -u $NA -X POST http://127.0.0.1:8701/service/extdirect -H 'Content-Type: application/json' \
  -d '{"action":"capability_Capability","method":"read","type":"rpc","tid":1,"data":[]}' \
  | grep -oE '"id":"[^"]*","typeId":"OutreachManagementCapability"' | grep -oE '"id":"[^"]*"'
# ② 用该 id 禁用(下面命令把 <id> 替换为上一步结果)
curl -s -u $NA -X POST http://127.0.0.1:8701/service/extdirect -H 'Content-Type: application/json' \
  -d '{"action":"capability_Capability","method":"disable","type":"rpc","tid":2,"data":["<id>"]}'

纯展示层操作,不影响任何功能;也可在 UI 的 System → Capabilities 里对 "Outreach: Management" 点禁用。

第二部分:HTTPS(nginx + Let's Encrypt)

2.1 DNS 与转发规则(先建 http 规则才能签证书!)

  1. DNS:maven.xzsoft.com A 记录 → 线路机公网 IP(不是宿主机)
  2. 面板 API 建 http 规则(ACME 验证流量的入口):
curl -sk -X POST https://<>/panel/api/proxy -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d '{"rule_type":"http","server_name":"maven.xzsoft.com","backend_host":"172.16.0.<octet>","backend_port":80,"machine_id":<id>,"enabled":true}'

2.2 VM 内 nginx + 签证书

apt-get install -y nginx certbot
mkdir -p /var/www/html
cat > /etc/nginx/sites-available/maven <<'EOF'
server {
    listen 80; listen [::]:80;
    server_name maven.xzsoft.com;
    location /.well-known/acme-challenge/ { root /var/www/html; }
    location / { return 301 https://$host$request_uri; }
}
EOF
ln -sf /etc/nginx/sites-available/maven /etc/nginx/sites-enabled/maven
rm -f /etc/nginx/sites-enabled/default && nginx -t && systemctl reload nginx

certbot certonly --webroot -w /var/www/html -d maven.xzsoft.com \
  --non-interactive --agree-tos --register-unsafely-without-email
# 证书 90 天有效,certbot.timer 自动续期(依赖上面 80 的 ACME location,别删)

2.3 面板建 https SNI 直通规则 → VM:443

curl -sk -X POST https://<>/panel/api/proxy -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d '{"rule_type":"https","server_name":"maven.xzsoft.com","backend_host":"172.16.0.<octet>","backend_port":443,"machine_id":<id>,"enabled":true}'

(https 规则是 SNI 直通,TLS 在 VM 的 nginx 终结)

第三部分:SSO 对接看门鹅

3.1 看门鹅侧:注册接入应用

管理后台 UI(或 API)创建应用:

  • client_id:nexus;机密客户端(生成 secret 记存密码管理)
  • redirect_uri:https://maven.xzsoft.com/oauth2/callback
  • scopes:openid,profile,email;grant:authorization_code,refresh_token

依赖的看门鹅能力(2026-09-18 版已内置,旧版需升级部署):

  • GET /api/auth/sso-logout:浏览器中心登出跳板(销毁会话 + BCL 扇出 + 302 登录页)
  • 授权端强制重认证:authorize 带 approval_prompt=force/prompt=login → 销毁当前会话
    → 302 剥参 URL 重走流程(oauth2-proxy 每次授权自动带 force ⇒ "登出后访问必须重新输密码")

3.2 oauth2-proxy 容器

docker run -d --name oauth2-proxy --restart always --network nxnet -p 127.0.0.1:4180:4180 \
  quay.io/oauth2-proxy/oauth2-proxy:v7.15.1 \
  --provider=oidc \
  --oidc-issuer-url=https://auth.xzsoft.com \
  --client-id=nexus \
  --client-secret=<看门鹅应用secret> \
  --redirect-url=https://maven.xzsoft.com/oauth2/callback \
  --cookie-secret=<恰16/24/32字节随机串> \
  --cookie-name=_maven_sso \
  --cookie-secure=true --cookie-samesite=lax \
  --cookie-expire=0 \
  --email-domain='*' \
  --scope='openid profile email' \
  --code-challenge-method=S256 \
  --oidc-groups-claim=roles \
  --reverse-proxy=true \
  --pass-user-headers=true \
  --set-xauthrequest=true \
  --skip-provider-button \
  --whitelist-domain='maven.xzsoft.com' \
  --upstream=http://nexus:8081 \
  --http-address=0.0.0.0:4180

参数要点:--skip-provider-button 未登录直接跳 IdP;--cookie-expire=0 浏览器会话级
(关浏览器即需重登);--oidc-groups-claim=roles 接看门鹅应用级角色。
⚠️ 配置文件方式(--config)对格式极敏感,命令行参数最稳;cookie-secret 变更 = 全部 SSO 会话失效。

3.3 安装 SSO 插件(nexus-oauth2-proxy-plugin 3.6.1)

⚠️ 放 /deploy 目录无效(Nexus 的 Karaf 不扫描),必须手动挂载 feature:

# ① bundle 入 Karaf system repo(mvn 坐标布局)
docker exec -u 0 nexus mkdir -p /opt/sonatype/nexus/system/com/github/tumbl3w33d/nexus-oauth2-proxy-plugin/3.6.1
docker cp nexus-oauth2-proxy-plugin-3.6.1.jar nexus:/opt/sonatype/nexus/system/com/github/tumbl3w33d/nexus-oauth2-proxy-plugin/3.6.1/
docker cp nexus-oauth2-proxy-plugin-3.6.1-features.xml nexus:/opt/sonatype/nexus/system/com/github/tumbl3w33d/nexus-oauth2-proxy-plugin/3.6.1/
# jar 下载:https://github.com/tumbl3w33d/nexus-oauth2-proxy-plugin/releases/download/3.6.1/nexus-oauth2-proxy-plugin.kar
# (.kar 是 zip,用 jar tf / unzip 解出 repository/ 下两个文件)

# ② 改 community edition 的 features XML(⚠️ 容器层路径,容器重建需重做)
XML=/opt/sonatype/nexus/system/com/sonatype/nexus/assemblies/nexus-community-feature/3.77.2-02/nexus-community-feature-3.77.2-02-features.xml
#    聚合 feature 内追加一行:<feature version="3.6.1">nexus-oauth2-proxy-plugin</feature>
#    文件末尾 </features> 前追加:
#    <feature name="nexus-oauth2-proxy-plugin" version="3.6.1">
#        <bundle>mvn:com.github.tumbl3w33d/nexus-oauth2-proxy-plugin/3.6.1</bundle></feature>

# ③ 重启并验证 realm 出现
docker restart nexus
curl -s -u $NA "$NX/security/realms/available" | grep -o 'OAuth2ProxyRealm' && echo 插件OK

3.4 激活 realm

curl -s -u $NA -X PUT "$NX/security/realms/active" -H 'Content-Type: application/json' \
  -d '["OAuth2ProxyRealm","NexusAuthenticatingRealm","DefaultRole","DockerToken"]'

3.5 nginx 443 完整配置(最终形态,含登出体系)

server {
    listen 443 ssl; listen [::]:443 ssl;
    http2 on;
    server_name maven.xzsoft.com;

    ssl_certificate /etc/letsencrypt/live/maven.xzsoft.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/maven.xzsoft.com/privkey.pem;

    client_max_body_size 512m;      # deploy 大构件
    proxy_read_timeout 300s;

    # Maven 协议:匿名 + Basic(deploy/个人token),直通 Nexus;信任头一律清空防外部伪造
    location /repository/ {
        proxy_pass http://127.0.0.1:8701;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Forwarded-User "";
        proxy_set_header X-Forwarded-Email "";
        proxy_set_header X-Forwarded-Preferred-Username "";
        proxy_set_header X-Forwarded-Groups "";
    }

    # 管理/REST API:带 Authorization(脚本/CI)直通;无凭据(浏览器会话)转 oauth2-proxy
    location /service/ {
        if ($http_authorization = "") {
            rewrite ^/service/(.*)$ /__proxy/service/$1 last;
        }
        proxy_pass http://127.0.0.1:8701;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Forwarded-User "";
        proxy_set_header X-Forwarded-Email "";
        proxy_set_header X-Forwarded-Preferred-Username "";
        proxy_set_header X-Forwarded-Groups "";
    }
    location /__proxy/ {
        internal;
        rewrite ^/__proxy/(.*)$ /$1 break;
        proxy_pass http://127.0.0.1:4180;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }

    # Sign out 按钮接管:清本站双会话 cookie(UI 不自刷新,F5 后因看门鹅 force 重认证到登录页)
    location = /service/rapture/session {
        if ($request_method = DELETE) {
            rewrite ^ /__signout last;
        }
        proxy_pass http://127.0.0.1:4180;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
    location = /__signout {
        internal;
        add_header Set-Cookie "_maven_sso=; Path=/; Expires=Thu, 01 Jan 1970 00:00:00 GMT; Secure; HttpOnly; SameSite=Lax" always;
        add_header Set-Cookie "NXSESSIONID=; Path=/; Expires=Thu, 01 Jan 1970 00:00:00 GMT; Secure; HttpOnly" always;
        return 204;
    }

    # 统一彻底登出入口(书签):清本站 cookie → 看门鹅中心登出 → 登录页
    location = /logout {
        add_header Set-Cookie "_maven_sso=; Path=/; Expires=Thu, 01 Jan 1970 00:00:00 GMT; Secure; HttpOnly; SameSite=Lax" always;
        add_header Set-Cookie "NXSESSIONID=; Path=/; Expires=Thu, 01 Jan 1970 00:00:00 GMT; Secure; HttpOnly" always;
        return 302 https://auth.xzsoft.com/api/auth/sso-logout;
    }

    # oauth2-proxy 端点(登录跳转/回调)
    location /oauth2/ {
        proxy_pass http://127.0.0.1:4180;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }

    # UI 及其余浏览器流量:全部经 oauth2-proxy(未登录 302 看门鹅;已登录注入信任头转 Nexus)
    location / {
        proxy_pass http://127.0.0.1:4180;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

3.6 服务器侧管理补充(3.77 无对应 REST 时用 extdirect + basic)

# capability 管理
curl -s -u $NA -X POST http://127.0.0.1:8701/service/extdirect -H 'Content-Type: application/json' \
  -d '{"action":"capability_Capability","method":"read","type":"rpc","tid":1,"data":[]}'
# 登录 RPC(账号密码须 base64)
# {"action":"rapture_Security","method":"authenticate","type":"rpc","tid":2,"data":["<b64user>","<b64pass>"]}

第四部分:登录/登出语义与权限

操作 行为
浏览器打开私服(无会话) 302 看门鹅登录页(每次要求输密码:force 语义)
同一浏览器会话内 保持登录(SSO cookie 会话级)
点 Sign out + F5 看门鹅登录页要求重新输密码(实测终态)
/logout 书签 彻底退出(本站 + 中心会话全销毁)
mvn 拉包 匿名(不走 SSO)
mvn 发布 deploy 账号 或 个人 API Token(User→OAuth2 Proxy API Token 自助重置,仅显示一次)

权限模型:SSO 用户权限 = 插件"外部角色映射"(Security→Roles→External Role Mapping,把看门鹅
roles claim 的角色名映射到 nx-admin/nx-anonymous);直接给插件用户赋默认角色无效(插件 issue #26)。
新增可访问用户:先在 Nexus 预建同名账号并赋角色,陌生看门鹅账号默认 401(白名单制)。

第五部分:验证清单(装完自测)

curl -sI https://maven.xzsoft.com/repository/maven-public/   # 200(匿名拉包)
curl -sI -H 'Accept: text/html' https://maven.xzsoft.com/    # 302 → auth.xzsoft.com/oauth2/authorize
curl -s -u admin:<> https://maven.xzsoft.com/service/rest/v1/status   # 200(basic 管理)
mvn -q clean compile   # 客户端经 maven-public 拉包
# 浏览器:登录 → Sign out → F5 = 看门鹅登录页;/logout 后重开 maven 仍要求登录

踩坑速查(本部署真实踩过,共 14 条)

# 现象 根因与解法
1 全站 403(admin 也 403) 3.77 强制 EULA 未接受(§1.4②)
2 插件放 /deploy 不加载 Karaf 不扫描该目录;按 §3.3 手动挂 feature
3 Nexus 起不来(karaf.log 写入失败) /data/nexus 属 root;chown 200:200
4 oauth2-proxy 起不来 cookie-secret 必须 16/24/32 字节
5 oauth2-proxy 配置解析失败 ini 格式对 CRLF/键名敏感;用命令行参数
6 未登录显示 Sign In 页不跳转 缺 --skip-provider-button
7 登录后页面 401/匿名态 传给 Nexus 的身份标识取值不对(应以 preferred_username 为准)
8 Sign out "退不出去" 三层合力缺一不可:nginx 拦截清 cookie + cookie 会话级 + 看门鹅 force 重认证
9 登出后自动回弹(signed out 又 signed in) 看门鹅中心会话在 → 靠 force 重认证解决
10 sso-logout 302 但会话没销毁 sa-token token-prefix=Bearer 与 cookie 读取互斥;端点手动解析 cookie 裸值
11 登录 RPC "Authentication failed" rapture 的账号密码参数是 base64;旧 cookie 会干扰(清缓存/换无痕)
12 UI 提示 Azure Blob Store Known Issue 无关公告(非 Azure 部署),忽略
13 仓库创建 400 3.77 枚举差异:writePolicy=ALLOW_ONCE;更新接口路径带仓库名且 body 含 name
14 同 IP 重建 VM 后 SSH 失败 host key 变化,ssh-keygen -R(本机+跳板机)

运维备忘

  • 日志:docker logs nexus / oauth2-proxy;nginx access log 可见全部分流(DELETE /service/rapture/session
    返回 204 = Sign out 服务器侧已生效,别信页面表象)
  • 证书:certbot.timer 自动续期(勿删 80 的 ACME location)
  • 备份:/data/nexus 整目录(构件/DB/插件用户)
  • 升级警示:禁止升级 Nexus 3.78+(插件失效);容器重建后需重做 §3.3(features XML 在容器层)
  • 凭据存档:见 watchgoose 仓库 docs/private/生产部署拓扑.md(生产运维档,不入库)
评论0
尚无评论
© 2026 SanoBlog