Twisted认证凭据问题

2 投票
2 回答
1758 浏览
提问于 2025-04-16 21:56

我在pb和cred上遇到了错误...

我们有一个简单的客户端:

#!/usr/bin/env python

from twisted.spread import pb
from twisted.internet import reactor
from twisted.cred import credentials

def main():
    factory = pb.PBClientFactory()
    reactor.connectTCP("localhost", 8801, factory)
    def1 = factory.login(credentials.UsernamePassword("admin", "pass2"))
    def1.addCallback(connected)
    def1.addErrback(bad_connected)
    def1.addBoth(disconnect)
    reactor.run()

def bad_connected(perspective):
    print 'bad login or password', perspective
    perspective.addCallback(disconnect)

def connected(perspective):
    print "got perspective1 ref:", perspective
    print "asking it to foo(13)"

    return perspective.callRemote("foo", 13)

def disconnect(perspective):
    print 'disconnect'
    reactor.stop()

main()

如果我们连接上了,就会调用 perspective.callRemote("foo", 13) 然后断开连接。

如果没有连接上,就会打印 '错误的登录或密码' 然后断开连接。

服务器代码:

#!/usr/bin/env python

from zope.interface import implements
from twisted.python import failure, log
from twisted.cred import portal, checkers, credentials, error as credError
from twisted.internet import defer, reactor
from twisted.spread import pb


class PasswordDictChecker:
    implements(checkers.ICredentialsChecker)
    credentialInterfaces = (credentials.IUsernamePassword,)

    def __init__(self, passwords):
        "passwords: a dict-like object mapping usernames to passwords"
        self.passwords = passwords

    def requestAvatarId(self, credentials):
        username = credentials.username
        if self.passwords.has_key(username):
            if credentials.password == self.passwords[username]:
                return defer.succeed(username)
            else:
                return defer.fail(
                    credError.UnauthorizedLogin("Bad password"))
        else:
            return defer.fail(
                credError.UnauthorizedLogin("No such user"))

class MyRealm(object):
    implements(portal.IRealm)

    def requestAvatar(self, user, mind, *interfaces):
        assert pb.IPerspective in interfaces
        avatar = MyAvatar(user)
        avatar.attached(mind)
        return pb.IPerspective, avatar, lambda a=avatar:a.detached(mind)

class MyAvatar(pb.Avatar):
    def __init__(self, user):
        self.user = user

    def attached(self, mind):
        self.remote = mind
        print 'User %s connected' % (self.user,)

    def detached(self, mind):
        self.remote = None
        print 'User %s disconnected' % (self.user,)

passwords = {
    'admin': 'aaa',
    'user1': 'bbb',
    'user2': 'ccc'
    }

if __name__ == "__main__":
    checker = PasswordDictChecker(passwords)
    realm = MyRealm()
    p = portal.Portal(realm, [checker])

    reactor.listenTCP(8801, pb.PBServerFactory(p))
    reactor.run()

问题是,这段代码执行时显示了一个错误:

Unhandled Error
Traceback (most recent call last):
Failure: twisted.cred.error.UnhandledCredentials: No checker for twisted.cred.credentials.IUsernameHashedPassword, twisted.spread.pb.IUsernameMD5Password, twisted.spread.interfaces.IJellyable

为什么我需要 IUsernameHashedPassword?

如果我改成:

 credentialInterfaces = (credentials.IUsernamePassword, redentials.IUsernameHashedPassword)

代码是可以执行的,但在这一行死掉了:

if credentials.password == self.passwords[username]:

Unhandled Error
Traceback (most recent call last):
  File "C:\Python27\lib\site-packages\twisted\spread\pb.py", line 841, in _recvMessage
    netResult = object.remoteMessageReceived(self, message, netArgs, netKw)
  File "C:\Python27\lib\site-packages\twisted\spread\flavors.py", line 114, in remoteMessageReceived
    state = method(*args, **kw)
  File "C:\Python27\lib\site-packages\twisted\spread\pb.py", line 1347, in remote_respond
    d = self.portal.login(self, mind, IPerspective)
  File "C:\Python27\lib\site-packages\twisted\cred\portal.py", line 115, in login
    return maybeDeferred(self.checkers[i].requestAvatarId, credentials
--- <exception caught here> ---
  File "C:\Python27\lib\site-packages\twisted\internet\defer.py", line 133, in maybeDeferred
    result = f(*args, **kw)
  File "C:/Dropbox/my_py/network/pb-cred/pb6serverV2.py", line 21, in requestAvatarId
    if credentials.password == self.passwords[username]:
exceptions.AttributeError: _PortalAuthChallenger instance has no attribute 'password'

请帮我理解这个问题。

2 个回答

0

我的简单测试服务器使用了PB和MySQL。

# -*- coding: utf-8 -*-
import MySQLdb
from twisted.cred import portal, checkers, credentials, error as credError
from twisted.protocols import basic
from twisted.internet import protocol, reactor, defer
from zope.interface import Interface, implements
from twisted.spread import pb
from twisted.enterprise import adbapi, util as dbutil


class MyPerspective(pb.Avatar):
    def __init__(self, name):
        self.name = name
    def perspective_foo(self, arg):
        print "I am", self.name, "perspective_foo(",arg,") called on", self
        return arg

class MyRealm:
    implements(portal.IRealm)
    def requestAvatar(self, avatarId, mind, *interfaces):
        #print 'qqqq', avatarId
        if pb.IPerspective not in interfaces:
            raise NotImplementedError
        return pb.IPerspective, MyPerspective(avatarId), lambda:None


class DbPasswordChecker(object):
    implements(checkers.ICredentialsChecker)
    credentialInterfaces = (credentials.IUsernamePassword,
                            credentials.IUsernameHashedPassword)
    def __init__(self, dbconn):
        self.dbconn = dbconn
    def requestAvatarId(self, credentials):
        query = "select userid, password from user where username = %s" % (
            dbutil.quote(credentials.username, "char"))
        return self.dbconn.runQuery(query).addCallback(
            self._gotQueryResults, credentials)
    def _gotQueryResults(self, rows, userCredentials):
        if rows:
            userid, password = rows[0]
            return defer.maybeDeferred(
                userCredentials.checkPassword, password).addCallback(
                self._checkedPassword, userid)
        else:
            raise credError.UnauthorizedLogin, "No such user"
    def _checkedPassword(self, matched, userid):
        if matched:
            return userid
        else:
            raise credError.UnauthorizedLogin("Bad password")

DB_DRIVER = "MySQLdb"
DB_ARGS = {
    'db': 'dbname',
    'user': 'root',
    'passwd': '',
    }

connection = adbapi.ConnectionPool(DB_DRIVER, **DB_ARGS)
p = portal.Portal(MyRealm())
p.registerChecker(DbPasswordChecker(connection))
#p.registerChecker(PasswordDictChecker(passwords))
reactor.listenTCP(8800, pb.PBServerFactory(p))
reactor.run()
2

你需要使用一个叫做 twisted.spread.pb.IUsernameMD5Password 的凭证对象来登录,因为Twisted的PB在认证时使用了一种小挑战/响应机制,这个机制要求密码要经过哈希处理(MD5)。这个算法现在是写死在PB模块里的。除非你打算自己实现一个认证子协议,否则你不能轻易地用其他的凭证容器与PB一起使用。

这个协议的目的是为了保护客户端和服务器免受中间人攻击。

我建议你看看 InMemoryUsernamePasswordDatabaseDontUse 的源代码,了解一下检查器是如何检查传给它们的凭证的(这个类的名字其实是在暗示你不要在生产服务器上使用这个类...)

撰写回答