本章分析用户的登录。
用户首先发送一个关于sasl验证的请求,
<auth mechanism="DIGEST-MD5" xmlns="urn:ietf:params:xml:ns:xmpp-sasl"></auth>
根据前面几章的分析,用户的这条XMPP消息到达服务器后会经过层层处理,最后到达ClientStanzaHandler的process函数,
public void process(String stanza, XMPPPacketReader reader) throws Exception {
if (!sessionCreated || initialStream) {
...
}
Element doc = reader.read(new StringReader(stanza)).getRootElement();
String tag = doc.getName();
if ("starttls".equals(tag)) {
...
}
else if ("auth".equals(tag)) {
startedSASL = true;
saslStatus = SASLAuthentication.handle(session, doc);
} else if (startedSASL && "response".equals(tag) || "abort".equals(tag)) {
...
}
else if ("compress".equals(tag)) {
...
}
else {
process(doc);
}
}
这里标签名为tag,因此调用SASLAuthentication的handle函数进一步处理,
public static Status handle(LocalSession session, Element doc) throws UnsupportedEncodingException {
Status status;
String mechanism;
if (doc.getNamespace().asXML().equals(SASL_NAMESPACE)) {
ElementType type = ElementType.valueof(doc.getName());
switch (type) {
case ABORT:
...
case AUTH:
mechanism = doc.attributeValue("mechanism");
session.setSessionData("SaslMechanism", mechanism);
if (mechanism.equalsIgnoreCase("ANONYMOUS") &&
mechanisms.contains("ANONYMOUS")) {
...
}
else if (mechanism.equalsIgnoreCase("EXTERNAL")) {
...
}
else if (mechanisms.contains(mechanism)) {
try {
Map<String, String> props = new TreeMap<String, String>();
props.put(Sasl.QOP, "auth");
SaslServer ss = Sasl.createSaslServer(mechanism, "xmpp",
JiveGlobals.getProperty("xmpp.fqdn", session.getServerName()), props, new XMPPCallbackHandler());
byte[] token = new byte[0];
String value = doc.getTextTrim();
if (value.length() > 0) {
...
}
if (mechanism.equals("DIGEST-MD5")) {
token = new byte[0];
}
byte[] challenge = ss.evaluateResponse(token);
if (ss.isComplete()) {
...
}
else {
sendChallenge(session, challenge);
status = Status.needResponse;
}
session.setSessionData("SaslServer", ss);
}
catch (SaslException e) {
}
}
else {
}
break;
case RESPONSE:
...
default:
...
}
}
else {
}
return status;
}
首先调用createSaslServer为客户端请求的 SASL 机制获得一个 SaslServer 实例,其中注册了用来验证用户名和密码的事件处理器XMPPCallbackHandler,最后面分析。然后就调用evaluateResponse获得challenge,然后通过sendChallenge把这个challenge发往客户端,并将刚刚构造的SaslServer设置进Session中。
sendChallenge定义在SASLAuthentication中,
private static void sendChallenge(Session session, byte[] challenge) {
StringBuilder reply = new StringBuilder(250);
String challenge_b64 = StringUtils.encodeBase64(challenge).trim();
if ("".equals(challenge_b64)) {
challenge_b64 = "=";
}
reply.append(
"<challenge xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\">");
reply.append(challenge_b64);
reply.append("</challenge>");
session.deliverRawText(reply.toString());
}
根据该函数,服务器返回给客户端的XMPP消息如下,
<response xmlns="urn:ietf:params:xml:ns:xmpp-sasl">
#challenge
</response>
这里的#challenge代表一串通过编码后的字符串。
客户端收到刚刚服务器发来的challenge后,会根据用户名和密码组织消息,然后向服务器发送第二个消息,
<response xmlns="urn:ietf:params:xml:ns:xmpp-sasl">
#challenge
</response>
该消息中的#challenge包含了用户名和密码等信息,到达服务器后,会到达ClientStanzaHandler的process函数,
public void process(String stanza, XMPPPacketReader reader) throws Exception {
if (!sessionCreated || initialStream) {
...
}
Element doc = reader.read(new StringReader(stanza)).getRootElement();
String tag = doc.getName();
if ("starttls".equals(tag)) {
...
}
else if ("auth".equals(tag)) {
...
} else if (startedSASL && "response".equals(tag) || "abort".equals(tag)) {
saslStatus = SASLAuthentication.handle(session, doc);
}
else if ("compress".equals(tag)) {
...
}
else {
process(doc);
}
}
和第一个请求一样,这里也是调用SASLAuthentication的handle函数进一步处理,
public static Status handle(LocalSession session, Element doc) throws UnsupportedEncodingException {
Status status;
String mechanism;
if (doc.getNamespace().asXML().equals(SASL_NAMESPACE)) {
ElementType type = ElementType.valueof(doc.getName());
switch (type) {
case ABORT:
...
case AUTH:
...
case RESPONSE:
mechanism = (String) session.getSessionData("SaslMechanism");
if (mechanism.equalsIgnoreCase("EXTERNAL")) {
...
}
else if (mechanism.equalsIgnoreCase("JIVE-SHAREDSECRET")) {
...
}
else if (mechanisms.contains(mechanism)) {
SaslServer ss = (SaslServer) session.getSessionData("SaslServer");
if (ss != null) {
boolean ssComplete = ss.isComplete();
String response = doc.getTextTrim();
if (!BASE64_ENCODED.matcher(response).matches()) {
...
}
try {
if (ssComplete) {
...
}
else {
byte[] data = StringUtils.decodeBase64(response);
byte[] challenge = ss.evaluateResponse(data);
if (ss.isComplete()) {
authenticationSuccessful(session, ss.getAuthorizationID(),
challenge);
status = Status.authenticated;
}
else {
...
}
}
}
catch (SaslException e) {
}
}
else {
...
}
}
else {
...
}
break;
default:
...
}
}
else {
}
if (status == Status.failed || status == Status.authenticated) {
session.removeSessionData("SaslServer");
session.removeSessionData("SaslMechanism");
}
return status;
}
这里首先获得前面处理第一个请求时构造的SaslServer,然后调用evaluateResponse处理客户端的信息,这里假设验证成功,则会调用authenticationSuccessful,定义在SASLAuthentication中,
private static void authenticationSuccessful(LocalSession session, String username,
byte[] successData) {
if (username != null && LockOutManager.getInstance().isAccountDisabled(username)) {
...
}
StringBuilder reply = new StringBuilder(80);
reply.append("<success xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\"");
if (successData != null) {
String successData_b64 = StringUtils.encodeBase64(successData).trim();
reply.append(">").append(successData_b64).append("</success>");
}
else {
reply.append("/>");
}
session.deliverRawText(reply.toString());
if (session instanceof ClientSession) {
((LocalClientSession) session).setAuthToken(new AuthToken(username));
}
else if (session instanceof IncomingServerSession) {
String hostname = username;
((LocalIncomingServerSession) session).addValidatedDomain(hostname);
}
}
这里就是构造返回信息并发给客户端,然后构造AuthToken并设置进LocalClientSession中。根据代码,客户端返回的信息如下,
<success xmlns="urn:ietf:params:xml:ns:xmpp-sasl">
#challenge
</success>
Sasl框架会回调XMPPCallbackHandler中的handle函数,
public void handle(final Callback[] callbacks)
throws IOException, UnsupportedCallbackException {
String realm;
String name = null;
for (Callback callback : callbacks) {
if (callback instanceof RealmCallback) {
realm = ((RealmCallback) callback).getText();
if (realm == null) {
realm = ((RealmCallback) callback).getDefaultText();
}
}
else if (callback instanceof NameCallback) {
name = ((NameCallback) callback).getName();
if (name == null) {
name = ((NameCallback) callback).getDefaultName();
}
}
else if (callback instanceof PasswordCallback) {
try {
((PasswordCallback) callback)
.setPassword(AuthFactory.getPassword(name).toCharArray());
}
...
}
else if (callback instanceof VerifyPasswordCallback) {
VerifyPasswordCallback vpcb = (VerifyPasswordCallback) callback;
try {
AuthToken at = AuthFactory.authenticate(name, new String(vpcb.getPassword()));
vpcb.setVerified((at != null));
}
catch (Exception e) {
vpcb.setVerified(false);
}
}
else if (callback instanceof AuthorizeCallback) {
...
}
else {
...
}
}
}
获得了用户名和密码之后,会调用AuthFactory.authenticate进行验证,输入为用户名和密码,
public static AuthToken authenticate(String username, String password)
throws UnauthorizedException, ConnectionException, InternalUnauthenticatedException {
authProvider.authenticate(username, password);
return new AuthToken(username);
}
authProvider默认为DefaultAuthProvider,
public void authenticate(String username, String password) throws UnauthorizedException {
username = username.trim().toLowerCase();
if (username.contains("@")) {
int index = username.indexOf("@");
String domain = username.substring(index + 1);
if (domain.equals(XMPPServer.getInstance().getServerInfo().getXMPPDomain())) {
username = username.substring(0, index);
} else {
...
}
}
try {
if (!password.equals(getPassword(username))) {
throw new UnauthorizedException();
}
}
catch (UserNotFoundException unfe) {
...
}
}
该函数就是对用户名做了一些处理,主要是调用getPassword获取密码并比对,getPassword就是简单地从数据库中读取密码。这里假设验证成功,返回到上层的authenticate中,构造AuthToken并返回。
用户验证成功后,需要绑定资源,下一章继续分析。