gloox代碼分析3 - 註冊模塊

jabber協議中如何註冊一個用戶?
首先要與服務器建立一個連接, 在完成TLS握手之後就可以進行註冊了,爲什麼不需要SASL握手呢?因爲SASL握手只針對已經註冊的用戶在登陸服務器的時候使用.(修改密碼和刪除用戶的時候需要SASL握手)
下面以openfire作爲服務器,註冊一個用戶的過程如下:
(假設已經完成了TLS握手)
1. ( C->S )
<stream:stream
    to='ziz-wrks-tfsxp1'
    xmlns='jabber:client'
    xmlns:stream='http://etherx.jabber.org/streams'
    xml:lang='en'
    version='1.0'>

TLS握手結束後, 發送新的'hello'消息

2. ( S->C )
<stream:stream
    xmlns:stream='http://etherx.jabber.org/streams'
    xmlns='jabber:client'
    from='ziz-wrks-tfsxp1'
    id='b691538a'
    xml:lang='en' version='1.0'/>

Server迴應Client的hello消息.

3. ( S->C )
<stream:features>
<mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>
    <mechanism>DIGEST-MD5</mechanism>
    <mechanism>PLAIN</mechanism>
    <mechanism>ANONYMOUS</mechanism>
    <mechanism>CRAM-MD5</mechanism>
</mechanisms>
<compression xmlns='http://jabber.org/features/compress'>
    <method>zlib</method>
</compression>
<auth xmlns='http://jabber.org/features/iq-auth'/>
<register xmlns='http://jabber.org/features/iq-register'/>
</stream:features>

服務器支持的features(可以register)

4. ( C->S )
<iq type='get' id='uid1'>
<query xmlns='jabber:iq:register'/>
</iq>

客戶端請求註冊
5. ( S->C )
<iq type='result' id='uid1'>
<query xmlns='jabber:iq:register'>
    <username/>
    <password/>
    <email/>
    <name/>
    <x xmlns='jabber:x:data' type='form'>
      <title>XMPP Client Registration</title>
      <instructions>Please provide the following information</instructions>
      <field var='FORM_TYPE' type='hidden'>
        <value>jabber:iq:register</value>
      </field>
      <field label='Username' var='username' type='text-single'>
        <required/>
      </field>
      <field label='Full name' var='name' type='text-single'/>
      <field label='Email' var='email' type='text-single'/>
      <field label='Password' var='password' type='text-private'>
        <required/>
      </field>
    </x>
</query>
</iq>

服務器返回註冊需要的fields
(其中元素x裏面的數據是擴展性的數據表但,暫時忽略)

6. ( C->S )
<iq id='uid2' type='set'>
<query xmlns='jabber:iq:register'>
    <username>user3</username>
    <password>user@123</password>
    <name/>
    <email/>
</query>
</iq>

用戶提交註冊信息.

7. ( S->C )
<iq type='result' id='uid2' to='ziz-wrks-tfsxp1/b691538a'/>

服務器返回註冊結果

#如何更改用戶密碼
更改用戶密碼必須在完成與服務器的鏈接之後才能進行,也就是完成與服務器的TLS,SASl握手之後
再進行.

1. ( C->S )
<iq type='set' id='uid3'>
<query xmlns='jabber:iq:register'>
    <username>user23</username>
    <password>123456</password>
</query>
</iq>

發送更改密碼的請求

2. ( S->C )
<iq type='result' id='uid3' to='user23@ziz-wrks-tfsxp1/2f21fd1f'/>

返回處理結果(操作成功)

#如何註銷一個用戶
註銷一個用戶必須在完成與服務器的鏈接之後才能進行,也就是完成與服務器的TLS,SASl握手之後
再進行.

1. ( C->S )
<iq type='set' id='uid3' from='testuser@ziz-wrks-tfsxp1/3a418274'>
   <query xmlns='jabber:iq:register'><remove/></query>
</iq>

發送註銷用戶的請求

2. ( S->S )

<iq type='result' id='uid3' to='testuser@ziz-wrks-tfsxp1/3a418274'/>

返回處理結果(註銷成功)

總結:
註冊用戶和更改或者註銷用戶的一個區別就是註冊用戶不需要經過SASL握手,而更改和註銷用戶需要.
也就是需要對用戶身份進行驗證.

