You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

AsyncSSH基础SFTP服务器权限错误:连接失败

解决AsyncSSH SFTP服务器权限认证与自定义根路径问题

问题描述

尝试用Python的AsyncSSH库实现单元测试用的基础SFTP服务器,需求是自定义根路径并设置用户名/密码验证,但运行代码时出现权限错误。

原代码:

import asyncio
import asyncssh
import os
import logging

logging.basicConfig(level=logging.DEBUG)

class BasicSFTPServer(asyncssh.SFTPServer):
    def __init__(self):
        super().__init__(chan=22, chroot=os.path.basename(__file__))

async def main():
    host_key = asyncssh.generate_private_key("ssh-rsa")
    server = await asyncssh.listen(
        host="127.0.0.1",
        port=0,
        server_host_keys=[host_key],
        sftp_factory=BasicSFTPServer
    )
    server_port = server.get_port()

    async with asyncssh.connect(host="localhost", port=server_port, known_hosts=None) as conn:
        async with conn.start_sftp_client() as sftp:
            result = await sftp.listdir()
            print(result)

    server.close()
    await server.wait_closed()

if(__name__ == "__main__"):
    asyncio.run(main())

运行时错误信息:

DEBUG:asyncio:Using proactor: IocpProactor
INFO:asyncssh:Creating SSH listener on 127.0.0.1
INFO:asyncssh:Host canonicalization disabled
INFO:asyncssh:Opening SSH connection to localhost, port 53559
INFO:asyncssh:[conn=0] Connected to SSH server at localhost, port 53559
INFO:asyncssh:[conn=0]   Local address: 127.0.0.1, port 53561
INFO:asyncssh:[conn=0]   Peer address: 127.0.0.1, port 53559
DEBUG:asyncssh:[conn=0] Sending version SSH-2.0-AsyncSSH_2.21.0
INFO:asyncssh:[conn=1] Accepted SSH client connection
INFO:asyncssh:[conn=1]   Local address: 127.0.0.1, port 53559
INFO:asyncssh:[conn=1]   Peer address: 127.0.0.1, port 53561
DEBUG:asyncssh:[conn=1] Sending version SSH-2.0-AsyncSSH_2.21.0
DEBUG:asyncssh:[conn=1] Received version SSH-2.0-AsyncSSH_2.21.0
DEBUG:asyncssh:[conn=1] Requesting key exchange
DEBUG:asyncssh:[conn=0] Received version SSH-2.0-AsyncSSH_2.21.0
DEBUG:asyncssh:[conn=0] Requesting key exchange
DEBUG:asyncssh:[conn=0] Received key exchange request
DEBUG:asyncssh:[conn=0] Beginning key exchange
DEBUG:asyncssh:[conn=1] Received key exchange request
DEBUG:asyncssh:[conn=1] Beginning key exchange
DEBUG:asyncssh:[conn=0] Completed key exchange
DEBUG:asyncssh:[conn=1] Completed key exchange
INFO:asyncssh:[conn=0] Beginning auth for user Manuel
INFO:asyncssh:[conn=1] Beginning auth for user Manuel
INFO:asyncssh:[conn=0] Auth failed for user Manuel
INFO:asyncssh:[conn=0] Connection failure: Permission denied for user Manuel on host localhost
INFO:asyncssh:[conn=0] Aborting connection
Traceback (most recent call last):
  File "...\Documents\workspace\repos\template-python-service\debug\main.py", line 34, in <module>
    asyncio.run(main())
    ~~~~~~~~~~~^^^^^^^^
  File "...\AppData\Roaming\uv\python\cpython-3.13.1-windows-x86_64-none\Lib\asyncio\runners.py", line 194, in run
    return runner.run(main)
           ~~~~~~~~~~^^^^^^
  File "...\AppData\Roaming\uv\python\cpython-3.13.1-windows-x86_64-none\Lib\asyncio\runners.py", line 118, in run
    return self._loop.run_until_complete(task)
           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^
  File "...\AppData\Roaming\uv\python\cpython-3.13.1-windows-x86_64-none\Lib\asyncio\base_events.py", line 720, in run_until_complete
    return future.result()
           ~~~~~~~~~~~~~^^
  File "...\Documents\workspace\repos\template-python-service\debug\main.py", line 25, in main
    async with asyncssh.connect(host="localhost", port=server_port, known_hosts=None) as conn:
               ~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "...\Documents\workspace\repos\template-python-service\.venv\Lib\site-packages\asyncssh\misc.py", line 386, in __aenter__
    self._coro_result = await self._coro
                        ^^^^^^^^^^^^^^^^
  File "...\Documents\workspace\repos\template-python-service\.venv\Lib\site-packages\asyncssh\connection.py", line 9186, in connect
    return await asyncio.wait_for(
           ^^^^^^^^^^^^^^^^^^^^^^^
    ...<2 lines>...
        timeout=new_options.connect_timeout)
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "...\AppData\Roaming\uv\python\cpython-3.13.1-windows-x86_64-none\Lib\asyncio\tasks.py", line 507, in wait_for
    return await fut
           ^^^^^^^^^
  File "...\.venv\Lib\site-packages\asyncssh\connection.py", line 528, in _connect
    await options.waiter
