📄 saslauthentication.java
字号:
/**
* $RCSfile$
* $Revision: $
* $Date: $
*
* Copyright (C) 2005 Jive Software. All rights reserved.
*
* This software is published under the terms of the GNU Public License (GPL),
* a copy of which is included in this distribution.
*/
package org.jivesoftware.wildfire.net;
import org.dom4j.Element;
import org.jivesoftware.util.JiveGlobals;
import org.jivesoftware.util.Log;
import org.jivesoftware.util.StringUtils;
import org.jivesoftware.wildfire.ClientSession;
import org.jivesoftware.wildfire.Session;
import org.jivesoftware.wildfire.XMPPServer;
import org.jivesoftware.wildfire.auth.AuthFactory;
import org.jivesoftware.wildfire.auth.AuthToken;
import org.jivesoftware.wildfire.auth.UnauthorizedException;
import org.jivesoftware.wildfire.server.IncomingServerSession;
import org.xmpp.packet.JID;
import javax.net.ssl.SSLPeerUnverifiedException;
import javax.security.sasl.Sasl;
import javax.security.sasl.SaslException;
import javax.security.sasl.SaslServer;
import java.io.UnsupportedEncodingException;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.util.*;
/**
* SASLAuthentication is responsible for returning the available SASL mechanisms to use and for
* actually performing the SASL authentication.<p>
*
* The list of available SASL mechanisms is determined by 1) the type of
* {@link org.jivesoftware.wildfire.user.UserProvider} being used since some SASL mechanisms
* require the server to be able to retrieve user passwords; 2) whether anonymous logins are
* enabled or not and 3) whether the underlying connection has been secured or not.
*
* @author Hao Chen
* @author Gaston Dombiak
*/
public class SASLAuthentication {
/**
* The utf-8 charset for decoding and encoding Jabber packet streams.
*/
protected static String CHARSET = "UTF-8";
private static final String SASL_NAMESPACE = "xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\"";
private static Map<String, ElementType> typeMap = new TreeMap<String, ElementType>();
private static Set<String> mechanisms = null;
static {
initMechanisms();
}
public enum ElementType {
AUTH("auth"), RESPONSE("response"), CHALLENGE("challenge"), FAILURE("failure"), UNDEF("");
private String name = null;
public String toString() {
return name;
}
private ElementType(String name) {
this.name = name;
typeMap.put(this.name, this);
}
public static ElementType valueof(String name) {
if (name == null) {
return UNDEF;
}
ElementType e = typeMap.get(name);
return e != null ? e : UNDEF;
}
}
public enum Status {
/**
* Entity needs to respond last challenge. Session is still negotiating
* SASL authentication.
*/
needResponse,
/**
* SASL negotiation has failed. The entity may retry a few times before the connection
* is closed.
*/
failed,
/**
* SASL negotiation has been successful.
*/
authenticated
}
/**
* Returns a string with the valid SASL mechanisms available for the specified session. If
* the session's connection is not secured then only include the SASL mechanisms that don't
* require TLS.
*
* @return a string with the valid SASL mechanisms available for the specified session.
*/
public static String getSASLMechanisms(Session session) {
if (!(session instanceof ClientSession) && !(session instanceof IncomingServerSession)) {
return "";
}
StringBuilder sb = new StringBuilder(195);
sb.append("<mechanisms xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\">");
if (session instanceof IncomingServerSession) {
// Server connections dont follow the same rules as clients
if (session.getConnection().isSecure()) {
// Offer SASL EXTERNAL only if TLS has already been negotiated
sb.append("<mechanism>EXTERNAL</mechanism>");
}
}
else {
for (String mech : mechanisms) {
if (mech.equals("CRAM-MD5") || mech.equals("DIGEST-MD5")) {
// Check if the user provider in use supports passwords retrieval. Accessing
// to the users passwords will be required by the CallbackHandler
if (!AuthFactory.getAuthProvider().supportsPasswordRetrieval()) {
continue;
}
}
else if (mech.equals("ANONYMOUS")) {
// Check anonymous is supported
if (!XMPPServer.getInstance().getIQAuthHandler().isAnonymousAllowed()) {
continue;
}
}
sb.append("<mechanism>");
sb.append(mech);
sb.append("</mechanism>");
}
}
sb.append("</mechanisms>");
return sb.toString();
}
/**
* Handles the SASL authentication packet. The entity may be sending an initial
* authentication request or a response to a challenge made by the server. The returned
* value indicates whether the authentication has finished either successfully or not or
* if the entity is expected to send a response to a challenge.
*
* @param session the session that is authenticating with the server.
* @param doc the stanza sent by the authenticating entity.
* @return value that indicates whether the authentication has finished either successfully
* or not or if the entity is expected to send a response to a challenge.
* @throws UnsupportedEncodingException If UTF-8 charset is not supported.
*/
public static Status handle(Session 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 AUTH:
mechanism = doc.attributeValue("mechanism");
// Store the requested SASL mechanism by the client
session.setSessionData("SaslMechanism", mechanism);
//Log.debug("SASLAuthentication.doHandshake() AUTH entered: "+mechanism);
if (mechanism.equalsIgnoreCase("PLAIN") &&
mechanisms.contains("PLAIN")) {
status = doPlainAuthentication(session, doc);
}
else if (mechanism.equalsIgnoreCase("ANONYMOUS") &&
mechanisms.contains("ANONYMOUS")) {
status = doAnonymousAuthentication(session);
}
else if (mechanism.equalsIgnoreCase("EXTERNAL")) {
status = doExternalAuthentication(session, doc);
}
else if (mechanisms.contains(mechanism)) {
// The selected SASL mechanism requires the server to send a challenge
// to the client
try {
Map<String, String> props = new TreeMap<String, String>();
props.put(Sasl.QOP, "auth");
if (mechanism.equals("GSSAPI")) {
props.put(Sasl.SERVER_AUTH, "TRUE");
}
SaslServer ss = Sasl.createSaslServer(mechanism, "xmpp",
session.getServerName(), props,
new XMPPCallbackHandler());
// evaluateResponse doesn't like null parameter
byte[] token = new byte[0];
if (doc.isTextOnly()) {
// If auth request includes a value then validate it
token = StringUtils.decodeBase64(doc.getTextTrim());
if (token == null) {
token = new byte[0];
}
}
byte[] challenge = ss.evaluateResponse(token);
// Send the challenge
sendChallenge(session, challenge);
session.setSessionData("SaslServer", ss);
status = Status.needResponse;
}
catch (SaslException e) {
Log.warn("SaslException", e);
authenticationFailed(session);
status = Status.failed;
}
}
else {
Log.warn("Client wants to do a MECH we don't support: '" +
mechanism + "'");
authenticationFailed(session);
status = Status.failed;
}
break;
case RESPONSE:
// Store the requested SASL mechanism by the client
mechanism = (String) session.getSessionData("SaslMechanism");
if (mechanism.equalsIgnoreCase("PLAIN") &&
mechanisms.contains("PLAIN")) {
status = doPlainAuthentication(session, doc);
}
else if (mechanism.equalsIgnoreCase("EXTERNAL")) {
status = doExternalAuthentication(session, doc);
}
else if (mechanisms.contains(mechanism)) {
SaslServer ss = (SaslServer) session.getSessionData("SaslServer");
if (ss != null) {
boolean ssComplete = ss.isComplete();
String response = doc.getTextTrim();
try {
if (ssComplete) {
authenticationSuccessful(session, ss.getAuthorizationID(),
null);
status = Status.authenticated;
}
else {
byte[] data = StringUtils.decodeBase64(response);
if (data == null) {
data = new byte[0];
}
byte[] challenge = ss.evaluateResponse(data);
if (ss.isComplete()) {
authenticationSuccessful(session, ss.getAuthorizationID(),
challenge);
status = Status.authenticated;
}
else {
// Send the challenge
sendChallenge(session, challenge);
status = Status.needResponse;
}
}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -