构建支持 OAuth 的 Web Twitter 客户端

红薯 发布于 2010/05/21 16:05
阅读 1K+
收藏 1

在阅读本文前,您应该了解 OAuth 为使用者站点访问存储在服务提供商上的用户的受保 护资源提供了一种更好的方式。因为使用 OAuth,永远不会将用户凭证泄露给除拥有用户数据的站点之外的其他站点。随着 第 1 部分中桌面 Twitter 客户端的开发,在授予了对存储在 Twitter 上的数据的MyTtDesktopClient 访问权限之后,您就可以更新 Twitter 状态了。如果您返回到 Twitter,则会获得更好的用户体验,因为访问令牌可以被重用。

本文展示了如何使用 OAuth 开发 Web Twitter 客户端。目标是提供一个 Web 应用程序,该应用程序应支持用户通过 OAuth 对 Twitter 进行身份验证并更新其状态。它还能够显示好友的时间轴。该应用程序是优秀混搭站点的基础。

Twitter Web 客户端的开发

MyTtDesktopClient 类似,该 Web Twitter 客户端将支持用户更新其 Twitter 状态。此外,它将显示用户的最新状态并支持他们删除其状态。该 Web 应用程序还支持您设置回调 URL,在成功进行了 OAuth 身份验证后,Twitter 将重定向到此 URL。从开发目的来说,这很有用,因为我们可以将浏览器重定向到 localhost。 您还会注意到,将此应用程序注册为 Browser 应用程序和将其注册为桌面应用程序的 OAuth 身份验证略有不同。

在 Twitter 上注册您的 Web 应用程序 - MyTtWebClient

再次说明,在 Twitter 上使用 OAuth 之前,您需要在 http://twitter.com/oauth_clients 注册 Web 应用程序。 或者,如果您愿意,您可以修改已为桌面 Twitter 客户端注册的应用程序。我将按照以下方式在 Twitter 上注册一个新的应用程序。

  • Application Name:MyTtWebClient
  • Description:使用 OAuth 的 Web Twitter 客户端
  • Application Web site:在此处输入您的应用程序主页
  • Application Type:Browser - 我们将开发一个 Web 应用程序
  • Callback URL:在 此处输入您的应用程序的回调 URL,localhost 将不起作用
  • Default Access type:Read & Write - 我们想要授予用户写入权限
  • Use Twitter for login:Yes - 我们不打算使用 Twitter 进行身份验证

注意,与第 1 部分不同,此次应用程序类型为 Browser 而不是 Client。此外,切记授 予 Read & Write 权限,并选中 Yes, user Twitter for login 复选框。

成功注册了应用程序后,您将获得一个使用者密钥使用者机密 和 3 个 URL(请求令牌 URL访问令牌 URL授权 URL)。使用者密钥使用者机密 将在 WEB-INF/web.xml 中设置。

开发和测试 MyTtWebClient

如果您不想阅读代码(源代码如后面的 下载 部分所示),而是希望直接编译并运行 Web 应用程序,则应该在 web.xml 中设置自己的使用者密钥和使用者机密。在 WEB-INF/lib 下添加以下库文件:

  • commons-logging-1.1.1.jar
  • log4j-1.2.15.jar
  • twitter4j-2.0.9.jar

在这里我想提醒您使用 Twitter4J 2.0.9 以上的版本,因为只有 Twitter4J 2.0.9 以上的版本才支持回调 URL 自定义。默认情况下,在指向 localhostweb.xml 中配置回调 URL。(Twitter4J 是 TwitterAPI 的一个开源 Java™ 库,参见 参考资 料。) 编译了应用程序并将其组装为 MyTtWebClient.war 后,在 Tomcat 下部署它并在 http://localhost:8080/MyTtWebClient 上尝试一下。

MyTtWebClient 的核心是称为 MyTwitterServlet(完整源代 码,请参见下面的 下载 部分)的 servlet。此 servlet 在类 myttwebclient.MyTwitterServlet 中定义。doPost(HttpServletRequest request、HttpServletResponse response) 方法负责进行 OAuth 身份验证,更新和删除用户的 Twitter 状态,以及显示好友的时间轴。