asyncssh.misc.PermissionDenied: Permission denied for user xxxx on host localhost

错误原因

  1. 缺少用户认证配置:AsyncSSH默认没有启用任何用户认证方式,导致客户端连接时被拒绝。
  2. SFTPServer初始化参数错误:__init__方法中硬编码chan=22不符合AsyncSSH的参数要求,且chroot路径使用os.path.basename(__file__)会指向当前脚本文件名而非目录,无法作为SFTP根路径。

修复后的完整代码

import asyncio
import asyncssh
import os
import logging

logging.basicConfig(level=logging.DEBUG)

# 自定义用户密码认证回调
def password_auth_handler(username, password):
    # 这里设置测试用的用户名和密码
    allowed_users = {"test_user": "test_pass"}
    return allowed_users.get(username) == password

class BasicSFTPServer(asyncssh.SFTPServer):
    def __init__(self, chan, ctx):
        # 设置自定义根路径,这里用当前脚本所在目录作为示例
        root_path = os.path.dirname(os.path.abspath(__file__))
        super().__init__(chan, ctx, chroot=root_path)

async def main():
    host_key = asyncssh.generate_private_key("ssh-rsa")
    server = await asyncssh.listen(
        host="127.0.0.1",
        port=0,
        server_host_keys=[host_key],
        sftp_factory=BasicSFTPServer,
        # 启用密码认证并指定回调函数
        password_auth_handler=password_auth_handler
    )
    server_port = server.get_port()

    # 客户端连接时指定用户名和密码
    async with asyncssh.connect(
        host="localhost",
        port=server_port,
        known_hosts=None,
        username="test_user",
        password="test_pass"
    ) as conn:
        async with conn.start_sftp_client() as sftp:
            result = await sftp.listdir()
            print("SFTP根目录内容:", result)

    server.close()
    await server.wait_closed()

if __name__ == "__main__":
    asyncio.run(main())

代码说明

  • 用户认证配置:添加password_auth_handler回调函数,定义允许登录的用户名和密码,并在asyncssh.listen中指定该回调启用密码认证。
  • 修复SFTPServer初始化:__init__方法接收chan和ctx参数(AsyncSSH会自动传入),使用os.path.dirname(os.path.abspath(__file__))获取当前脚本所在目录作为SFTP根路径,也可以替换为任意你需要的测试目录。
  • 客户端连接调整:在asyncssh.connect中添加username和password参数,使用配置好的测试账号登录。

内容的提问来源于stack exchange,提问作者Manuel Díaz Pozo

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.12 14:15:53