gloox的註冊模塊
registration.h
registration.cpp
registrationhandler.h
就是對上述協議的封裝,方便創建register stanza,並把收到的消息分發到相應的handler方法.

gloox的client是如何處理xml流的呢?
1. 連接到服務器後,使客戶端進入"receive mode",可以循環的接收數據.
2. 數據流 [接收到的數據 handleReceiveData() ] -> [解析xml數據流,解析成不同的tag, parser()] -> [ 觸發handleTag(), 針對不同的tag分發到不同的handlers做不同的處理 ]
- stream:stream - ...
- stream:error - ...
- Iq           - IqHandler
- Presence - presenceHandler
- Message - messageHandler

Iq消息的處理機制,以及trackID機制:
實質上registration是一個Iqhandler, 它實現的是handleIqID( Stanza *stanza, int context )接口,而不是handleIq( Stanza *stanza )接口,其中context實質上爲iqHandler具體實現中的子操作類型,在registration中分別爲 fetchRegisterFiled, createAccount, changePassword, removeAccount.

對一個Iq Stanza的處理方式可以通過一般的IqHandler - xmlnamespace filer或者IqHandler - TrackID機制來處理.

下面代碼展示瞭如何註冊/修改密碼/刪除用戶(服務器是openfire):
1#include <iostream>
2#include "registration.h" // gloox headers
3#include "client.h"
4#include "connectionlistener.h"
5#include "logsink.h"
6#include "loghandler.h"
7
8#pragma comment( lib, "gloox.lib" )
9using namespace gloox;
10
11#define SERVERNAME   "xxxxxx"
12#define HEADERTO     "xxxxxx"
13
14#define USERNAME     "user123"
15#define PASSWORD     "user@123"
16#define NEWPASSWORD "123456"
17
18//
19// three scenario3
20// 1 - register user (Note: don't need SASL handshake)
21// 2 - change password (Note: need SASL handshake)
22// 3 - delete user (Note: need SASL handshake)
23//
24class RegisterImpl : public RegistrationHandler, ConnectionListener, LogHandler {
25public:
26 void run();
27
28 //implement RegistrationHandler
29 void handleRegistrationFields( const JID& from, int fields, std::string instructions );
30 void handleAlreadyRegistered( const JID& from );
31 void handleRegistrationResult( const JID& from, RegistrationResult regResult );
32 void handleDataForm( const JID& from, const DataForm &form );
33 void handleOOB( const JID& from, const OOB& oob );
34
35 //implement ConnectionListener
36 void onConnect();
37 void onDisconnect( ConnectionError e );
38 void onResourceBindError( ResourceBindError error );
39 void onSessionCreateError( SessionCreateError error );
40 bool onTLSConnect( const CertInfo& info );
41 void onStreamEvent( StreamEvent event );
42
43 //implement LogHandler
44 void handleLog( LogLevel level, LogArea area, const std::string& message );
45
46private:
47 Registration* register_;
48 Client* client_;
49};
50
51void RegisterImpl::run() {
52 client_ = new Client(SERVERNAME);
53 client_->setHeaderTo(HEADERTO);
54 client_->registerConnectionListener(this);
55 register_ = new Registration( client_ );
56 register_->registerRegistrationHandler( this );
57 client_->logInstance().registerLogHandler( LogLevelDebug, LogAreaAll, this );
58
59 // run scenario2 and scenario3 need enable username and
60 // password to run SASL, scenario1 did not need.
61 client_->setUsername(USERNAME);
62 client_->setPassword(PASSWORD);
63
64 client_->connect();
65
66 delete register_;
67 delete client_;
68}
69
70void RegisterImpl::handleRegistrationFields( const JID& from, int fields, std::string instructions ) {
71 // register account
72 std::cout<<"impl# register instruction: "<<instructions<<std::endl;
73 RegistrationFields vals;
74 vals.username = USERNAME;
75 vals.password = PASSWORD;
76 register_->createAccount( fields, vals );
77}
78
79void RegisterImpl::handleAlreadyRegistered( const JID& from ) {
80 std::cout<<"impl# the count already exists."<<std::endl;
81}
82
83void RegisterImpl::handleRegistrationResult( const JID& from, RegistrationResult regResult ) {
84 if( regResult == RegistrationSuccess ) {
85    std::cout<<"impl# operation success."<<std::endl;
86 } else {
87    std::cout<<"impl# operation failed."<<std::endl;
88 }
89
90 client_->disconnect();
91}
92
93void RegisterImpl::handleDataForm( const JID& from, const DataForm &form ) {
94 //TODO:
95}
96
97void RegisterImpl::handleOOB( const JID& from, const OOB& oob ) {
98 //TODO:
99}
100
101void RegisterImpl::onConnect() {
102 std::cout<<"impl# connect to server success."<<std::endl;
103 //operation
104
105 // scenario1 - register
106 // this will invoke handleRegistrationResult() to createAccount
107 // Note: doesn't need SASL handshake
108 register_->fetchRegistrationFields();
109
110 // scenario2 - change password
111 // Note: need SASL handshake, in gloox, set the username & password in clientbase, will
112 //       run the SASL handshake.
113 // register_->changePassword( client_->username(), NEWPASSWORD );
114
115 // scenario3 - delete account
116 // Note: need SASL handshake,
117 // register_->removeAccount();
118}
119
120void RegisterImpl::onDisconnect( ConnectionError e ) {
121 std::cout<<"impl# disconnected."<<std::endl;
122}
123
124void RegisterImpl::onResourceBindError( ResourceBindError error ) {
125 //TODO:
126}
127
128void RegisterImpl::onSessionCreateError( SessionCreateError error ) {
129 //TODO:
130}
131
132bool RegisterImpl::onTLSConnect( const CertInfo& info ) {
133 std::cout<<"impl# tls connect to server success."<<std::endl;
134 return true;
135}
136
137void RegisterImpl::onStreamEvent( StreamEvent event ) {
138 //TODO:
139}
140
141void RegisterImpl::handleLog( LogLevel level, LogArea area, const std::string& message ) {
142 std::cout<<"impl-log# "<<message<<std::endl;
143}
144
145int main( int argc, char* argv[] ) {
146 RegisterImpl impl;
147 impl.run();
148 return 0;
149}