doPost(...) 方法使用 login_twitter.htmlupdate_twitter_status.jsp 处理 OAuth 身份验证和不同的交互。此应用程序的欢迎页面(login_twitter.html)提供了一个供用户 登录 Twitter 的按钮,参见 图 1。 当用户单击此按钮时,将会调用 doPost(...) 方法。有两种情况:新用户返 回的用户。如 清单 1 所示, 代码试图从 cookie 加载用户的 Twitter ID。如果没有加载 cookie,则用户将被视为新用户。 将会从 Twitter 请求请求令牌,然后用户将被重定向到 Twitter,以授予代码读/写用户 Twitter 数据的权限,参见 图 2。 如果一切都进行得很顺利,Twitter 应将用户重定向到在 web.xml 中设置的回调 URL。在这里我将回调 URL 设置为指向 servlet MyTtServlet。此次已授权了请求令牌并且它可以与 Twitter 交换以获得访问令牌。获得了访问令牌后,该访问令牌将会存储在 WEB-INF/token.txt 文件中以便将来使用。在实际应用中,您可能会在数据库中保存访问令牌。此外,cookie 将存储在用户的浏览器中,以便我们下次检查该用户是否是返 回的用户。用户现在将被重定向到 update_twitter_status.jsp。成功登录页面如 图 3 所示,它显示了用户的上一次更新,以及其好友的一些时间轴。


