We have an old client application that is deployed on JBoss 4.2.3 and written in JAVA EJB2. It has classes that depend on JBoss's security libraries, an Oracle DataSource and ANT as build method. Now there is a need of upgrading the application server because JBoss 4 no longer has life support and we are required to upgrade to Wildfly(Version 8.2 in our case). Naturally we are having a lot of problems during the process and working tirelessly just to go no further from where we are.
I just would like to get community's thoughts on this process. Is it worth the effort to upgrade JBoss or should one just re-write the client from scratch with a newer technology e.g Spring? What is the best practice in a situation like this?
By the way this client is not a big application, it is used by only 6 users.
As proposed by Michele Dorigatti, Here are some more details on the project:
I already spent an estimate of 15 m/d on the upgrade proces.
We are required to implement the solution in 3 weeks from now on.
The app itself isn't that large, it consists of 1 login screen and 1 main view. There are several functionalities which would make up to maybe 15-20 use cases.
The team for the project consists of 2 developers (One being me), who have another project on their hand.
The app functions mainly on Oracle stored procedures and works maybe on 5-10 DB tables.
Also here is an example code snippet from the app
package tr.com.splogin;
import org.jboss.security.SimpleGroup;
import org.jboss.security.SimplePrincipal;
import org.jboss.security.auth.spi.AbstractServerLoginModule;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.security.auth.Subject;
import javax.security.auth.callback.*;
import javax.security.auth.login.FailedLoginException;
import javax.security.auth.login.LoginException;
import javax.sql.DataSource;
import java.security.Principal;
import java.security.acl.Group;
import java.sql.*;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
public class SPLoginModule extends AbstractServerLoginModule {
private static final int USER_LOCKEDOUT = 23;
private static final int USER_VALFAIL = 24;
private static final int USER_MAXATTEMPTS = 25;
private static final String ROLE_GROUP_NAME = "Roles";
private static final String ID_GROUP_NAME = "Id";
private static Logger logger = LoggerFactory.getLogger(SPLoginModule.class);
private static final SimplePrincipal GUEST = new SimplePrincipal("guest");
private static boolean initialized = false;
private static boolean initFailed = false;
private static Connection conn;
private static CallableStatement cs;
private static PreparedStatement ps;
private static ResultSet rs;
/**
* The principal to use when a null username and password are seen
*/
private static Principal unauthenticatedIdentity;
private static Map options;
/**
* The roles of the authenticated user
*/
private Group[] roleSets;
/**
* The proof of login identity
*/
private char[] credential;
/**
* The login identity
*/
private Principal identity;
public void initialize(Subject subject, CallbackHandler callbackHandler, Map sharedState, Map options) {
logger.info("initialize start");
System.out.println("initialize start");
super.initialize(subject, callbackHandler, sharedState, options);
if (!initialized) {
this.options = options;
init(options);
initialized = true;
}
logger.info("initialize stop");
}
private String getUsername() {
String username = null;
if (getIdentity() != null)
username = getIdentity().getName();
return username;
}
public boolean login() throws LoginException {
System.out.println("login is called.");
String[] info = getUsernameAndPassword();
String username = info[0];
String password = info[1];
logger.info(username);
logger.info(password);
super.loginOk = false;
if (username == null && password == null) {
identity = unauthenticatedIdentity;
Group roles = new SimpleGroup(ROLE_GROUP_NAME);
Set groups = new HashSet();
groups.add(roles);
roles.addMember(GUEST);
roleSets = new Group[groups.size()];
groups.toArray(roleSets);
logger.info("Authenticating as unauthenticatedIdentity=" + identity);
}
if (identity == null) {
identity = new SimplePrincipal(username);
login(username, password);
}
super.loginOk = true;
logger.info("User '" + identity + "' authenticated, loginOk=" + loginOk);
return true;
}
public Principal getIdentity() {
return identity;
}
public Group[] getRoleSets() {
return roleSets;
}
private void login(String username, String password) throws LoginException {
System.out.println("login is called.");
try {
int userIdCode = 3;
int resultCode = 4;
int result, userId;
cs.setString(1, username);
cs.setString(2, password);
cs.registerOutParameter(userIdCode, Types.INTEGER);
cs.registerOutParameter(resultCode, Types.INTEGER);
cs.execute();
result = cs.getInt(resultCode);
if (result == 0) {
userId = cs.getInt(userIdCode);
logger.info("Id: " + userId);
Group roles = new SimpleGroup(ROLE_GROUP_NAME);
Group id = new SimpleGroup(ID_GROUP_NAME);
Set groups = new HashSet();
String roleName;
groups.add(roles);
groups.add(id);
ps.setInt(1, userId);
rs = ps.executeQuery();
id.addMember(new SimplePrincipal((new Integer(userId)).toString()));
while (rs.next()) {
roleName = rs.getString(1);
logger.debug("Action: " + roleName);
roles.addMember(new SimplePrincipal(roleName));
}
roles.addMember(GUEST);
roleSets = new Group[groups.size()];
groups.toArray(roleSets);
} else {
String message = new String();
roleSets = new Group[0];
switch (result) {
case USER_VALFAIL:
System.out.println("login is failed.");
message = new String("Login failed");
break;
case USER_LOCKEDOUT:
message = new String("User is locked out");
break;
case USER_MAXATTEMPTS:
message = new String("Max number of attempts reached, user is locked out");
break;
default:
message = new String("Unkown failed login error with code: " + result);
break;
}
logger.info("Error result code: " + result);
logger.info("Error message: " + message);
throw new FailedLoginException(message);
}
} catch (SQLException e) {
logger.error(e.toString());
init(options);
if (!initFailed)
login(username, password);
} finally {
try {
if (rs != null)
rs.close();
} catch (SQLException e1) {
logger.error(e1.toString());
}
}
}
private void init(Map options) {
logger.info("init");
try {
if (cs != null)
cs.close();
if (ps != null)
ps.close();
if (conn != null)
conn.close();
} catch (SQLException e) {
logger.error(e.toString());
}
try {
InitialContext ctx = new InitialContext();
DataSource ds = (DataSource) ctx.lookup("java:/OracleDS");
conn = ds.getConnection();
String sp_login = "{call admin_pck.pc_login(?,?,?,?)}";
String query_user_action = "select aa.name from admin_user au,admin_role ar,admin_action aa,admin_user_role aur,admin_role_action ara,owner o where au.id=? and aur.id_admin_user=au.id and aa.id=ara.id_admin_action and ara.id_admin_role=ar.id and ar.id=aur.id_role and o.id=aur.id_owner and o.id=au.id_primary_owner order by aa.name";
cs = conn.prepareCall(sp_login);
ps = conn.prepareStatement(query_user_action);
String name = (String) options.get("unauthenticatedIdentity");
if (name != null) {
unauthenticatedIdentity = new SimplePrincipal(name);
logger.info("Saw unauthenticatedIdentity=" + name);
}
initFailed = false;
} catch (NamingException e) {
logger.error(e.toString());
initFailed = true;
} catch (SQLException e) {
logger.error(e.toString());
initFailed = true;
}
}
/**
* Called by login() to acquire the username and password strings for
* authentication. This method does no validation of either.
*
* #return String[], [0] = username, [1] = password
* #throws LoginException thrown if CallbackHandler is not set or fails.
*/
protected String[] getUsernameAndPassword() throws LoginException {
String[] info = {null, null};
// prompt for a username and password
if (callbackHandler == null) {
throw new LoginException("Error: no CallbackHandler available to collect authentication information");
}
NameCallback nc = new NameCallback("User name: ");
PasswordCallback pc = new PasswordCallback("Password: ", false);
Callback[] callbacks = {nc, pc};
String username = null;
String password = null;
try {
callbackHandler.handle(callbacks);
username = nc.getName();
char[] tmpPassword = pc.getPassword();
if (tmpPassword != null) {
credential = new char[tmpPassword.length];
System.arraycopy(tmpPassword, 0, credential, 0, tmpPassword.length);
pc.clearPassword();
password = new String(credential);
}
} catch (java.io.IOException e) {
throw new LoginException(e.toString());
} catch (UnsupportedCallbackException e) {
throw new LoginException("CallbackHandler does not support: " + e.getCallback());
}
info[0] = username;
info[1] = password;
return info;
}
}
While i am try to implement response.redirect in jersey by using Response.temporaryRedirect(location) and Response.seeOther(location), But both are not working as like Servlet response.sendRedirect.
As i tried,
Java script :-
function loginFunction(){
var password=$("#password").val();
var username=$("#emailId").val();
function make_base_auth(user, password) {
var tok = user + ':' + password;
var hash = btoa(tok);
return "Basic " + hash;
}
$.ajax({
url:'/test/login/filter',
type:'GET',
crossDomain: true,
beforeSend : function(req) {
req.setRequestHeader('Authorization', make_base_auth(username, password));
},
success:function(res,status,xhr){
userToken=xhr.getResponseHeader("Authorization");
localStorage.setItem("userToken", userToken);
if(userToken != undefined || userToken != null){
window.location.href = "./index.html";
}else{
$('.errmsg').html('please provide a valid credentials.')
$(this).load();
}
},
error : function(jqXHR, textStatus, errorThrown) {
console.log("textStatus is:"+textStatus);
}
});
}
Jersey Code:-
#Path("/login")
public class UserLogin {
private static Map<String ,Token> tokTable=new HashMap<String ,Token>();
#GET
#Path("/filter")
public void filter(#Context ContainerRequestContext context){
String authentication = filterContext.getHeaderString(HttpHeaders.AUTHORIZATION);
if (authentication == null) {
throw new AuthenticationException("Authentication credentials are required");
}
if (!authentication.startsWith("Basic ")) {
return null;
}
authentication = authentication.substring("Basic ".length());
String[] values = new String(DatatypeConverter.parseBase64Binary(authentication), Charset.forName("ASCII")).split(":");
String username=null,givenPass=null;
try{
username = values[0];
givenPass=org.glassfish.jersey.internal.util.Base64.encodeAsString( values[1]);
}catch(Exception e){
throw new AuthenticationException("User name and password can't be empty\r\n");
}
}
User user = AuthLookUpTables.userTable.get(username);
// Validate the extracted credentials
if ( user == null ) {
logger.info("USER NOT AUTHENTICATED");
throw new AuthenticationException("Invalid username");
}
String password=org.glassfish.jersey.internal.util.Base64.encodeAsString(givenPass.concat(user.getSalt()));
if ((username == null) || (password == null)) {
throw new WebApplicationException(400);
}
String dbPass=org.glassfish.jersey.internal.util.Base64.encodeAsString(user.getHashedPassword().concat(user.getSalt()));
if ( dbPass.equals(password) ) {
logger.info("USER AUTHENTICATED");
//SOme code using for main sever.
} else {
logger.info("USER NOT AUTHENTICATED");
throw new AuthenticationException("Invalid username or password\r\n");
}
return user;
}
POJO (USER):-
public class User {
public String username;
public String roles;
public String salt;
public String hashedPassword;
//and below getter and setter methods
}
Custom Exception:-
public class AuthenticationException extends RuntimeException {
public AuthenticationException() {
super();
}
public AuthenticationException(String message) {
super(message);
}
}
Exception Mapper:-
#Provider
public class AuthenticationExceptionMapper implements ExceptionMapper<AuthenticationException> {
private Logger logger=Logger.getLogger(AuthenticationExceptionMapper.class);
#Context
UriInfo uriInfo;
public Response toResponse(AuthenticationException e) {
UriBuilder builder=null;
if (e.getMessage() != null) {
builder=UriBuilder.fromPath(uriInfo.getBaseUriBuilder().toString()).path("..");
return Response.temporaryRedirect(builder.build()).build();
} else {
return Response
.status(Status.UNAUTHORIZED)
.type("text/plain")
.entity(e.getMessage())
.build();
}
}
}
this is what i get in developer tool,
But, i am expecting server redirect to login page, how do i do that, kindly same one help me to do this.
thank you
I found this guide for developing your own Server Authentication Module (SAM) for Glassfish: http://docs.oracle.com/cd/E18930_01/html/821-2418/gizel.html
It seems pretty straightforward to verify some credentials (in HTTP Auth headers for instance), but my question is this:
Can I develop my SAM in such a way that I can forward the user to a specific page if he's not logged in?
Here's the example from the guide:
package tip.sam;
import java.io.IOException;
import java.util.Map;
import javax.security.auth.Subject;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.UnsupportedCallbackException;
import javax.security.auth.message.AuthException;
import javax.security.auth.message.AuthStatus;
import javax.security.auth.message.MessageInfo;
import javax.security.auth.message.MessagePolicy;
import javax.security.auth.message.callback.CallerPrincipalCallback;
import javax.security.auth.message.callback.GroupPrincipalCallback;
import javax.security.auth.message.callback.PasswordValidationCallback;
import javax.security.auth.message.module.ServerAuthModule;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.catalina.util.Base64;
public class MySam implements ServerAuthModule {
protected static final Class[]
supportedMessageTypes = new Class[]{
HttpServletRequest.class,
HttpServletResponse.class
};
private MessagePolicy requestPolicy;
private MessagePolicy responsePolicy;
private CallbackHandler handler;
private Map options;
private String realmName = null;
private String defaultGroup[] = null;
private static final String REALM_PROPERTY_NAME =
"realm.name";
private static final String GROUP_PROPERTY_NAME =
"group.name";
private static final String BASIC = "Basic";
static final String AUTHORIZATION_HEADER =
"authorization";
static final String AUTHENTICATION_HEADER =
"WWW-Authenticate";
public void initialize(MessagePolicy reqPolicy,
MessagePolicy resPolicy,
CallbackHandler cBH, Map opts)
throws AuthException {
requestPolicy = reqPolicy;
responsePolicy = resPolicy;
handler = cBH;
options = opts;
if (options != null) {
realmName = (String)
options.get(REALM_PROPERTY_NAME);
if (options.containsKey(GROUP_PROPERTY_NAME)) {
defaultGroup = new String[]{(String)
options.get(GROUP_PROPERTY_NAME)};
}
}
}
public Class[] getSupportedMessageTypes() {
return supportedMessageTypes;
}
public AuthStatus validateRequest(
MessageInfo msgInfo, Subject client,
Subject server) throws AuthException {
try {
String username =
processAuthorizationToken(msgInfo, client);
if (username ==
null && requestPolicy.isMandatory()) {
return sendAuthenticateChallenge(msgInfo);
}
setAuthenticationResult(
username, client, msgInfo);
return AuthStatus.SUCCESS;
} catch (Exception e) {
AuthException ae = new AuthException();
ae.initCause(e);
throw ae;
}
}
private String processAuthorizationToken(
MessageInfo msgInfo, Subject s)
throws AuthException {
HttpServletRequest request =
(HttpServletRequest)
msgInfo.getRequestMessage();
String token =
request.getHeader(AUTHORIZATION_HEADER);
if (token != null && token.startsWith(BASIC + " ")) {
token = token.substring(6).trim();
// Decode and parse the authorization token
String decoded =
new String(Base64.decode(token.getBytes()));
int colon = decoded.indexOf(':');
if (colon <= 0 || colon == decoded.length() - 1) {
return (null);
}
String username = decoded.substring(0, colon);
// use the callback to ask the container to
// validate the password
PasswordValidationCallback pVC =
new PasswordValidationCallback(s, username,
decoded.substring(colon + 1).toCharArray());
try {
handler.handle(new Callback[]{pVC});
pVC.clearPassword();
} catch (Exception e) {
AuthException ae = new AuthException();
ae.initCause(e);
throw ae;
}
if (pVC.getResult()) {
return username;
}
}
return null;
}
private AuthStatus sendAuthenticateChallenge(
MessageInfo msgInfo) {
String realm = realmName;
// if the realm property is set use it,
// otherwise use the name of the server
// as the realm name.
if (realm == null) {
HttpServletRequest request =
(HttpServletRequest)
msgInfo.getRequestMessage();
realm = request.getServerName();
}
HttpServletResponse response =
(HttpServletResponse)
msgInfo.getResponseMessage();
String header = BASIC + " realm=\"" + realm + "\"";
response.setHeader(AUTHENTICATION_HEADER, header);
response.setStatus(
HttpServletResponse.SC_UNAUTHORIZED);
return AuthStatus.SEND_CONTINUE;
// MAYBE SOMETHING HERE?
}
public AuthStatus secureResponse(
MessageInfo msgInfo, Subject service)
throws AuthException {
return AuthStatus.SEND_SUCCESS;
}
public void cleanSubject(MessageInfo msgInfo,
Subject subject)
throws AuthException {
if (subject != null) {
subject.getPrincipals().clear();
}
}
private static final String AUTH_TYPE_INFO_KEY =
"javax.servlet.http.authType";
// distinguish the caller principal
// and assign default groups
private void setAuthenticationResult(String name,
Subject s, MessageInfo m)
throws IOException,
UnsupportedCallbackException {
handler.handle(new Callback[]{
new CallerPrincipalCallback(s, name)
});
if (name != null) {
// add the default group if the property is set
if (defaultGroup != null) {
handler.handle(new Callback[]{
new GroupPrincipalCallback(s, defaultGroup)
});
}
m.getMap().put(AUTH_TYPE_INFO_KEY, ""MySAM");
}
}
}
Yes, you can do that in the validateRequest method.
Here is a simple example:
public AuthStatus validateRequest(MessageInfo messageInfo,
Subject clientSubject,
Subject serviceSubject) throws AuthException {
// clientSubject.getPrincipals() returns the principals
// check this set to know if the user is not logged in
// if the user is not logged in do the following
HttpServletResponse response = (HttpServletResponse) messageInfo.getResponseMessage();
response.sendRedirect("login.html");
}
It might be better to do it inside of a custom LoginModule (if you already know what that is), but I guess this depends on your requirements.
See also:
LoginModule Bridge Profile (JASPIC) in glassfish
Implementing container authentication in Java EE with JASPIC
JAAS for human beings
I'm calling SAOP Webservice via a main method and it works fine.. But when i invoke the same method via a browser called method it give me the following error.
Caused by: java.io.IOException: Server returned HTTP response code: 403 for URL:
------------Working Code as Follows---------------------------
public class WSConnectionUtil {
private static final WSConnectionUtil INSTANCE = new WSConnectionUtil();
public SynchronizationServiceWSImpl getSyncServicePort(){
SynchronizationServiceWSImplService service = new SynchronizationServiceWSImplService();
SynchronizationServiceWSImpl servicePort = service.getSynchronizationServiceWSImplPort();
((BindingProvider) servicePort).getRequestContext().put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY,getInstance().getSyncUrl());
return servicePort;
}
public static WSConnectionUtil getInstance() {
return INSTANCE;
}
private String getSyncUrl(){
String url = "http://10.2.241.33/synchronize?wsdl";
return url;
}
}
public void syncAll(){
System.out.println("===========syncAll======"+new Date());
SynchronizationRequest request = new SynchronizationRequest();
WSConnectionUtil wsCon = WSConnectionUtil.getInstance();
request.setPosCode("TNCB");
SynchronizationResponse response = wsCon.getSyncServicePort().synchronize(request);
List<String> types = response.getUpdateTypes();
System.out.println("===========types======"+types.size());
}
----------Error Code---------------------
/**
*
* service for login execution
* - user : Contains user id & password
* #param user
* #return
*
*/
#RequestMapping(value = "/login", method = RequestMethod.POST)
public #ResponseBody ModelMap login(#ModelAttribute ("User")User user ){
String username = user.getName();
String password = user.getPassword();
ModelMap model = new ModelMap();
Boolean status = loginService.login(username, password);
if(status == true){
model.put("status", true);
}
return model;
}
public boolean login(String loginUser,String password){
Steward steward = new Steward();
steward.setStewardId(Integer.parseInt(loginUser));
//List<Steward> stewardsList = stewardDao.getStewardsByCriteria(steward);
//if(stewardsList!=null && stewardsList.size()>0){
// steward = stewardsList.get(0);
//}else{
// LOG.error("Cannot Find a Steward for Login : "+loginUser);
// return false;
//}
TouchPosApplication.getApplication().setUser("SYSTEM");
TouchPosApplication.getApplication().setOutletCode("A");
TouchPosApplication.getApplication().setLoginUserId(loginUser);
// final SynchronizationServiceImpl impl = new SynchronizationServiceImpl();
// impl.syncAll();
new Thread(new Runnable() {
private static final long serialVersionUID = -4094418102152819603L;
#Override
public void run() {
while (true) {
long i =0;
try {
i = 1000 * 60 * 1;
Thread.sleep(i);
} catch (InterruptedException e) {
System.out.println("===InterruptedException==========="+e);
}
SyncUtil.synchronizeAutomatic(true);
}
}
}).start();
LOG.info("::::: Successfuly Logged In :"+loginUser);
return true;
}
I have tried all the things to connect Facebook with XMPP but i have faced only
one error all the time which is :
SASL authentication failed using mechanism DIGEST-MD5 I am implementing following method to perform this task :
public class MySASLDigestMD5Mechanism extends SASLMechanism {
public MySASLDigestMD5Mechanism(SASLAuthentication saslAuthentication) {
super(saslAuthentication);
}
protected void authenticate() throws IOException, XMPPException {
String[] mechanisms = { getName() };
Map<String, String> props = new HashMap<String, String>();
sc = Sasl.createSaslClient(mechanisms, null, "xmpp", hostname, props, this);
super.authenticate();
}
public void authenticate(String username, String host, String password) throws IOException, XMPPException {
this.authenticationId = username;
this.password = password;
this.hostname = host;
String[] mechanisms = { getName() };
Map<String,String> props = new HashMap<String,String>();
sc = Sasl.createSaslClient(mechanisms, null, "xmpp", host, props, this);
super.authenticate();
}
public void authenticate(String username, String host, CallbackHandler cbh) throws IOException, XMPPException {
String[] mechanisms = { getName() };
Map<String,String> props = new HashMap<String,String>();
sc = Sasl.createSaslClient(mechanisms, null, "xmpp", host, props, (org.apache.harmony.javax.security.auth.callback.CallbackHandler) cbh);
super.authenticate();
}
protected String getName() {
return "DIGEST-MD5";
}
/*public void challengeReceived1(String challenge) throws IOException {
// Build the challenge response stanza encoding the response text
StringBuilder stanza = new StringBuilder();
byte response[];
if (challenge != null) {
response = sc.evaluateChallenge(Base64.decode(challenge));
} else {
response = sc.evaluateChallenge(null);
}
String authenticationText="";
if (response != null) { // fix from 3.1.1
authenticationText = Base64.encodeBytes(response, Base64.DONT_BREAK_LINES);
if (authenticationText.equals("")) {
authenticationText = "=";
}
}
stanza.append("<response xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\">");
stanza.append(authenticationText);
stanza.append("</response>");
// Send the authentication to the server
getSASLAuthentication().send(stanza.toString());
}*/
public void challengeReceived(String challenge)
throws IOException {
byte response[];
if (challenge != null) {
response = sc.evaluateChallenge(Base64.decode(challenge));
} else {
response = sc.evaluateChallenge(new byte[0]);
}
Packet responseStanza;
if (response == null) {
responseStanza = new Response();
} else {
responseStanza = new Response(Base64.encodeBytes(response, Base64.DONT_BREAK_LINES));
}
getSASLAuthentication().send(responseStanza);
}
}
And Connection Function is :
try{
SASLAuthentication.registerSASLMechanism("DIGEST-MD5",MySASLDigestMD5Mechanism. class);
ConnectionConfiguration config = new ConnectionConfiguration("chat.facebook.com",5222);
config.setSASLAuthenticationEnabled(true);
config.setRosterLoadedAtLogin (true);
connection = new XMPPConnection(config);
connection.connect();
Log.d("Connect...", "Afetr Connect");
connection.login("username#chat.facebook.com", "password");
Log.d("done","XMPP client logged in");
}
catch(XMPPException ex)
{
Log.d("not done","in catchhhhhhhhh");
System.out.println(ex.getMessage ());
connection.disconnect();
}
}
but "After connect" it gone to the ctach and give me error like :
SASL authentication failed using mechanism DIGEST-MD5
I searched all blog and find same thing but i dont know what am i doing wrong here..
If is there any other way or solution to connect Facebook XMPP then please Help me
ASAP
Finally, thanks to the no.good.at.coding code and the suggestion of harism, I've been able to connect to the Facebook chat. This code is the Mechanism for the Asmack library (the Smack port for Android). For the Smack library is necessary to use the no.good.at.coding mechanism.
SASLXFacebookPlatformMechanism.java:
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.Map;
import org.apache.harmony.javax.security.auth.callback.CallbackHandler;
import org.apache.harmony.javax.security.sasl.Sasl;
import org.jivesoftware.smack.SASLAuthentication;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.sasl.SASLMechanism;
import org.jivesoftware.smack.util.Base64;
public class SASLXFacebookPlatformMechanism extends SASLMechanism
{
private static final String NAME = "X-FACEBOOK-PLATFORM";
private String apiKey = "";
private String applicationSecret = "";
private String sessionKey = "";
/** * Constructor. */
public SASLXFacebookPlatformMechanism(SASLAuthentication saslAuthentication)
{
super(saslAuthentication);
}
#Override
protected void authenticate() throws IOException, XMPPException
{
getSASLAuthentication().send(new AuthMechanism(NAME, ""));
}
#Override
public void authenticate(String apiKeyAndSessionKey, String host, String applicationSecret) throws IOException, XMPPException
{
if (apiKeyAndSessionKey == null || applicationSecret == null)
{
throw new IllegalArgumentException("Invalid parameters");
}
String[] keyArray = apiKeyAndSessionKey.split("\\|", 2);
if (keyArray.length < 2)
{
throw new IllegalArgumentException( "API key or session key is not present"); }
this.apiKey = keyArray[0];
this.applicationSecret = applicationSecret;
this.sessionKey = keyArray[1];
this.authenticationId = sessionKey;
this.password = applicationSecret;
this.hostname = host;
String[] mechanisms = { "DIGEST-MD5" };
Map<String, String> props = new HashMap<String, String>();
this.sc = Sasl.createSaslClient(mechanisms, null, "xmpp", host, props,this);
authenticate();
}
#Override
public void authenticate(String username, String host, CallbackHandler cbh)throws IOException, XMPPException
{
String[] mechanisms = { "DIGEST-MD5" };
Map<String, String> props = new HashMap<String, String>();
this.sc = Sasl.createSaslClient(mechanisms, null, "xmpp", host, props,cbh);
authenticate();
} #Override protected String getName()
{
return NAME;
}
#Override
public void challengeReceived(String challenge) throws IOException
{
byte[] response = null;
if (challenge != null)
{
String decodedChallenge = new String(Base64.decode(challenge));
Map<String, String> parameters = getQueryMap(decodedChallenge);
String version = "1.0";
String nonce = parameters.get("nonce");
String method = parameters.get("method");
long callId = new GregorianCalendar().getTimeInMillis();
String sig = "api_key=" + apiKey + "call_id=" + callId + "method=" + method + "nonce=" + nonce + "session_key=" + sessionKey + "v=" + version + applicationSecret;
try
{
sig = md5(sig);
}
catch (NoSuchAlgorithmException e)
{
throw new IllegalStateException(e);
}
String composedResponse = "api_key=" + URLEncoder.encode(apiKey, "utf-8") + "&call_id=" + callId + "&method="+ URLEncoder.encode(method, "utf-8") + "&nonce="+ URLEncoder.encode(nonce, "utf-8")+ "&session_key="+ URLEncoder.encode(sessionKey, "utf-8") + "&v="+ URLEncoder.encode(version, "utf-8") + "&sig="+ URLEncoder.encode(sig, "utf-8");response = composedResponse.getBytes("utf-8");
}
String authenticationText = "";
if (response != null)
{
authenticationText = Base64.encodeBytes(response, Base64.DONT_BREAK_LINES);
}
// Send the authentication to the server
getSASLAuthentication().send(new Response(authenticationText));
}
private Map<String, String> getQueryMap(String query)
{
Map<String, String> map = new HashMap<String, String>();
String[] params = query.split("\\&");
for (String param : params)
{
String[] fields = param.split("=", 2);
map.put(fields[0], (fields.length > 1 ? fields[1] : null));
}
return map;
}
private String md5(String text) throws NoSuchAlgorithmException,UnsupportedEncodingException
{
MessageDigest md = MessageDigest.getInstance("MD5");
md.update(text.getBytes("utf-8"), 0, text.length());
return convertToHex(md.digest());
}
private String convertToHex(byte[] data)
{
StringBuilder buf = new StringBuilder();
int len = data.length;
for (int i = 0; i < len; i++)
{
int halfByte = (data[i] >>> 4) & 0xF;
int twoHalfs = 0;
do
{
if (0 <= halfByte && halfByte <= 9)
{
buf.append((char) ('0' + halfByte));
}
else
{
buf.append((char) ('a' + halfByte - 10));
}
halfByte = data[i] & 0xF;
}
while (twoHalfs++ < 1);
}
return buf.toString();
}
}
To use it:
ConnectionConfiguration config = new ConnectionConfiguration("chat.facebook.com", 5222);
config.setSASLAuthenticationEnabled(true);
XMPPConnection xmpp = new XMPPConnection(config);
try
{
SASLAuthentication.registerSASLMechanism("X-FACEBOOK-PLATFORM", SASLXFacebookPlatformMechanism.class);
SASLAuthentication.supportSASLMechanism("X-FACEBOOK-PLATFORM", 0);
xmpp.connect();
xmpp.login(apiKey + "|" + sessionKey, sessionSecret, "Application");
}
catch (XMPPException e)
{
xmpp.disconnect();
e.printStackTrace();
}
apiKey is the API key given in the application settings page in Facebook. sessionKey is the second part of the access token. If the token is in this form, AAA|BBB|CCC, the BBB is the session key. sessionSecret is obtained using the old REST API with the method auth.promoteSession. To use it, it's needed to make a Http get to this url:
https://api.facebook.com/method/auth.promoteSession?access_token=yourAccessToken
Despite of the Facebook Chat documentation says that it's needed to use your application secret key, only when I used the key that returned that REST method I was able to make it works. To make that method works, you have to disable the Disable Deprecated Auth Methods option in the Advance tab in your application settings.
I solved this problem. I find solution that http://community.igniterealtime.org/thread/41080
Jerry Magill wrote this...
import java.io.IOException;
import java.util.HashMap;
import javax.security.auth.callback.CallbackHandler;
import javax.security.sasl.Sasl;
import org.jivesoftware.smack.SASLAuthentication;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.sasl.SASLMechanism;
import org.jivesoftware.smack.packet.Packet;
import org.jivesoftware.smack.util.Base64;
public class MySASLDigestMD5Mechanism extends SASLMechanism
{
public MySASLDigestMD5Mechanism(SASLAuthentication saslAuthentication)
{
super(saslAuthentication);
}
protected void authenticate()
throws IOException, XMPPException
{
String mechanisms[] = {
getName()
};
java.util.Map props = new HashMap();
sc = Sasl.createSaslClient(mechanisms, null, "xmpp", hostname, props, this);
super.authenticate();
}
public void authenticate(String username, String host, String password)
throws IOException, XMPPException
{
authenticationId = username;
this.password = password;
hostname = host;
String mechanisms[] = {
getName()
};
java.util.Map props = new HashMap();
sc = Sasl.createSaslClient(mechanisms, null, "xmpp", host, props, this);
super.authenticate();
}
public void authenticate(String username, String host, CallbackHandler cbh)
throws IOException, XMPPException
{
String mechanisms[] = {
getName()
};
java.util.Map props = new HashMap();
sc = Sasl.createSaslClient(mechanisms, null, "xmpp", host, props, cbh);
super.authenticate();
}
protected String getName()
{
return "DIGEST-MD5";
}
public void challengeReceived(String challenge)
throws IOException
{
//StringBuilder stanza = new StringBuilder();
byte response[];
if(challenge != null)
response = sc.evaluateChallenge(Base64.decode(challenge));
else
//response = sc.evaluateChallenge(null);
response = sc.evaluateChallenge(new byte[0]);
//String authenticationText = "";
Packet responseStanza;
//if(response != null)
//{
//authenticationText = Base64.encodeBytes(response, 8);
//if(authenticationText.equals(""))
//authenticationText = "=";
if (response == null){
responseStanza = new Response();
} else {
responseStanza = new Response(Base64.encodeBytes(response,Base64.DONT_BREAK_LINES));
}
//}
//stanza.append("<response xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\">");
//stanza.append(authenticationText);
//stanza.append("</response>");
//getSASLAuthentication().send(stanza.toString());
getSASLAuthentication().send(responseStanza);
}
}
It is then called thus from the JabberSmackAPI:
public void login(String userName, String password) throws XMPPException
{
SASLAuthentication.registerSASLMechanism("DIGEST-MD5",MySASLDigestMD5Mechanism. class);
ConnectionConfiguration config = new ConnectionConfiguration("chat.facebook.com",5222);
config.setSASLAuthenticationEnabled(true);
config.setRosterLoadedAtLogin (true);
connection = new XMPPConnection(config);
connection.connect();
connection.login(userName, password);
}