Skip to content

[BUG] 解绑 SSO/OIDC 账号后重新登录报错:UNIQUE constraint failed: x_users.username (2067) #2872

Description

@UE-DND

请确认以下事项

  • 我已确认阅读并同意 AGPL-3.0 第15条
    本程序不提供任何明示或暗示的担保,使用风险由您自行承担。

  • 我已确认阅读并同意 AGPL-3.0 第16条
    无论何种情况,版权持有人或其他分发者均不对使用本程序所造成的任何损失承担责任。

  • 我确认我的描述清晰,语法礼貌,能帮助开发者快速定位问题,并符合社区规则。

  • 我已确认阅读了OpenList文档

  • 我已确认没有重复的问题或讨论。

  • 我已确认是OpenList的问题,而不是其他原因(例如 网络依赖操作)。

  • 我认为此问题必须由OpenList处理,而非第三方。

  • 我已确认这个问题在最新版本中没有被修复。

  • 我没有阅读这个清单,只是闭眼选中了所有的复选框,请关闭这个 Issue 。

OpenList 版本(必填)

v4.2.4

使用的存储驱动(必填)

中国移动云盘

问题描述(必填)

在使用 OIDC/SSO 登录/注册后,若在「个人资料」中解除单点登录绑定并保存,当再次使用同一个 SSO 账号登录时,服务端会抛出 400 错误:

{
  "code": 400,
  "message": "constraint failed: UNIQUE constraint failed: x_users.username (2067)",
  "data": null
}

Note

  1. 此 OIDC Provider 为个人自建,但根据 message 信息,问题似乎出在 OpenList 侧
  2. OpenList 运行在 SQLite 下

SSO 配置

Image

复现

  1. 配置并开启单点登录,开启 SSO 自动注册
  2. 使用 OIDC 账号登录系统
  3. 进入「个人资料」设置页面,点击「解绑单点登录平台」并保存
  4. 退出登录后,再次点击通过相同的 OIDC 账号登录
  5. OIDC 页面显示 400 错误:UNIQUE constraint failed: x_users.username (2067)

预期行为

if err = db.CreateUser(user); err != nil {
if strings.HasPrefix(err.Error(), "UNIQUE constraint failed") && strings.HasSuffix(err.Error(), "username") {
user.Username = user.Username + "_" + userID
if err = db.CreateUser(user); err != nil {
return nil, err
}
} else {
return nil, err
}
}

以上代码中的 user.Username = user.Username + "_" + userID 重试逻辑触发,添加 userID 后缀并继续尝试完成用户创建

个人分析

本人不熟悉 go 语言,以下分析仅供参考