清单 1. 使用 Twitter 进行 OAuth 身份验证

				
protected void doPost(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
HttpSession session = request.getSession();

// Call back from Twitter
String oauthToken = request.getParameter(PARAM_OAUTH_TOKEN);
if (oauthToken != null) {
logger.debug(PARAM_OAUTH_TOKEN + " received from Twitter");
try {
Twitter twitter = (Twitter) session.getAttribute(ATTR_TWITTER);
RequestToken requestToken = (RequestToken) session
.getAttribute(ATTR_REQUEST_TOKEN);
AccessToken accessToken;
if (callbackUrl == null) {
accessToken = twitter.getOAuthAccessToken(requestToken);
} else {
String oauthVerifier = request
.getParameter(PARAM_OAUTH_VERIFIER);
logger.debug(PARAM_OAUTH_VERIFIER
+ " received from Twitter");
accessToken = twitter.getOAuthAccessToken(requestToken
.getToken(), requestToken.getTokenSecret(),
oauthVerifier);
}
twitter.setOAuthAccessToken(accessToken);
session.removeAttribute(ATTR_REQUEST_TOKEN);
session.setAttribute(ATTR_TWITTER, twitter);

int id = twitter.verifyCredentials().getId();
logger.debug("Access token retrieved for user " + id
+ " from Twitter");
storeAccessToken(id, accessToken);
Cookie cookie = new Cookie(COOKIE_TWITTER_ID, "" + id);
cookie.setMaxAge(63072000); // Valid for 2 years
response.addCookie(cookie);
logger.debug("Cookie set for user " + id);

// Get last status and friends' timelines
getMyLastStatusAndStoreInSession(session);
getFriendsTimelinesAndStoreInSession(session);

// Go to the update status page
request.getRequestDispatcher(PAGE_UPDATE_STATUS).forward(
request, response);
} catch (TwitterException e) {
logger.error("Failed to retrieve access token - "
+ e.getMessage());
throw new ServletException(e);
}
}

// Actions within this application
String action = request.getParameter(PARAM_ACTION);
if (ACTION_SIGN_IN.equals(action)) {
logger.debug("Signing in with Twitter...");
Twitter twitter = new Twitter();
twitter.setOAuthConsumer(consumerKey, consumerSecret);

// Try to load Twitter ID from cookies
String id = null;
Cookie[] cookies = request.getCookies();
if (cookies != null) {
Cookie cookie;
for (int i = 0; i < cookies.length; i++) {
cookie = cookies[i];
if (COOKIE_TWITTER_ID.equals(cookie.getName())) {
id = cookie.getValue();
}
}
}

// Try to load access token if user's Twitter ID is retrieved
AccessToken accessToken = null;
if (id != null) {
accessToken = loadAccessToken(id);
if (accessToken != null) {
twitter.setOAuthAccessToken(accessToken);
session.setAttribute(ATTR_TWITTER, twitter);

// Get last status and friends' timelines
try {
getMyLastStatusAndStoreInSession(session);
getFriendsTimelinesAndStoreInSession(
session, true);
} catch (TwitterException e) {
e.printStackTrace();
}

// Access token loaded, go the up update status page
logger.debug("Going to the status update page...");
request.getRequestDispatcher(PAGE_UPDATE_STATUS).forward(
request, response);
}
}

// Can not load access token, go to Twitter for authentication
if (accessToken == null) {
try {
RequestToken requestToken;
if (callbackUrl == null) {
requestToken = twitter.getOAuthRequestToken();
} else {
requestToken =
twitter.getOAuthRequestToken(callbackUrl);
}
String authorisationUrl = requestToken
.getAuthorizationURL();
session.setAttribute(ATTR_TWITTER, twitter);
session.setAttribute(ATTR_REQUEST_TOKEN, requestToken);

logger.debug("Redirecting user to " + authorisationUrl);
response.sendRedirect(authorisationUrl);
} catch (TwitterException e) {
logger.error("Sign in with Twitter failed - "
+ e.getMessage());
throw new ServletException(e);
}
}

} else if (ACTION_UPDATE.equals(action)) {
// Handle ACTION_UPDATE, ACTION_DELETE, ACTION_MORE and ACTION_LATEST
......
}

 

如果在用户单击了登录按钮后,MyTwitterServlet 可以加载用户的 cookie,则该用户将被视为返回的用户。如 清单 1 所示,可以从 token.txt 获得用户的访问令牌。因此,没有必要联系 Twitter 以对此用户再次进行身份验证。在从 Twitter 获得了用户的上一次状态及其一些好友时间轴后,该用户将被重定向到 updatee_twitter_status.jsp, 参见 图 3

如果您已经尝试了本系列文章 第 1 部分 中的 MyTtDesktopClient,您可能记得,在获得了访问 Twitter 数据的权限后,您必须输入从 Twitter 返回的 PIN。因为这次我们可以使用 servlet 中的重定向,并且在注册过程中选择了 Browser 应用程序类型,所以能够获得更好的用户体验。


图 1. MyTtWebClient 的欢迎页面
Web 页面的屏幕截图如下所示:标题是 My Twitter Web client;文本是 This web  site allows you to update your Twitter status without touching your  Twitter password. This is done through OAuth.;然后是一个名为 Sign in with  Twitter 的按钮

图 2. 授予对 MyTtWebClient 的访问权限
页面的屏幕截图,页面上有一个覆盖部分,询问用户是接受还是拒绝对 MyTtWebClient 的访问并更新  Twitter 上的数据。需要输入用户名和密码。

图 3. 进行了 OAuth 身份验证之后的 MyTtWebClient
My Twitter Web  客户端页面的屏幕截图,显示了用户的上一次更新、添加新更新的文本字段,以及好友状态列表。

doPost(...) 方法的其他内容如 清单 2 所示。此部分代码响应 图 3 显示的 3 个按钮和用户上次更新末部的删除链接。有了这 3 个按钮和 1 个删除链接,用户可以删除其上一次更新,更新其状态并查看好友的时间轴。可对此应用程序应用 Ajax (例如,使用 GWT)以改进代码,从而改善用户体验。我将此作为您的家庭作业。图 4 显示了成功进行状态更新之后的屏幕截图。


清单 2. myttwebclient.MyTwitterServlet 支持的操作

				
protected void doPost(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
HttpSession session = request.getSession();

// OAuth authentication support
......

} else if (ACTION_UPDATE.equals(action)) {
logger.debug("Updating Twitter status...");
String status = request.getParameter(PARAM_STATUS);
Twitter twitter = (Twitter) session.getAttribute(ATTR_TWITTER);
try {
Status twitterStatus = twitter.updateStatus(status);
logger.debug("Successfully updated the status to ["
+ twitterStatus.getText() + "].");
// Update last status
session.setAttribute(ATTR_LAST_UPDATED_STATUS, twitterStatus
.getText()
+ " " + twitterStatus.getCreatedAt());
session.setAttribute(ATTR_LAST_UPDATED_STATUS_ID, ""
+ twitterStatus.getId());

// Update friends' timelines
getFriendsTimelinesAndStoreInSession(session, true);

// Stay in the update status page
logger.debug("Staying in the status update page...");
request.getRequestDispatcher(PAGE_UPDATE_STATUS).forward(
request, response);
} catch (TwitterException e) {
logger.error("Failed to update Twitter status - "
+ e.getMessage());
throw new ServletException(e);
}
} else if (ACTION_DELETE.equals(action)) {
logger.debug("Deleting Twitter status...");
String strId = (String) session
.getAttribute(ATTR_LAST_UPDATED_STATUS_ID);
if (strId != null && strId != "") {
Twitter twitter = (Twitter) session.getAttribute(ATTR_TWITTER);
try {
int id = twitter.verifyCredentials().getId();
twitter.destroyStatus(Long.parseLong(strId));
session.removeAttribute(ATTR_LAST_UPDATED_STATUS);
session.removeAttribute(ATTR_LAST_UPDATED_STATUS_ID);
logger.debug("Last update deleted for Twitter user " + id
+ " deleted, session record removed");

// Get last status and friends' timelines
getMyLastStatusAndStoreInSession(session);
getFriendsTimelinesAndStoreInSession(session, true);
} catch (TwitterException e) {
logger.error("Failed to delete Twitter status - "
+ e.getMessage());
throw new ServletException(e);
}
}

// Stay in the update status page
logger.debug("Staying in the status update page...");
request.getRequestDispatcher(PAGE_UPDATE_STATUS).forward(request,
response);
} else if (ACTION_MORE.equals(action)) {
// Update friends' timelines
getFriendsTimelinesAndStoreInSession(session);

// Stay in the update status page
logger.debug("Staying in the status update page...");
request.getRequestDispatcher(PAGE_UPDATE_STATUS).forward(request,
response);
} else if (ACTION_LATEST.equals(action)) {
// Update friends' latest timelines
getFriendsTimelinesAndStoreInSession(session, true);

// Stay in the update status page
logger.debug("Staying in the status update page...");
request.getRequestDispatcher(PAGE_UPDATE_STATUS).forward(request,
response);
}
}