posted on 2008-11-06 12:59 ysong.lee 閱讀(1106) 評論(3) 編輯 收藏 引用
Feedback
# re: gloox代碼分析3 - 註冊模塊 2008-11-14 19:09 ly

hao 回覆 更多評論
# re: gloox代碼分析3 - 註冊模塊 2008-11-14 19:13 ly

client_->setHeaderTo(HEADERTO);
錯誤提示在client 裏找不到setHeaderTo?????什麼問題?
我把你這句去掉後,改成
client_->disableRoster();
發現能註冊成功,但是,登陸提示:sasl failed。
登陸不成功!~~~什麼原因~~~望解答!~~ 回覆 更多評論
# re: gloox代碼分析3 - 註冊模塊 2008-11-18 13:01 ysong.lee

1. 我對gloox進行了修改,client_->setHeaderTo(HEADERTO); 是自己添加的方法,目的是爲了fix gloox不能連接到gtalk server的問題,通過修改,可以自己控制ClientBase::header()方法中在發送hello信息時候的to屬性,這在服務器是 gtalk server的時候是有用的,如果我們的服務器地址設置爲talk.google.com,則header()方法默認to= "talk.google.com",這是錯誤的,應該爲to="gmail.com".

2. sasl failed。登陸的時候需要sasl 驗證,如果服務器爲gtalk server, 當你接收到服務器發送的以下信息的時候
(Server -> Client)
<stream:features>
<mechanisms xmlns="urn:ietf:params:xml:ns:xmpp-sasl">
<mechanism>PLAIN</mechanism>
<mechanism>X-GOOGLE-TOKEN</mechanism>
</mechanisms>
</stream:features>

需要進行sasl驗證,發送的驗證數據格式爲:
'/0'+username+'/0'+password 的base64編碼, username和password必須是經過認證的.
例如:
'/0' + '[email protected]' + '/0' + 123456 -> 經過base64編碼處理
(Client -> Server)
<auth xmlns="urn:ietf:params:xml:ns:xmpp-sasl" mechanism="PLAIN">
AHlzb25nLmxlZUBnbWFpbC5jb20AeXNvbmdAMTk4NA==
</auth>


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章