Sviluppare una canvas app per Facebook con Java: autentificazione

Tags: 

L'area sviluppatori di facebook mette a disposizione SDK, strumenti e esempi in PHP ma pressochè nulla per Java. L'unica cosa che ci va vicino è la libreria per Android, che comunque è estremamente orientata alle applicazioni mobili (e al di la di qualche snippet di codice non si estrae nulla di buono).

Di librerie esterne se ne trovano varie, alcune ormai datate, altre valide e aggiornate per l'accesso alle graph api (http://restfb.com/), ma che non permettono di ottenere il token di accesso mediante signed_request (il metodo usato delle canvas app).

In questo articolo illustro il metodo per ottenere questo token, e più in generale per decodificare i dati passati da facebook alle applicazioni mediante signed_request.

Il metodo è aggiornato per OAuth 2.0 (testato in maggio 2012), e prevede l'utilizzo di 2 librerie esterne:

  • Apache commons codec http://commons.apache.org/codec/
  • JSon.org http://www.json.org/java/
  • (Facoltativo) RestFB http://restfb.com/
  •  

  • A differenza di snippet di codice che si trovano in giro questo verifica anche la signature dei dati passati (per evitare accessi non autorizzati).
  •  

    import java.io.UnsupportedEncodingException;
    import java.security.InvalidKeyException;
    import java.security.NoSuchAlgorithmException;
     
    import javax.crypto.Mac;
    import javax.crypto.spec.SecretKeySpec;
    import javax.servlet.http.HttpServletRequest;
     
    import org.apache.commons.codec.binary.Base64;
    import org.json.JSONObject;
     
    import com.restfb.DefaultFacebookClient;
    import com.restfb.FacebookClient;
    import com.restfb.types.User;
    public class Facebook {
        // Configurazioni, inserite dell'init
        private String appId;
        private String secretKey;
        
        // Dati ottenuti dal signed_request
        private String userId;
        private String authToken;
        
        public void init(String appId, String secretKey) {
            this.appId = appId;
            this.secretKey = secretKey;
        }
        
        /**
         * Inizializza la richiesta a una canvas app:
         * - cerca se facebook ha inviato un signed_request, nel caso lo controlla, decodifica, ottiene le informazioni utili e le mette in sessione
         * - altrimenti cerca le informazioni in sessione
         * 
         * @param request
         * @return TRUE se l'inizializzazione e' stata corretta e l'utente risulta autorizzato su facebook
         * @throws FacebookException 
         */
        public boolean initCanvasApp(HttpServletRequest request) throws FacebookException {
            String signedRequest = request.getParameter("signed_request");
            if (signedRequest != null && parseSignedRequest(signedRequest)) {
                if (userId != null)
                    request.getSession().setAttribute("facebook_user_id", userId);
                if (authToken != null)
                    request.getSession().setAttribute("facebook_auth_token", authToken);
                return true;
            }
            
            if (request.getSession().getAttribute("facebook_user_id") != null)
                userId = (String) request.getSession().getAttribute("facebook_user_id");
            if (request.getSession().getAttribute("facebook_auth_token") != null)
                authToken = (String) request.getSession().getAttribute("facebook_auth_token");
            return isAuthorized();
        }
     
        /**
         * @see http://developers.facebook.com/docs/authentication/signed_request/
         */
        public boolean parseSignedRequest(String signedReq) throws FacebookException {
            // it is important to enable url-safe mode for Base64 encoder
            Base64 base64 = new Base64(true);
     
            // split request into signature and data
            String[] signedRequest = signedReq.split("\\.", 2);
     
            // parse data and convert to json object
            try {
                // parse signature
                String sig = new String(base64.decode(signedRequest[0].getBytes("UTF-8")));
                String payload = new String(base64.decode(signedRequest[1].getBytes("UTF-8")));
     
                JSONObject payloadObject = new JSONObject(payload);
     
                // check signature algorithm
                if (!payloadObject.has("algorithm") || !("" + payloadObject.get("algorithm")).equals("HMAC-SHA256")) {
                    return false;
                }
                // check if data is signed correctly
                if (!hmacSHA256(signedRequest[1], secretKey).equals(sig)) {
                    return false;
                }
     
                if (payloadObject.has("user_id"))
                    userId = "" + payloadObject.get("user_id");
                if (payloadObject.has("oauth_token"))
                    authToken = "" + payloadObject.get("oauth_token");
     
                return true;
     
            } catch (Exception e) {
                throw new FacebookException("Error parsing facebook signed_request: " + e.getMessage(), e);
            }
        }
        
        public boolean isAuthorized() {
            return userId != null && authToken != null;
        }
        
        public String getUserId() {
            return userId;
        }
        
        public FacebookClient getFacebookClient() {
            if (authToken != null)
                return new DefaultFacebookClient(authToken);
            else
                return new DefaultFacebookClient();
        }
        
        public User getMe() {
            if (!isAuthorized())
                return null;
            FacebookClient facebookClient = getFacebookClient();
            return facebookClient.fetchObject("me", User.class);
        }
     
        /**
         * HmacSHA256 implementation 
         * @throws UnsupportedEncodingException 
         * @throws NoSuchAlgorithmException 
         * @throws InvalidKeyException 
         */
        private String hmacSHA256(String data, String key) throws UnsupportedEncodingException, NoSuchAlgorithmException, InvalidKeyException {
            SecretKeySpec secretKey = new SecretKeySpec(key.getBytes("UTF-8"), "HmacSHA256");
            Mac mac = Mac.getInstance("HmacSHA256");
            mac.init(secretKey);
            byte[] hmacData = mac.doFinal(data.getBytes("UTF-8"));
            return new String(hmacData);
        }
    }
     
    public class FacebookException extends Exception {
        private static final long serialVersionUID = 2816200733258622814L;
     
        public FacebookException(String message) {
            super(message);
        }
     
        public FacebookException(String message, Throwable cause) {
            super(message, cause);
        }
    }

    Per inizializzare il sistema da dentro una servlet occorre chiamare prima il metodo "init(appId, secret)", quindi il metodo "initCanvasApp(servletRequest)".

    Il sistema automaticamente memorizza in sessione in token, in modo da avere a disposizione la connessione facebook anche alle richieste successive alla prima.

    Da notare che i metodi "getFacebookClient" e "getMe" sono dei metodi "bonus" che utilizzano la libreria RestFB per ottenere il collegamento alle Graph API e i dati dell'utente loggato. Se usate altre librerie di accesso alle Graph API, o non vi serve tale collegamento, potete eliminare quei metodi e non usare la libreria RestFB.

    Aggiungi un commento