SQLite 返回的错误字符串末尾带有扩展错误码 x_users.username (2067)strings.HasSuffix(err.Error(), "username" 为 false,未能进入重试。若 bug 确实存在,建议强化错误判定方式。

日志(必填)

[GIN] 2026/07/29 - 20:20:29 | 200 | 150.717µs | xxx.xxx.xxx.xxx | GET      "/api/auth/logout"
[GIN] 2026/07/29 - 20:20:31 | 302 |  35.31ms | xxx.xxx.xxx.xxx | GET      "/api/auth/sso?method=sso_get_token"
[GIN] 2026/07/29 - 20:20:53 | 200 |  58.8ms | xxx.xxx.xxx.xxx | GET      "/api/auth/sso_callback?method=sso_get_token&code=REDACTED&state=REDACTED&iss=https%3A%2F%2Foidc.example.com"
[GIN] 2026/07/29 - 20:20:53 | 200 | 358.399µs | xxx.xxx.xxx.xxx | GET      "/api/me"
[GIN] 2026/07/29 - 20:20:54 | 200 | 122.728µs | xxx.xxx.xxx.xxx | GET      "/api/admin/user/list"
[GIN] 2026/07/29 - 20:21:03 | 302 |  11.81ms | xxx.xxx.xxx.xxx | GET      "/api/auth/sso?method=get_sso_id"
[GIN] 2026/07/29 - 20:21:04 | 200 |  59.22ms | xxx.xxx.xxx.xxx | GET      "/api/auth/sso_callback?method=get_sso_id&code=REDACTED&state=REDACTED&iss=https%3A%2F%2Foidc.example.com"
[GIN] 2026/07/29 - 20:21:10 | 200 | 110.543µs | xxx.xxx.xxx.xxx | GET      "/api/auth/logout"
[GIN] 2026/07/29 - 20:21:12 | 302 |   5.53ms | xxx.xxx.xxx.xxx | GET      "/api/auth/sso?method=sso_get_token"
[GIN] 2026/07/29 - 20:21:13 | 200 |  60.71ms | xxx.xxx.xxx.xxx | GET      "/api/auth/sso_callback?method=sso_get_token&code=REDACTED&state=REDACTED&iss=https%3A%2F%2Foidc.example.com"
[GIN] 2026/07/29 - 20:21:14 | 200 | 117.894µs | xxx.xxx.xxx.xxx | GET      "/api/me"
[GIN] 2026/07/29 - 20:21:20 | 200 |  99.45µs | xxx.xxx.xxx.xxx | GET      "/api/auth/logout"
[GIN] 2026/07/29 - 20:21:21 | 200 | 127.01µs | xxx.xxx.xxx.xxx | POST     "/api/auth/login/hash"
[GIN] 2026/07/29 - 20:21:21 | 200 | 115.199µs | xxx.xxx.xxx.xxx | GET      "/api/me"
[GIN] 2026/07/29 - 20:21:23 | 200 | 431.081µs | xxx.xxx.xxx.xxx | GET      "/api/admin/user/list"
[GIN] 2026/07/29 - 20:26:02 | 200 | 135.962µs | xxx.xxx.xxx.xxx | GET      "/api/auth/logout"
[GIN] 2026/07/29 - 20:26:04 | 302 |  28.98ms | xxx.xxx.xxx.xxx | GET      "/api/auth/sso?method=sso_get_token"
[GIN] 2026/07/29 - 20:26:14 | 200 |  64.34ms | xxx.xxx.xxx.xxx | GET      "/api/auth/sso_callback?method=sso_get_token&code=REDACTED&state=REDACTED&iss=https%3A%2F%2Foidc.example.com"
[GIN] 2026/07/29 - 20:26:14 | 200 | 114.665µs | xxx.xxx.xxx.xxx | GET      "/api/me"
[GIN] 2026/07/29 - 20:26:37 | 200 | 80.728µs | xxx.xxx.xxx.xxx | GET      "/@manage"
[GIN] 2026/07/29 - 20:26:37 | 200 | 36.202µs | xxx.xxx.xxx.xxx | GET      "/api/public/archive_extensions"
[GIN] 2026/07/29 - 20:26:37 | 200 | 111.087µs | xxx.xxx.xxx.xxx | GET      "/api/public/settings"
[GIN] 2026/07/29 - 20:26:39 | 200 | 93.715µs | xxx.xxx.xxx.xxx | GET      "/api/me"
[GIN] 2026/07/29 - 20:26:48 | 200 |   4.09ms | xxx.xxx.xxx.xxx | POST     "/api/me/update"
[GIN] 2026/07/29 - 20:26:52 | 302 |   13.8ms | xxx.xxx.xxx.xxx | GET      "/api/auth/sso?method=sso_get_token"
[GIN] 2026/07/29 - 20:26:52 | 200 |  59.28ms | xxx.xxx.xxx.xxx | GET      "/api/auth/sso_callback?method=sso_get_token&code=REDACTED&state=REDACTED&iss=https%3A%2F%2Foidc.example.com"
[GIN] 2026/07/29 - 20:26:52 | 302 | 248.853µs | xxx.xxx.xxx.xxx | GET      "/favicon.ico"
[GIN] 2026/07/29 - 20:26:58 | 302 |   5.36ms | xxx.xxx.xxx.xxx | GET      "/api/auth/sso?method=sso_get_token"
[GIN] 2026/07/29 - 20:27:01 | 200 |  60.53ms | xxx.xxx.xxx.xxx | GET      "/api/auth/sso_callback?method=sso_get_token&code=REDACTED&state=REDACTED&iss=https%3A%2F%2Foidc.example.com"
[GIN] 2026/07/29 - 20:29:08 | 200 | 369.688µs | xxx.xxx.xxx.xxx | POST     "/api/auth/login/hash"
[GIN] 2026/07/29 - 20:29:08 | 200 | 112.515µs | xxx.xxx.xxx.xxx | GET      "/api/me"
[GIN] 2026/07/29 - 20:29:09 | 200 | 404.359µs | xxx.xxx.xxx.xxx | GET      "/api/admin/user/list"
[GIN] 2026/07/29 - 20:29:12 | 200 |   2.91ms | xxx.xxx.xxx.xxx | POST     "/api/admin/user/delete?id=3"
[GIN] 2026/07/29 - 20:29:13 | 200 | 416.35µs | xxx.xxx.xxx.xxx | GET      "/api/admin/user/list"
[GIN] 2026/07/29 - 20:29:15 | 200 | 106.419µs | xxx.xxx.xxx.xxx | GET      "/api/auth/logout"
[GIN] 2026/07/29 - 20:29:17 | 302 |  27.57ms | xxx.xxx.xxx.xxx | GET      "/api/auth/sso?method=sso_get_token"
[GIN] 2026/07/29 - 20:29:19 | 200 |  62.49ms | xxx.xxx.xxx.xxx | GET      "/api/auth/sso_callback?method=sso_get_token&code=REDACTED&state=REDACTED&iss=https%3A%2F%2Foidc.example.com"
[GIN] 2026/07/29 - 20:29:20 | 200 | 452.693µs | xxx.xxx.xxx.xxx | GET      "/api/me"
[GIN] 2026/07/29 - 20:29:20 | 200 | 124.584µs | xxx.xxx.xxx.xxx | GET      "/api/admin/user/list"
[GIN] 2026/07/29 - 20:29:25 | 200 | 101.022µs | xxx.xxx.xxx.xxx | GET      "/api/auth/logout"
[GIN] 2026/07/29 - 20:29:27 | 200 | 123.159µs | xxx.xxx.xxx.xxx | POST     "/api/auth/login/hash"
[GIN] 2026/07/29 - 20:29:28 | 200 | 95.771µs | xxx.xxx.xxx.xxx | GET      "/api/me"
[GIN] 2026/07/29 - 20:29:30 | 200 | 436.825µs | xxx.xxx.xxx.xxx | GET      "/api/admin/user/list"
[GIN] 2026/07/29 - 20:57:50 | 200 | 88.163µs | xxx.xxx.xxx.xxx | GET      "/"
INFO[2026-07-29 21:12:28] Start cache GC                               
[GIN] 2026/07/29 - 21:19:12 | 200 | 95.138µs | xxx.xxx.xxx.xxx | GET      "/"
[GIN] 2026/07/29 - 21:19:13 | 200 | 43.444µs | xxx.xxx.xxx.xxx | GET      "/api/public/archive_extensions"
[GIN] 2026/07/29 - 21:19:13 | 200 | 503.897µs | xxx.xxx.xxx.xxx | GET      "/api/public/settings"
[GIN] 2026/07/29 - 21:19:13 | 200 | 420.961µs | xxx.xxx.xxx.xxx | GET      "/api/me"
[GIN] 2026/07/29 - 21:19:13 | 200 | 261.469µs | xxx.xxx.xxx.xxx | GET      "/manifest.json"
[GIN] 2026/07/29 - 21:19:14 | 200 | 572.061µs | xxx.xxx.xxx.xxx | GET      "/api/public/offline_download_tools"
[GIN] 2026/07/29 - 21:19:16 | 200 |    2.01s | xxx.xxx.xxx.xxx | POST     "/api/fs/get"
[GIN] 2026/07/29 - 21:19:16 | 200 | 437.027µs | xxx.xxx.xxx.xxx | POST     "/api/fs/list"
[GIN] 2026/07/29 - 21:19:19 | 200 |    3.04s | xxx.xxx.xxx.xxx | GET      "/p/README.md?sign=REDACTED&openlist_ts=1785331156131"
[GIN] 2026/07/29 - 21:19:36 | 200 | 107.153µs | xxx.xxx.xxx.xxx | GET      "/api/me"
[GIN] 2026/07/29 - 21:19:38 | 200 | 414.933µs | xxx.xxx.xxx.xxx | GET      "/api/admin/setting/list?group=1"
[GIN] 2026/07/29 - 21:19:39 | 200 | 401.118µs | xxx.xxx.xxx.xxx | GET      "/api/admin/setting/list?group=2"
[GIN] 2026/07/29 - 21:19:41 | 200 | 133.167µs | xxx.xxx.xxx.xxx | GET      "/api/admin/setting/list?group=1"
[GIN] 2026/07/29 - 21:20:11 | 200 | 110.509µs | xxx.xxx.xxx.xxx | GET      "/api/admin/driver/names"
[GIN] 2026/07/29 - 21:20:11 | 200 | 756.693µs | xxx.xxx.xxx.xxx | GET      "/api/admin/storage/list"
[GIN] 2026/07/29 - 21:20:13 | 200 |   2.04ms | xxx.xxx.xxx.xxx | GET      "/api/admin/driver/list"
[GIN] 2026/07/29 - 21:20:29 | 200 | 178.972µs | xxx.xxx.xxx.xxx | GET      "/api/admin/driver/names"
[GIN] 2026/07/29 - 21:20:29 | 200 | 573.121µs | xxx.xxx.xxx.xxx | GET      "/api/admin/storage/list"
[GIN] 2026/07/29 - 21:20:54 | 200 | 105.747µs | xxx.xxx.xxx.xxx | GET      "/api/auth/logout"
[GIN] 2026/07/29 - 21:21:00 | 200 | 142.997µs | xxx.xxx.xxx.xxx | POST     "/api/auth/login/hash"
[GIN] 2026/07/29 - 21:21:01 | 200 | 115.768µs | xxx.xxx.xxx.xxx | GET      "/api/me"
[GIN] 2026/07/29 - 21:21:02 | 200 | 423.79µs | xxx.xxx.xxx.xxx | GET      "/api/admin/user/list"
[GIN] 2026/07/29 - 21:21:06 | 200 | 96.809µs | xxx.xxx.xxx.xxx | GET      "/api/auth/logout"
[GIN] 2026/07/29 - 21:21:08 | 302 |  28.91ms | xxx.xxx.xxx.xxx | GET      "/api/auth/sso?method=sso_get_token"
[GIN] 2026/07/29 - 21:21:12 | 200 |  60.31ms | xxx.xxx.xxx.xxx | GET      "/api/auth/sso_callback?method=sso_get_token&code=REDACTED&state=REDACTED&iss=https%3A%2F%2Foidc.example.com"
[GIN] 2026/07/29 - 21:21:12 | 200 | 107.428µs | xxx.xxx.xxx.xxx | GET      "/api/me"
[GIN] 2026/07/29 - 21:21:13 | 200 | 146.15µs | xxx.xxx.xxx.xxx | GET      "/api/admin/user/list"
[GIN] 2026/07/29 - 21:21:20 | 200 |   3.81ms | xxx.xxx.xxx.xxx | POST     "/api/me/update"
[GIN] 2026/07/29 - 21:21:23 | 302 |  12.29ms | xxx.xxx.xxx.xxx | GET      "/api/auth/sso?method=sso_get_token"
[GIN] 2026/07/29 - 21:21:24 | 200 |  59.72ms | xxx.xxx.xxx.xxx | GET      "/api/auth/sso_callback?method=sso_get_token&code=REDACTED&state=REDACTED&iss=https%3A%2F%2Foidc.example.com"
[GIN] 2026/07/29 - 21:26:14 | 200 | 134.153µs | xxx.xxx.xxx.xxx | POST     "/api/auth/login/hash"
[GIN] 2026/07/29 - 21:26:15 | 200 | 110.177µs | xxx.xxx.xxx.xxx | GET      "/api/me"
[GIN] 2026/07/29 - 21:26:19 | 200 | 82.903µs | xxx.xxx.xxx.xxx | GET      "/"
[GIN] 2026/07/29 - 21:26:20 | 200 | 118.788µs | xxx.xxx.xxx.xxx | GET      "/api/public/settings"
[GIN] 2026/07/29 - 21:26:20 | 200 | 37.569µs | xxx.xxx.xxx.xxx | GET      "/api/public/archive_extensions"
[GIN] 2026/07/29 - 21:26:20 | 200 | 127.724µs | xxx.xxx.xxx.xxx | GET      "/api/me"
[GIN] 2026/07/29 - 21:26:21 | 200 | 187.268µs | xxx.xxx.xxx.xxx | POST     "/api/fs/get"
[GIN] 2026/07/29 - 21:26:21 | 200 | 461.73µs | xxx.xxx.xxx.xxx | GET      "/api/public/offline_download_tools"
[GIN] 2026/07/29 - 21:26:22 | 200 | 208.756µs | xxx.xxx.xxx.xxx | POST     "/api/fs/list"
[GIN] 2026/07/29 - 21:26:34 | 200 |   11.59s | xxx.xxx.xxx.xxx | GET      "/p/README.md?sign=REDACTED&openlist_ts=1785331582433"

日志中的 OIDC 相关信息经过脱敏

配置文件内容(必填)

{
  "force": false,
  "site_url": "",
  "cdn": "",
  "jwt_secret": "REDACTED",
  "token_expires_in": 48,
  "database": {
    "type": "sqlite3",
    "host": "",
    "port": 0,
    "user": "",
    "password": "",
    "name": "",
    "db_file": "data/data.db",
    "table_prefix": "x_",
    "ssl_mode": "",
    "dsn": ""
  },
  "meilisearch": {
    "host": "http://localhost:7700",
    "api_key": "",
    "index": "openlist"
  },
  "scheme": {
    "address": "0.0.0.0",
    "http_port": 5244,
    "https_port": -1,
    "force_https": false,
    "cert_file": "",
    "key_file": "",
    "unix_file": "",
    "unix_file_perm": "",
    "enable_h2c": false,
    "enable_h3": false
  },
  "temp_dir": "data/temp",
  "bleve_dir": "data/bleve",
  "dist_dir": "",
  "log": {
    "enable": true,
    "name": "data/log/log.log",
    "max_size": 50,
    "max_backups": 30,
    "max_age": 28,
    "compress": false,
    "filter": {
      "enable": false,
      "filters": [
        {
          "cidr": "",
          "path": "/ping",
          "method": ""
        },
        {
          "cidr": "",
          "path": "",
          "method": "HEAD"
        },
        {
          "cidr": "",
          "path": "/dav/",
          "method": "PROPFIND"
        }
      ]
    }
  },
  "delayed_start": 0,
  "auto_memory_limit": 4,
  "min_free_memory": 0,
  "max_block_limit": 0,
  "max_connections": 0,
  "max_concurrency": 64,
  "tls_insecure_skip_verify": false,
  "tasks": {
    "download": {
      "workers": 5,
      "max_retry": 1,
      "task_persistant": false
    },
    "transfer": {
      "workers": 5,
      "max_retry": 2,
      "task_persistant": false
    },
    "upload": {
      "workers": 5,
      "max_retry": 0,
      "task_persistant": false
    },
    "copy": {
      "workers": 5,
      "max_retry": 2,
      "task_persistant": false
    },
    "move": {
      "workers": 5,
      "max_retry": 2,
      "task_persistant": false
    },
    "decompress": {
      "workers": 5,
      "max_retry": 2,
      "task_persistant": false
    },
    "decompress_upload": {
      "workers": 5,
      "max_retry": 2,
      "task_persistant": false
    },
    "allow_retry_canceled": false
  },
  "cors": {
    "allow_origins": [
      "*"
    ],
    "allow_methods": [
      "*"
    ],
    "allow_headers": [
      "*"
    ]
  },
  "s3": {
    "enable": false,
    "port": 5246,
    "ssl": false
  },
  "ftp": {
    "enable": false,
    "listen": ":5221",
    "find_pasv_port_attempts": 50,
    "active_transfer_port_non_20": false,
    "idle_timeout": 900,
    "connection_timeout": 30,
    "disable_active_mode": false,
    "default_transfer_binary": false,
    "enable_active_conn_ip_check": true,
    "enable_pasv_conn_ip_check": true
  },
  "sftp": {
    "enable": false,
    "listen": ":5222"
  },
  "mcp": {
    "enable": false
  },
  "last_launched_version": "v4.2.4",
  "proxy_address": ""
}

复现链接(可选)

No response

AI生成内容(可选)

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions