OAuth 简介

OAuth 是由 Blaine Cook、Chris Messina、Larry Halff 及 David Recordon 共同发起的,目的在于为 API 访问授权提供一个安全、开放的标准。

基于 OAuth 认证授权具有以下特点:

  • 安全。OAuth 与别的授权方式不同之处在于:OAuth 的授权不会使消费方(Consumer)触及到用户的帐号信息(如用户名与密码),也是是说,消费方无需使用用户的用户名与密码就可以申请获得该用户资源的授权。
  • 开放。任何消费方都可以使用 OAuth 认证服务,任何服务提供方 (Service Provider) 都可以实现自身的 OAuth 认证服务。
  • 简单。不管是消费方还是服务提供方,都很容易于理解与使用。

OAuth 的解决方案如下图所示。

图 1. OAuth Solution
 

如图 1 所示 OAuth 解决方案中用户、消费方及其服务提供方之间的三角关系:当用户需要 Consumer 为其提供某种服务时,该服务涉及到需要从服务提供方那里获取该用户的保护资源。OAuth 保证:只有在用户显式授权的情况下(步骤 4),消费方才可以获取该用户的资源,并用来服务于该用户。

从宏观层次来看,OAuth 按以下方式工作:

  1. 消费方与不同的服务提供方建立了关系。
  2. 消费方共享一个密码短语或者是公钥给服务提供方,服务提供方使用该公钥来确认消费方的身份。
  3. 消费方根据服务提供方将用户重定向到登录页面。
  4. 该用户登录后告诉服务提供方该消费方访问他的保护资源是没问题的。

回页首

OAuth 认证授权流程

在了解 OAuth 认证流程之前,我们先来了解一下 OAuth 协议的一些基本术语定义:

  • Consumer Key:消费方对于服务提供方的身份唯一标识。
  • Consumer Secret:用来确认消费方对于 Consumer Key 的拥有关系。
  • Request Token:获得用户授权的请求令牌,用于交换 Access Token。
  • Access Token:用于获得用户在服务提供方的受保护资源。
  • Token Secret:用来确认消费方对于令牌(Request Token 和 Access Token)的拥有关系。

图 2. OAuth 授权流程(摘自 OAuth 规范)
 

对于图 2 具体每一执行步骤,解释如下:

  • 消费方向 OAuth 服务提供方请求未授权的 Request Token。
  • OAuth 服务提供方在验证了消费方的合法请求后,向其颁发未经用户授权的 Request Token 及其相对应的 Token Secret。
  • 消费方使用得到的 Request Token,通过 URL 引导用户到服务提供方那里,这一步应该是浏览器的行为。接下来,用户可以通过输入在服务提供方的用户名 / 密码信息,授权该请求。一旦授权成功,转到下一步。
  • 服务提供方通过 URL 引导用户重新回到消费方那里,这一步也是浏览器的行为。
  • 在获得授权的 Request Token 后,消费方使用授权的 Request Token 从服务提供方那里换取 Access Token。
  • OAuth 服务提供方同意消费方的请求,并向其颁发 Access Token 及其对应的 Token Secret。
  • 消费方使用上一步返回的 Access Token 访问用户授权的资源。

总的来讲,在 OAuth 的技术体系里,服务提供方需要提供如下基本的功能:

  • 第 1、实现三个 Service endpoints,即:提供用于获取未授权的 Request Token 服务地址,获取用户授权的 Request Token 服务地址,以及使用授权的 Request Token 换取 Access Token 的服务地址。
  • 第 2、提供基于 Form 的用户认证,以便于用户可以登录服务提供方做出授权。
  • 第 3、授权的管理,比如用户可以在任何时候撤销已经做出的授权。

而对于消费方而言,需要如下的基本功能:

  • 第 1、从服务提供方获取 Customer Key/Customer Secret。
  • 第 2、提供与服务提供方之间基于 HTTP 的通信机制,以换取相关的令牌。

我们具体来看一个使用 OAuth 认证的例子。

