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. C#里如何把一个DataTable的数据追加进数据库里的某个表

    方法一: DataTable table = new DataTable(); //TODO: init table... string connStr = "user id=" ...

  2. android屏幕适配方案

    曾经看了android的屏幕适配方案,有非常多种.当中自己用到的一种是:先找一款主流的分辨率的android机,如:1080*1920的分辨率做基准,然后在这个基准上.调整好一切布局.图片.适配其它手 ...

  3. .less为后缀的文件是什么

    .less为后缀的文件是什么 一.总结 1.less是什么:LESS 为 Web 开发者带来了福音,它在 CSS 的语法基础之上,引入了变量,Mixin(混入),运算以及函数等功能,大大简化了 CSS ...

  4. jquery 获取上一个兄弟元素和下一个兄弟元素

    jQuery.prev(),返回上一个兄弟节点,不是所有的兄弟节点 jQuery.prevAll(),返回所有之前的兄弟节点 jQuery.next(),返回下一个兄弟节点,不是所有的兄弟节点 jQu ...

  5. 4、python基本知识点及字符串常用方法

    查看变量内存地址   id(变量名) ni = 123 n2 = 123 ni和n2肯定是用的两份内存,但是python对于数字在-5~257之间的数字共用一份地址,范围可以修改 name = ‘李璐 ...

  6. JavaScript字符串替换replace方法

    在日常的js开发中, 当要把字符串中的内容替换时,如果使用类似C#的string.replace方法,如下 var str='aabbccaa'; str=str.replace('aa','dd') ...

  7. Android原生生成JSON与解析JSON

    JSON数据是一种轻量级的数据交换格式,在Android中通常应用于client与server交互之间的传输数据.像如今在网上有非常多解析JSON数据的jar包,可是归根究竟用的都是Android原生 ...

  8. js进阶 12 jquery事件汇总

    js进阶 12 jquery事件汇总 一.常用事件 页面载入事件 ready() 文档就绪事件(当 HTML 文档就绪可用时) 鼠标事件 click() 触发.或将函数绑定到指定元素的 click 事 ...

  9. ZOJ 2679 Old Bill ||ZOJ 2952 Find All M^N Please 两题水题

    2679:http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemId=1679 2952:http://acm.zju.edu.cn/onli ...

  10. 【例题5-6 UVA 540 】Team Queue

    [链接] 我是链接,点我呀:) [题意] 在这里输入题意 [题解] 用两个队列模拟就好. 记录某个队在不在队列里面. 模拟 [错的次数] 在这里输入错的次数 [反思] 在这里输入反思 [代码] #in ...