在Server-Side如何取得AccessToken?
前言
先前介紹過Facebook的Authentication機制後,我們知道「一個應用程式要存取臉書上使用者基本資料以外的資源,就必須要先讓他認證,並取得一把授權鑰匙去操作Graph API。」,這把鑰匙就是AccessToken。但會因為不同種類的應用程式而使用不同授權方式,以一個WebApp來說,主要分為Client-Side與Server-Side的認證。之前有介紹過大家5分鐘建立一個臉書應用程式,裡面使用到Javascript SDK幫你把Client-Side的認證授權流程都包裝在FB.login中。所以我不再另外介紹Client-Side該怎麼做,更多的內容可以參考Document。在這篇文章中要教你的,是透過Java做Server-Side認證去取得AccessToken。
Server-Side認證流程
首先看看Server-Side認證流程圖,來建立基礎概念:(取自Facebook Developers)
|
實做
我在2010年實作國軍登出倒數計時器時,開發臉書應用程式的文件並不夠完善。加上鮮少有人使用Java去開發臉書應用程式,網路上能找到的資料大都採用PHP開發,所以大都靠自己慢慢嘗試完成。隨著臉書教學文件原來越多,現在也有聽過某些遊戲是用Flash(Client)+Java(Server)實做。接下來讓我分享給大家: Server-Side認證授權的實做。在應用程式整個認證流程共包含四個頁面: index.jsp、authentication.jsp、error.jsp與working.jsp。
|
index.jsp
很純粹的判斷session內是否有AccessToken去決定要導向authentication.jsp還是working.jsp。
<%@page import="org.tonylin.practice.facebook.web.SessionKeyEnum"%> <% if( session.getAttribute(SessionKeyEnum.ACCESS_TOKEN) == null ){ response.sendRedirect("authentication.jsp"); } else { response.sendRedirect("working.jsp"); } %>
authentication.jsp
首先會先確認是否有參數error_description傳入,主要是用來處理使用者拒絕授權的情況。當使用者拒絕授權後,會導向你所設定的REDIRECT_URI,並帶下面的參數,可依照需求選擇要拿哪一個參數來判斷。
YOUR_REDIRECT_URI? error_reason=user_denied &error=access_denied &error_description=The+user+denied+your+request. &state=YOUR_STATE_VALUE接著我把主要認證流程分成兩部分並實作於FacebookAuthUtil類別:
- 請求使用者授權: 在呼叫requestAuthentication後,會先跳出授權的對話盒讓使用者授權。接著會帶著code參數導向傳入的REDIRECT_URI,也就是authentication.jsp。你也可以選擇將這些步驟做成不同的jsp或servlet。
- 使用code交換AccessToken: 呼叫getAccessToken去取得AccessToken,最後存入session並導向至應用程式首頁。這裡我偷懶直接使用Facebook App的URL。
<%@page import="org.tonylin.practice.facebook.web.SessionKeyEnum"%> <%@page import="org.tonylin.util.web.ServerInfoUtil"%> <%@page import="org.tonylin.practice.facebook.web.FacebookAuthUtil"%> <%@page import="org.tonylin.practice.facebook.FacebookConfigProvider"%> <% String error_description = request.getParameter("error_description"); if( null != error_description ){ response.sendRedirect("error.jsp?msg=" + error_description); } else { String code = request.getParameter("code"); String appID = FacebookConfigProvider.getAppID(); String appSecret = FacebookConfigProvider.getAppSecret(); String appPermission = FacebookConfigProvider.getAppPermission(); String currentURL = ServerInfoUtil.getRelatedURL(request, "authentication.jsp"); if( null == code ){ FacebookAuthUtil.requestAuthentication( appID, currentURL, appPermission, request, response); } else { try { String accessToken = FacebookAuthUtil.getAccessToken( appID, appSecret, currentURL, request, response); session.setAttribute( SessionKeyEnum.ACCESS_TOKEN, accessToken); response.sendRedirect("http://apps.facebook.com/testingapfortony/"); } catch(Exception e){ response.sendRedirect("error.jsp?msg=" + e.getMessage()); } } } %>
- 註: ServerInfoUtil是我用來處理與WebServer相關的Utility。
FacebookAuthUtil.java
requestAuthentication
這method的目的就是要取得使用者授權,基本上就是照著臉書所需要的資訊照做。需特別注意的的有幾點:
- state: 用來防止Cross-site Request Forgery攻擊。這裡我是透過隨機產生一個數字並存入session中,在getAccessToken用來確認是否為同一個使用者存取的操作。
- callBackURL: 用來讓臉書攜帶code回來以交換AccessToken。
- response的javascript: 一般臉書應用程式都是使用Canvas的做法,這段javascript是確保將你整個頁面導向認證畫面而不是只有iframe內容。
public static void requestAuthentication(String appId, String callBackURL, String aPermission, HttpServletRequest request, HttpServletResponse response) throws IOException { String state = String.valueOf(RandomUtils.nextLong()); request.getSession().setAttribute(SessionKeyEnum.STATE, state); StringBuffer authURLSB = new StringBuffer( "https://www.facebook.com/dialog/oauth?client_id="); authURLSB.append(appId); authURLSB.append("&redirect_uri="); authURLSB.append(callBackURL); authURLSB.append("&scope="); authURLSB.append(aPermission); authURLSB.append("&state="); authURLSB.append(state); PrintWriter pw = response.getWriter(); pw.println("<html>"); pw.println("<script type=\"text/javascript\">"); pw.println("window.open ('" + authURLSB.toString() + "','_top')"); pw.println("</script>"); pw.println("</html>"); }
getAccessToken
這method的目的就是取得AccessToken。首先會確認state是否與requestAuthentication中的相同,並確認有沒有任何錯誤訊息,如果有問題都會丟FacebookAuthException給caller處理。否則就會使用HttpClient去請求authURLSB這個url以交換AccessToken回來(callBackURL必須與之前的相同)。透過HttpClient請求後的Response會像下面的格式,我只取出=之後的內容回傳使用。
access_token=xxxxxxxxxxxxxx
public static String getAccessToken(String appId, String appSecret, String callBackURL, HttpServletRequest request, HttpServletResponse response) throws FacebookAuthException { String state = request.getParameter("state"); String error_description = request.getParameter("error_description"); String previousState = (String) request.getSession().getAttribute( SessionKeyEnum.STATE); if (error_description != null) { throw new FacebookAuthException("You need to allow the permission."); } else if (state == null || previousState == null || !state.equals(previousState)) { throw new FacebookAuthException("State is incorrect."); } else { String code = request.getParameter("code"); StringBuffer authURLSB = new StringBuffer( "https://graph.facebook.com/oauth/access_token?client_id="); authURLSB.append(appId); authURLSB.append("&redirect_uri="); authURLSB.append(callBackURL); authURLSB.append("&client_secret="); authURLSB.append(appSecret); authURLSB.append("&code="); authURLSB.append(code); try { String content = URLConnectionUtil.getWebContent(authURLSB .toString()); String accessToken = content.split("=")[1]; return accessToken; } catch (Exception e) { throw new FacebookAuthException("Get AccessToken failed", e); } } }* 註: URLConnectionUtil是我用來處理與HttpClient相關的Utility,getWebContent就只是建立與URL的連線並讀出裡面的內容。
Summary
這篇教學中,已將精隨交給大家。我知道裡面還隱藏了一些實作細節,但欲知詳情…嘿嘿嘿。另外,我試圖將這部份程式碼做的可以重複使用,FacebookAuthUtil就是為了達到這個目的。至於能不能只呼叫一個method就取得AccessToken呢? 目前我只有Client-Side有能力做到而已。隨著你的臉書應用程式越多,隨著臉書的改變,如果你能改一隻程式就套到每一個應用程式上,何樂而不為?
認證流程除了文章中所提到的外,還要注意AccessToken是否有過期,之後會再撰文告訴大家。(偷偷告訴大家我是用Struts2的interceptor去處理的)
友藏內心獨白: 這樣寫會不會讓人太難懂?
Reference