在传统的网站应用中,如果您想在网站 A 导入网站 B 的联系人列表,需要在网站 A 输入您网站 B 的用户名、密码信息。例如,您登陆 Plaxo (https://www.plaxo.com ),一个联系人管理网站,当您想把 GMail 的联系人列表导入到 Plaxo,您需要输入您的 GMail 用户名 / 密码,如图 3 所示:

图 3. 在 Plaxo 获得 GMail 联系人
 

在这里,Plaxo 承诺不会保存您在 Gmail 的密码。

如果使用 OAuth 认证,情况是不同的,您不需要向网站 A(扮演 Consumer 角色)暴露您网站 B(扮演 Service Provider 角色)的用户名、密码信息。例如,您登录 http://lab.madgex.com/oauth-net/googlecontacts/default.aspx 网站, 如图 4 所示:

图 4. 在 lab.madgex.com 获得 GMail 联系人
 

点击“Get my Google Contacts”,浏览器将会重定向到 Google,引导您登录 Google,如图 5 所示:

图 5. 登录 Google
 

登录成功后,将会看到图 6 的信息:

图 6. Google 对 lab.madgex.com 网站授权
 

在您登录 Google,点击“Grant access”,授权 lab.madgex.com 后,lab.madgex.com 就能获得您在 Google 的联系人列表。

在上面的的例子中,网站 lab.madgex.com 扮演着 Consumer 的角色,而 Google 是 Service Provider,lab.madgex.com 使用基于 OAuth 的认证方式从 Google 获得联系人列表。

下一节,本文会给出一个消费方实现的例子,通过 OAuth 机制请求 Google Service Provider 的 OAuth Access Token,并使用该 Access Token 访问用户的在 Google 上的日历信息 (Calendar)。

回页首

示例

准备工作

作为消费方,首先需要访问 https://www.google.com/accounts/ManageDomains,从 Google 那里获得标志我们身份的 Customer Key 及其 Customer Secret。另外,您可以生成自己的自签名 X509 数字证书,并且把证书上传给 Google,Google 将会使用证书的公钥来验证任何来自您的请求。

具体的操作步骤,请读者参考 Google 的说明文档:http://code.google.com/apis/gdata/articles/oauth.html。

在您完成这些工作,您将会得到 OAuth Consumer Key 及其 OAuth Consumer Secret,用于我们下面的开发工作。

如何获得 OAuth Access Token

以下的代码是基于 Google Code 上提供的 OAuth Java 库进行开发的,读者可以从 http://oauth.googlecode.com/svn/code/java/core/ 下载获得。

  • 指定 Request Token URL,User Authorization URL,以及 Access Token URL,构造 OAuthServiceProvider 对象:

    OAuthServiceProvider serviceProvider = new OAuthServiceProvider(
    "https://www.google.com/accounts/OAuthGetRequestToken",
    "https://www.google.com/accounts/OAuthAuthorizeToken",
    "https://www.google.com/accounts/OAuthGetAccessToken");
  • 指定 Customer Key,Customer Secret 以及 OAuthServiceProvider,构造 OAuthConsumer 对象:
    OAuthConsumer oauthConsumer = new OAuthConsumer(null
    , "www.example.com"
    , "hIsGkM+T4+90fKNesTtJq8Gs"
    , serviceProvider);
  • 为 OAuthConsumer 指定签名方法,以及提供您自签名 X509 数字证书的 private key。
    oauthConsumer.setProperty(OAuth.OAUTH_SIGNATURE_METHOD, OAuth.RSA_SHA1);
    oauthConsumer.setProperty(RSA_SHA1.PRIVATE_KEY, privateKey);
  • 由 OAuthConsumer 对象生成相应的 OAuthAccessor 对象:
     accessor = new OAuthAccessor(consumer);
    
  • 指定您想要访问的 Google 服务,在这里我们使用的是 Calendar 服务:
  • 
    
    1. Collection<? extends Map.Entry> parameters
    2. = OAuth.newList("scope","http://www.google.com/calendar/feeds/");
  • 通过 OAuthClient 获得 Request Token:
  • 
    
    1. OAuthMessage response = getOAuthClient().getRequestTokenResponse(
    2. accessor, null, parameters);
  • 使用 Request Token, 将用户重定向到授权页面,如图 7 所示:

    图 7. OAuth User Authorization

  • 当用户点击“Grant access”按钮,完成授权后,再次通过 OAuthClient 获得 Access Token:
oauthClient.getAccessToken(accessor, null, null); 

在上述步骤成功完成后,Access Token 将保存在 accessor 对象的 accessToken 成员变量里。查看您的 Google Account 安全管理页面,可以看到您授权的所有消费方,如图 8 所示。

图 8. Authorized OAuth Access to your Google Account
 

使用 OAuth Access Token 访问 Google 服务

接下来,我们使用上一节获得的 Access Token 设置 Google Service 的 OAuth 认证参数,然后从 Google Service 获取该用户的 Calendar 信息:


  1. OAuthParameters para = new OAuthParameters();
  2. para.setOAuthConsumerKey("www.example.com");
  3. para.setOAuthToken(accessToken);
  4. googleService.setOAuthCredentials(para, signer);

清单 1 是完整的示例代码,供读者参考。

清单 1. 基于 OAuth 认证的 Google Service 消费方实现


  1. import java.util.Collection;
  2. import java.util.Map;
  3. import net.oauth.OAuth;
  4. import net.oauth.OAuthAccessor;
  5. import net.oauth.OAuthConsumer;
  6. import net.oauth.client.OAuthClient;
  7. public class DesktopClient {
  8. private final OAuthAccessor accessor;
  9. private OAuthClient oauthClient = null;
  10. public DesktopClient(OAuthConsumer consumer) {
  11. accessor = new OAuthAccessor(consumer);
  12. }
  13. public OAuthClient getOAuthClient() {
  14. return oauthClient;
  15. }
  16. public void setOAuthClient(OAuthClient client) {
  17. this.oauthClient = client;
  18. }
  19. //get the OAuth access token.
  20. public String getAccessToken(String httpMethod,
  21. Collection<? extends Map.Entry> parameters) throws Exception {
  22. getOAuthClient().getRequestTokenResponse(accessor, null,parameters);
  23. String authorizationURL = OAuth.addParameters(
  24. accessor.consumer.serviceProvider.userAuthorizationURL,
  25. OAuth.OAUTH_TOKEN, accessor.requestToken);
  26. //Launch the browser and redirects user to authorization URL
  27. Runtime.getRuntime().exec("rundll32 url.dll,FileProtocolHandler "
  28. + authorizationURL);
  29. //wait for user's authorization
  30. System.out.println("Please authorize your OAuth request token. "
  31. + "Once that is complete, press any key to continue...");
  32. System.in.read();
  33. oauthClient.getAccessToken(accessor, null, null);
  34. return accessor.accessToken;
  35. }
  36. }
  37. import java.net.URL;
  38. import java.security.KeyFactory;
  39. import java.security.PrivateKey;
  40. import java.security.spec.EncodedKeySpec;
  41. import java.security.spec.PKCS8EncodedKeySpec;
  42. import java.util.Collection;
  43. import java.util.Map;
  44. import com.google.gdata.client.GoogleService;
  45. import com.google.gdata.client.authn.oauth.OAuthParameters;
  46. import com.google.gdata.client.authn.oauth.OAuthRsaSha1Signer;
  47. import com.google.gdata.client.authn.oauth.OAuthSigner;
  48. import com.google.gdata.data.BaseEntry;
  49. import com.google.gdata.data.BaseFeed;
  50. import com.google.gdata.data.Feed;
  51. import net.oauth.OAuth;
  52. import net.oauth.OAuthConsumer;
  53. import net.oauth.OAuthMessage;
  54. import net.oauth.OAuthServiceProvider;
  55. import net.oauth.client.OAuthClient;
  56. import net.oauth.client.httpclient4.HttpClient4;
  57. import net.oauth.example.desktop.MyGoogleService;
  58. import net.oauth.signature.OAuthSignatureMethod;
  59. import net.oauth.signature.RSA_SHA1;
  60. public class GoogleOAuthExample {
  61. //Note, use the private key of your self-signed X509 certificate.
  62. private static final String PRIVATE_KEY = "XXXXXXXX";
  63. public static void main(String[] args) throws Exception {
  64. KeyFactory fac = KeyFactory.getInstance("RSA");
  65. //PRIVATE_KEY is the private key of your self-signed X509 certificate.
  66. EncodedKeySpec privKeySpec = new PKCS8EncodedKeySpec(
  67. OAuthSignatureMethod.decodeBase64(PRIVATE_KEY));
  68. fac = KeyFactory.getInstance("RSA");
  69. PrivateKey privateKey = fac.generatePrivate(privKeySpec);
  70. OAuthServiceProvider serviceProvider = new OAuthServiceProvider(
  71. //used for obtaining a request token
  72. //"https://www.google.com/accounts/OAuthGetRequestToken",
  73. //used for authorizing the request token
  74. "https://www.google.com/accounts/OAuthAuthorizeToken",
  75. //used for upgrading to an access token
  76. "https://www.google.com/accounts/OAuthGetAccessToken");
  77. OAuthConsumer oauthConsumer = new OAuthConsumer(null
  78. , "lszhy.weebly.com" //consumer key
  79. , "hIsGnM+T4+86fKNesUtJq7Gs" //consumer secret
  80. , serviceProvider);
  81. oauthConsumer.setProperty(OAuth.OAUTH_SIGNATURE_METHOD, OAuth.RSA_SHA1);
  82. oauthConsumer.setProperty(RSA_SHA1.PRIVATE_KEY, privateKey);
  83. DesktopClient client = new DesktopClient(oauthConsumer);
  84. client.setOAuthClient(new OAuthClient(new HttpClient4()));
  85. Collection<? extends Map.Entry> parameters =
  86. OAuth.newList("scope","http://www.google.com/calendar/feeds/");
  87. String accessToken = client.getAccessToken(OAuthMessage.GET,parameters);
  88. //Make an OAuth authorized request to Google
  89. // Initialize the variables needed to make the request
  90. URL feedUrl = new URL(
  91. "http://www.google.com/calendar/feeds/default/allcalendars/full");
  92. System.out.println("Sending request to " + feedUrl.toString());
  93. System.out.println();
  94. GoogleService googleService = new GoogleService("cl", "oauth-sample-app");
  95. OAuthSigner signer = new OAuthRsaSha1Signer(MyGoogleService.PRIVATE_KEY);
  96. // Set the OAuth credentials which were obtained from the step above.
  97. OAuthParameters para = new OAuthParameters();
  98. para.setOAuthConsumerKey("lszhy.weebly.com");
  99. para.setOAuthToken(accessToken);
  100. googleService.setOAuthCredentials(para, signer);
  101. // Make the request to Google
  102. BaseFeed resultFeed = googleService.getFeed(feedUrl, Feed.class);
  103. System.out.println("Response Data:");
  104. System.out.println("==========================================");
  105. System.out.println("|TITLE: " + resultFeed.getTitle().getPlainText());
  106. if (resultFeed.getEntries().size() == 0) {
  107. System.out.println("|\tNo entries found.");
  108. } else {
  109. for (int i = 0; i < resultFeed.getEntries().size(); i++) {
  110. BaseEntry entry = (BaseEntry) resultFeed.getEntries().get(i);
  111. System.out.println("|\t" + (i + 1) + ": "
  112. + entry.getTitle().getPlainText());
  113. }
  114. }
  115. System.out.println("==========================================");
  116. }
  117. }

回页首

小结

OAuth 协议作为一种开放的,基于用户登录的授权认证方式,目前互联网很多 Open API 都对 OAuth 提供了支持,这包括 Google, Yahoo,Twitter 等。本文以 Google 为例子,介绍了 Java 桌面程序如何开发 OAuth 认证应用。在开发桌面应用访问 Web 资源这样一类程序时,一般通行的步骤是:使用 OAuth 做认证,然后使用获得的 OAuth Access Token,通过 REST API 访问用户在服务提供方的资源。

事实上,目前 OAuth 正通过许多实现(包括针对 Java、C#、Objective-C、Perl、PHP 及 Ruby 语言的实现)获得巨大的动力。大部分实现都由 OAuth 项目维护并放在 Google 代码库 (http://oauth.googlecode.com/svn/) 上。开发者可以利用这些 OAuth 类库编写自己需要的 OAuth 应用。

参考资料

学习

讨论

关于作者

李三红,任职于 IBM 中国软件开发中心 Lotus 产品部门,负责 Lotus Notes 安全相关研发工作。在这之前,他一直从事网络应用开发相关工作。他感兴趣的技术领域包括:分布式对象计算、网络应用、OSGi、协作计算、Java 安全等方面。

基于 OAuth 安全协议的 Java 应用编程的更多相关文章

  1. 基于 OAuth 安全协议的 Java 应用编程1

    原文地址:http://www.ibm.com/developerworks/cn/java/j-lo-oauth/index.html 參考博客:http://www.cnblogs.com/wan ...

  2. 基于Socket的低层次Java网络编程

    Socket通讯 网络上的两个程序通过一个双向的通讯连接实现数据的交换,这个双向链路的一端称为一个Socket.Socket通常用来实现客户方和服务方的连接.Socket是TCP/IP协议的一个十分流 ...

  3. 基于TCP/IP协议的C++网络编程(API函数版)

    源代码:http://download.csdn.net/detail/nuptboyzhb/4169959 基于TCP/IP协议的网络编程 定义变量——获得WINSOCK版本——加载WINSOCK库 ...

  4. 基于URL的高层次Java网络编程

    一致资源定位器URL URL(Uniform Resource Locator)是一致资源定位器的简称,它表示Internet上某一资源的地址.通过URL我们可以访问Internet上的各种网络资源, ...

  5. Java 网络编程(二) 两类传输协议:TCP UDP

    链接地址:http://www.cnblogs.com/mengdd/archive/2013/03/09/2951841.html 两类传输协议:TCP,UDP TCP TCP是Transfer C ...

  6. 基于JVM原理、JMM模型和CPU缓存模型深入理解Java并发编程

    许多以Java多线程开发为主题的技术书籍,都会把对Java虚拟机和Java内存模型的讲解,作为讲授Java并发编程开发的主要内容,有的还深入到计算机系统的内存.CPU.缓存等予以说明.实际上,在实际的 ...

  7. Java网络编程和NIO详解9:基于NIO的网络编程框架Netty

    Java网络编程和NIO详解9:基于NIO的网络编程框架Netty 转自https://sylvanassun.github.io/2017/11/30/2017-11-30-netty_introd ...

  8. java网络编程+通讯协议的理解

    参考: http://blog.csdn.net/sunyc1990/article/details/50773014 网络编程对于很多的初学者来说,都是很向往的一种编程技能,但是很多的初学者却因为很 ...

  9. 基于连接的Java网络编程

    实现了基于TCP的Java Socket编程,功能很简单:客户端向服务器端输出一名话"connect",服务器端接收输出到控制台并向客户端输出一名话"Hello" ...

随机推荐

  1. [React] Render Elements Outside the Current React Tree using Portals in React 16

    By default the React Component Tree directly maps to the DOM Tree. In some cases when you have UI el ...

  2. matlab 可视化 —— 高级 api(montage)、insertObjectAnnotation、insertMaker

    1. montage:同时显示多个图像 thumbnails = train_x(:, :, :, 1:100); thumbnails = imresize(thumbnails, [64, 64] ...

  3. visual studio code 中 debugger for chrome 插件的配置

    安装 debugger for chrome 插件后,把默认的 launch.json 改成: { "name": "谷歌浏览器", "type&qu ...

  4. Docker---(3)Docker常用命令

    原文:Docker---(3)Docker常用命令 版权声明:欢迎转载,请标明出处,如有问题,欢迎指正!谢谢!微信:w1186355422 https://blog.csdn.net/weixin_3 ...

  5. Java基础学习总结(53)——HTTPS 理论详解与实践

    前言 在进行 HTTP 通信时,信息可能会监听.服务器或客户端身份伪装等安全问题,HTTPS 则能有效解决这些问题.在使用原始的HTTP连接的时候,因为服务器与用户之间是直接进行的明文传输,导致了用户 ...

  6. [Angular] Use Angular’s @HostBinding and :host(...) to add styling to the component itself

    One thing that we can do is to add styles directly to HTML elements that live within our component. ...

  7. Python写爬虫-爬甘农大学校新闻

    Python写网络爬虫(一) 关于Python: 学过C. 学过C++. 最后还是学Java来吃饭. 一直在Java的小世界里混迹. 有句话说: "Life is short, you ne ...

  8. 【Codeforces Round #440 (Div. 2) A】 Search for Pretty Integers

    [链接] 我是链接,点我呀:) [题意] 在这里输入题意 [题解] 先枚举一个数字的情况. 再枚举两个数的情况就好. [代码] #include <bits/stdc++.h> #defi ...

  9. 算法求解中的变量、数组与数据结构(STL 中的容器)

    本质上算法都是对数据的操作,没有数据,没有存储数据的容器和组织方式,算法就是无源之水无本之木,就是巧妇也难为无米之炊.算法是演员,变量.数组.容器等就是舞台, 然后整个算法的处理流程,都是针对这些数据 ...

  10. POJ 3086 Triangular Sums (ZOJ 2773)

    http://poj.org/problem?id=3086 http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemId=1773 让你计算两 ...