In questo articolo verrà utilizzato il protocollo Oauth 2.0 per per interagire con il social network Facebook: verrà costruita una semplice web application completa, sviluppando le idee già esposte nell'articolo introduttivo su OAuth.
Le funzionalità principali che implementeremo nella nostra web application saranno le seguenti:
- Autenticazione al servizio.
- Visualizzazione dei dati dell'utente connesso.
- Visualizzazione dei dati dei suoi contatti.
- Pubblicazione di un messaggio sulla bacheca di un contatto.
- Pubblicazione di un messaggio sulla bacheca di un gruppo di contatti contemporaneamente (messaggio broadcast).
- Visualizzazione degli ultimi post pubblicati sulla bacheca di un contatto.
I file necessari sono i seguenti:
web.xml
web.xml – il descrittore della nostra web applicazione nel quale definiamo le servlet e la nostra pagina home.
Pagine JSP
index.jsp
– la home page del nostro sito, dove richiederemo all'utente di accedere al servizio, oppure dove stamperemo i suoi dati, se autenticato.error.jsp
– la pagina di errore nella quale verrà rediretto l'utente che non ci concederà l'autorizzazione ad accedere ai suoi dati oppure se si verifica qualche altro problema.wall.jsp
– la pagina nella quale visualizzeremo i dati di un contatto e la lista degli ultimi post presenti.
Servlet
FacebookServlet
– gestisce l'integrazione con il servizio Facebook. Intercetta l'access token ricevuto da Facebook necessario per le successive invocazioni al servizio e lo carica nella sessione utente.BroadcastWriteServlet
– gestisce la pubblicazione di un messaggio sulla bacheca di un gruppo di contatti.WallServlet
– acquisisce da Facebook le informazioni di un contatto e gli ultimi post presenti sulla sua bacheca.WriteServlet
– gestisce la pubblicazione di un messaggio sulla bacheca di un contatto.
configurazione delle dipendenze
Per compilare ed eseguire il progetto, occorre aggiungere al classpath le seguenti librerie:
- activation-1.1.jar
- commons-lang-2.2.jar
- commons-logging-1.1.1.jar
- jaxb-api-2.1.jar
- jaxb-impl-2.1.9.jar
- json-20080701.jar
- jsr173_1.0_api.jar
- restfb-1.6.6.jar
- runtime-0.4.1.5.jar
- stax-api-1.0-2.jar
- visural-common-0.4.3.jar
Il file web.xml
Nel file web.xml, definiamo le quattro Servlet ed il loro corrispondente mapping.
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
id="WebApp_ID" version="2.5">
<display-name>FacebookIntegration</display-name>
<servlet>
<servlet-name>FacebookServlet</servlet-name>
<servlet-class>it.html.servlet.facebook.FacebookServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>FacebookServlet</servlet-name>
<url-pattern>/facebook</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>WriteServlet</servlet-name>
<servlet-class>it.html.servlet.facebook.WriteServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>WriteServlet</servlet-name>
<url-pattern>/write</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>BroadcastWriteServlet</servlet-name>
<servlet-class>it.html.servlet.facebook.BroadcastWriteServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>BroadcastWriteServlet</servlet-name>
<url-pattern>/broadcastWrite</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>WallServlet</servlet-name>
<servlet-class>it.html.servlet.facebook.WallServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>WallServlet</servlet-name>
<url-pattern>/wall</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
</web-app>
la pagina principale: index.jsp
Nella pagina index.jsp
verifichiamo se in sessione c'è un oggetto “user”. Se non è presente presentiamo l'url di login su Facebook.
L'indirizzo di redirezione accodato all'url di autenticazione al servizio punta, naturalmente, alla nostra FacebookServlet
che verrà automaticamente invocata da Facebook:
se l'utente risulta connesso (user presente nella sessione) stampiamo alcuni dati (data di nascita, email e sito web) e l'elenco di tutti i suo contatti.
Nella pagina inseriamo un form che permette di inviare un messaggio ai contatti selezionati. Nel form inseriamo una textarea e per ciascun contatto, inseriamo un campo checkbox, così da fornire un criterio di scelta semplice ma funzionale.
Abbiamo infine inserito nella pagina anche la stampa di un messaggio, qualora presente nella request.
A ciascun contatto corrisponde un link (nella forma wall?userId=XXXX
) che permette di accedere ai dati del contatto e agli ultimi messaggi pubblicati in bacheca.
<h1>Facebook Integration</h1>
<%
User user = (User)request.getSession().getAttribute("user");
if (user != null) {
%>
<h2><%= user.getName() %></h2>
<ul>
<li>Data di nascita: <%= user.getBirthday() %></li>
<li>Email: <%= user.getEmail() %></li>
<li>Sito Web: <%= user.getWebsite() %></li>
</ul>
<%
String message = (String)request.getAttribute("message");
if (message != null && message.length() > 0) {
%><h2 style="color: red"><%= message %></h2><%
}
%>
<h3>I miei amici</h3>
<form action="broadcastWrite" method="post">
<textarea rows="10" cols="100" name="message">Scrivi qui il messaggio da inviare a tutti i tuoi amici</textarea>
<br /><br />
<input type="submit" value="Invia messaggio a tutti" />
<br /><br />
<%
List<User> users = (List<User>)request.getSession().getAttribute("contacts");
for (int i = 0; i < users.size(); i++) {
User u = users.get(i);
%>
<input type="checkbox" value="S" name="<%= u.getId() %>">
<a href="wall?userId=<%=u.getId()%>"><%= u.getName() %></a>
<br />
<%
}
%>
</form>
<%
} else {
String redirectUri = "http://localhost:8080/FacebookIntegration/facebook";
String url = "https://www.facebook.com/dialog/oauth?client_id=" + ApplicationProperty.FACEBOOK_CLIENT_ID + "&redirect_uri=" + redirectUri + "&scope=email,read_stream";
%>
<h2>Non sei autenticato a Facebook</h2>
<a href="<%=url%>">Facebook Login</a>
<%
}
%>
Le servlet
La servlet FacebookServlet
Nella FacebookServlet
verifichiamo la presenza del parametro code. Se non è presente vuol dire che si è verificato qualche errore quindi riportiamo l'utente nella pagina di errore. La presenza dei parametri error_reason
, error
ed error_description
indicherà la mancata autorizzazione da parte dell'utente.
Se il parametro code è presente richiediamo a Facebook l'access token, altrimenti rediregiamo l'utente nella pagina di errore.
Per ottenere l'access token effettuiamo una chiamata http al seguente URL:
https://graph.facebook.com/oauth/access_token?client_id=" + this.id + "&redirect_uri=" + redirectUri + "&client_secret=" + secretKey + "&code=" + code
Facebook ci restituirà una stringa di testo nella quale troviamo l'access token ed il parametro expires, che ci permette di capire se il token è scaduto o meno. Per leggere i due parametri occorre interpretare la stringa di testo. La stringa segue la sintassi dei parametri GET
: coppia chiave=valore e carattere "&" per separare i parametri.
Se l'access token è valido, invochiamo nuovamente Facebook utilizzando la libreria RestFB, per ottenere i dati dell'utente.
L'access token viene memorizzato nella sessione utente così tutte le Servlet che devono interrogare il servizio, potranno utilizzarlo.
Se tutto va a buon fine, otteniamo l'oggetto User che viene caricato in sessione e l'utente viene rediretto alla pagina index.jsp
dove verranno stampati i dati. Viene richiesto anche l'elenco di tutti i contatti dell'utente, anch'essi caricati in sessione.
package it.html.servlet.facebook;
import it.html.ApplicationProperty;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.List;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.json.JSONObject;
import com.restfb.Connection;
import com.restfb.DefaultFacebookClient;
import com.restfb.FacebookClient;
import com.restfb.Parameter;
import com.restfb.types.User;
import com.visural.common.IOUtil;
public class FacebookServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
String redirectUri = "http://localhost:8080/FacebookIntegration/facebook";
String[] perms = new String[] {"publish_stream", "email"};
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String code = request.getParameter("code");
if (code != null) {
String red = "https://graph.facebook.com/oauth/access_token?client_id=" + ApplicationProperty.FACEBOOK_CLIENT_ID + "&redirect_uri=" + redirectUri + "&client_secret=" + ApplicationProperty.FACEBOOK_SECURE_KEY + "&code=" + code;
System.out.println(red);
URL url = new URL(red);
String accessToken = null;
Integer expires = null;
try {
String result = readURL(url);
String[] pairs = result.split("&");
for (String pair : pairs) {
String[] kv = pair.split("=");
if (kv.length != 2) {
throw new RuntimeException("Unexpected auth response");
} else {
if (kv[0].equals("access_token")) {
accessToken = kv[1];
}
if (kv[0].equals("expires")) {
expires = Integer.valueOf(kv[1]);
}
}
}
} catch (IOException e) {
throw new RuntimeException(e);
}
request.getSession().setAttribute("accessToken", accessToken);
FacebookClient facebookClient = new DefaultFacebookClient(accessToken);
User user = facebookClient.fetchObject("luca.santaniello", User.class, Parameter.with("fields", "id, name, birthday, website, email"));
request.getSession().setAttribute("user", user);
Connection<User> myFriends = facebookClient.fetchConnection("me/friends", User.class, Parameter.with("fields", "id, name"));
List<User> users = myFriends.getData();
request.getSession().setAttribute("contacts", users);
getServletConfig().getServletContext().getRequestDispatcher("/index.jsp").forward(request,response);
} else {
String errorReason = request.getParameter("error_reason");
if (errorReason != null)
request.setAttribute("messaggio", errorReason);
else
request.setAttribute("messaggio", "Code non presente");
getServletConfig().getServletContext().getRequestDispatcher("/error").forward(request,response);
}
}
private String readURL(URL url) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
InputStream is = url.openStream();
int r;
while ((r = is.read()) != -1) {
baos.write(r);
}
return new String(baos.toByteArray());
}
}
// questa classe consente di ricavare (e stampare a video) le informazioni specifiche per un utente in particolare:
// per comodità utilizziamo il formato json
class UserService {
public void authFacebookLogin(String accessToken, int expires) {
try {
JSONObject resp = new JSONObject(IOUtil.urlToString(new URL("https://graph.facebook.com/me?access_token=" + accessToken)));
String id = resp.getString("id");
String firstName = resp.getString("first_name");
String lastName = resp.getString("last_name");
String email = resp.getString("email");
System.out.println(firstName + " " + lastName);
} catch (Throwable ex) {
throw new RuntimeException("failed login", ex);
}
}
}
La servlet WriteServlet
La WriteServlet
riceve in ingresso il messaggio e l'id del contatto al quale si desidera inviarlo. Il messaggio viene pubblicato mediante il metodo publish
, e al termine del ciclo viene inserito nella request un messaggio di conferma che verrà stampato nella pagina wall.jsp
.
public class WriteServlet extends HttpServlet {
...
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String userId = request.getParameter("userId");
String message = (String)request.getParameter("message");
String accessToken = (String)request.getSession().getAttribute("accessToken");
FacebookClient facebookClient = new DefaultFacebookClient(accessToken);
FacebookType publishMessageResponse = facebookClient.publish(userId + "/feed", FacebookType.class, Parameter.with("message", message));
request.setAttribute("message", "Il Messaggio è stato pubblicato sulla bacheca");
getServletConfig().getServletContext().getRequestDispatcher("/wall?userId=" + userId).forward(request,response);
}
}
La servlet BroadcastWriteServlet
La BroadcastWriteServlet
riceve in ingresso il messaggio. Viene richiesta nuovamente a Facebook la lista dei contatti. Se il campo check relativo al contatto ha valore “S” il messaggio viene pubblicato sula bacheca utilizzando il metodo publish. Al termine del ciclo viene inserito nella request un messaggio di conferma che verrà stampato nella pagina index.jsp.
public class BroadcastWriteServlet extends HttpServlet {
...
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String accessToken = (String)request.getSession().getAttribute("accessToken");
FacebookClient facebookClient = new DefaultFacebookClient(accessToken);
Connection<User> myFriends = facebookClient.fetchConnection("me/friends", User.class, Parameter.with("fields", "id"));
List<User> users = myFriends.getData();
String message = (String)request.getParameter("message");
for (User user : users) {
String check = request.getParameter(user.getId());
if (check != null && check.equalsIgnoreCase("S")) {
FacebookType publishMessageResponse = facebookClient.publish(user.getId() + "/feed", FacebookType.class, Parameter.with("message", message));
System.out.println(user.getId());
}
request.setAttribute("message", "Il Messaggio è stato pubblicato sulla bacheca di tutti i contatti selezionati");
}
getServletConfig().getServletContext().getRequestDispatcher("/index.jsp").forward(request,response);
}
}
La servlet WallServlet
La Wallservlet
riceve in ingresso l'id dell'utente. Mediante il metodo fetchObject(userId...)
vengono acquisite le informazioni dell'utente, mediante il metodo fetchConnection(userId...)
vengono letti gli ultimi post pubblicati in bacheca. Entrambi i dati vengono caricati nella request per renderli visibili alla wall.jsp
.
public class WallServlet extends HttpServlet {
...
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String userId = request.getParameter("userId");
String accessToken = (String)request.getSession().getAttribute("accessToken");
FacebookClient facebookClient = new DefaultFacebookClient(accessToken);
User user = facebookClient.fetchObject(userId, User.class, Parameter.with("fields", "id, name, birthday, website, email"));
Connection&StatusMessage& myFeed = facebookClient.fetchConnection(userId + "/feed", StatusMessage.class);
request.setAttribute("user", user);
request.setAttribute("posts", myFeed.getData());
getServletConfig().getServletContext().getRequestDispatcher("/wall.jsp").forward(request,response);
}
}
Per eseguire l'applicazione occorre impostare i due parametri fondamentali che avete ricevuto durante la registrazione dell'applicazione sul sito Facebook: client id e secret id.
Entrambi i valori di questi parametri sono stati inseriti nella classe ApplicationProperty.
public class ApplicationProperty {
public final static String FACEBOOK_CLIENT_ID = "XXXX";
public final static String FACEBOOK_SECURE_KEY = "YYYY";
}