图 4. MyTtWebClient 更新的 Twitter 状态
My Twitter Web 客户端页面的屏幕截图,显示了用户的最新状态实际上已进行了更新。

我假设现在您已完全了解 MyTtWebClient。如果您想将其部署到 Web 服务器,可能就不再需要回调支持。要禁用回调支持,只需将 WEB-INF/web.xml 中的参数 callbackUrl 设置为一个空字符串即可。但是,记住,您必须为 Twitter 设置正确的回调 URL,以便在成功进行了 OAuth 身份验证后,Twitter 知道应该将用户重定向到哪里。

在您的 Web 应用程序投入使用之前,您可能会意识到应用于 API 调用的 Twitter 限制。例如,REST API 调用的默认速率限制为每小时 150 个请求。幸运的是,Twitter 允许您应用每小时 20,000 个请求。更多信息,请参见 Twitter FAQ。

结束语

在本系列的第 2 部分中,我介绍了如何开发 Twitter Web 客户端 MyTtWebClient。该 Web 应用程序演示了 OAuth 如何与 Web 浏览器进行无缝协作。与 MyTtDesktopClient 相比,我还为 MyTtWebClient 添加了更多功能,包括删除上一次更新和显示好友的时间轴。在第 3 部分,我将演示如何将此 Web Twitter 客户端迁移到 Google App Engine 中。

下载本文源码

加载中
0
程开源
程开源

还是少谈如何翻墙到twitter

返回顶部
顶部