使用Java开发实现OAuth安全认证的应用

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 按以下方式工作:
消费方与不同的服务提供方建立了关系。
消费方共享一个密码短语或者是公钥给服务提供方,服务提供方使用该公钥来确认消费方的身份。
消费方根据服务提供方将用户重定向到登录页面。
该用户登录后告诉服务提供方该消费方访问他的保护资源是没问题的。
回页首
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的授权流程

你所开发的应用需要流程如下:

  • 向应用服务商(新浪、搜狐等微博)请求request_token。
  • 得到request_token后重定向用户到服务商的授权页面。
  • 如果用户选择授权你得应用,用request_token向服务商请求换取access_token。
  • 得到access_token等信息访问受限资源。

而服务商相应的响应如下:

  • 创建request_token返回给应用。
  • 询问用户是否授权此应用。如果用户授权重定向用户至应用页面。
  • 创建access_token并返回给应用。
  • 响应受限资源请求并返回相关信息。
  • 通俗点的说法就是“你拿着你得身份证明(request_token)向服务商申请进入用户家的门钥匙(access_token),服务商询问用户同不同意,如果用户同意服务商就给你进入用户家门的钥匙(access_token),拿到钥匙后你就可以进到用户家里”。

OAuth授权的Java实现

作为一个开放协议目前有很多现成的Oauth库可供开发者使用,可以点击这里下载。不过有精力有时间的话还是自己去实现一下OAuth授权的流程,可以很好的体会OAuth认证协议的原理。以下就是我使用Java实现Oauth的具体步骤,代码很简单,如果有画蛇添足的地方还望高手一笑而过。

一、获取Request_token

首先得准备一下参数及其来源:

  • oauth_consumer_key —— 注册应用后由应用服务商提供
  • consumer_secret —— 注册应用后由应用服务商提供
  • oauth_callback —— 用户授权后的返回地址
  • oauth_nonce —— 随机字符串,须保证每次都不同
  • oauth_timestamp —— 时间戳
  • oauth_signature_method —— 签名base string 的方法,目前支持 HMAC-SHA1
  • oauth_version —— Oauth协议版本

还需要下面三个请求地址(这些地址任何一个提供OAuth的服务商都会提供给你,看下API文档就会找到):

  1. requst_token_url —— 上面第1步中的请求地址
  2. authorize_url —— 上面第2步的请求地址
  3. access_token_url —— 上面第3步的请求地址

至于如何注册应用,新浪微博、腾讯微博等等的网站上都有,这里就不再详细说明了。注册成功后就会获得oauth_consumer_key 和 consumer_secret 两个参数。

oauth_callback 起的作用是当用户授权成功后服务商会把用户重定向到这个网址。

oauth_nonce 是一个随机字符串下面是我的生成代码:

public String set_nonce() {
String base = "abcdefghijklmnopqrstuvwxyz0123456789";
Random random = new Random();
StringBuffer sb = new StringBuffer();
for (int i = 0; i < 18; i++) {
int number = random.nextInt(base.length());
sb.append(base.charAt(number));
}
return sb.toString();
}

oauth_timestamp 是请求的时间戳,我的代码如下:

public String set_timestamp() {

Date date = new Date();
long time = date.getTime();
return (time + "").substring(0, 10);
}

需要说明一下的是这里的时间戳为10位而不是13位,因此截取0-10位置。

其他参数直接指定就行了。

接下来,有了这些参数就可以组装base string了。准备base string的目的就是为了得到 oauth_signature 这个参数,这个参数向服务商发送请求的时候需要用到。

组装的方法是用下面8部分

POST(也可以是GET,取决于你应用服务商支持哪个)。

  • Urlencode之后的requst_token_url 。
  • oauth_callback=Urlencode之后你的oauth_callback(Urlencode的参数为“utf-8”)。
  • oauth_consumer_key = 你的oauth_consumer_key
  • oauth_nonce = 你的oauth_nonce
  • oauth_signature_method = 你的 oauth_signature_method
  • oauth_timestamp = 你的oauth_timestamp
  • oauth_version = “1.0”——目前大多数OAuth都采用的是1.0或1.0a版本。

需要注意的是上面除了1跟2外其他参数的格数形如: abc=“abc” ,然后先将上面1和2部分用&号相连得到串A、3-8部分用&相连得到串B,下面需要将串B再进行一次Urlencode得到串C,最后将A跟C以&号相连就得到了base string。这个过程中 oauth_callback 实质上经过了两次 Urlencode ,组装base string是非常容易出错的,一不小心丢一个引号或者格式稍有不对就会出错。

下面是我的Java实现代码:

public String set_basestring() throws UnsupportedEncodingException {
String bss;
bss = oauth_request_method + "&"
+ URLEncoder.encode(requst_token_url, "utf-8") + "&";
String bsss = "oauth_callback="
+ URLEncoder.encode(oauth_callback, "utf-8")
+ "&oauth_consumer_key=" + oauth_consumer_key + "&oauth_nonce="
+ oauth_nonce + "&oauth_signature_method="
+ oauth_signature_method + "&oauth_timestamp="
+ oauth_timestamp + "&oauth_version=" + oauth_version;
bsss = URLEncoder.encode(bsss, "utf-8");
return bss + bsss;
}

有了base string就可以签名生成oauth_signature这个参数,oauth_signature会在请求request_token的时候用到。签名算法是HMAC-SHA1,签名的key就是最开始的consumer_secret后加一个&号,签名算法代码如下:

public String hmacsha1(String data, String key) {
byte[] byteHMAC = null;
try {
Mac mac = Mac.getInstance("HmacSHA1");
SecretKeySpec spec = new SecretKeySpec(key.getBytes(), "HmacSHA1");
mac.init(spec);
byteHMAC = mac.doFinal(data.getBytes());
} catch (InvalidKeyException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException ignore) {
}
String oauth = new BASE64Encoder().encode(byteHMAC);
return oauth;
}

里面用的的BASE64Encoder这个类可以Google一个。

得到oauth_signature后就要开始向 requst_token_url 发送请求了,OAuth规范定义了三种传递OAuth参数方式:

  1. httpheader中
  2. url中
  3. post form中

国内各大微博的支持情况是:新浪Httpheader可用,网易Httpheader可用,腾讯只支持在url,搜狐由于没有appkey所以还没去尝试。

如果使用Httpheader传递参数头名为“Authorization”,值为下面的格式,将值改为自己应用的。

OAuth oauth_nonce="9zWH6qe0qG7Lc1telCn7FhUbLyVdjEaL3MO5uHxn8", oauth_signature_method="HMAC-SHA1", oauth_timestamp="1272323047", oauth_consumer_key="GDdmIQH6jhtmLUypg82g", oauth_token="8ldIZyxQeVrFZXFOZH5tAwj6vzJYuLQpl0WUEYtWc", oauth_verifier="pDNg57prOHapMbhv25RNf75lVRd6JDsni1AJJIDYoTY", oauth_signature="PUw%2FdHA4fnlJYM6RhXk5IU%2F0fCc%3D", oauth_version="1.0"

url和post form两种方式的参数名和参数值也即上面的,完全一样。

请求发送成功后就会得到的响应如下:

oauth_token=8ldIZyxQeVrFZXFOZH5tAwj6vzJYuLQpl0WUEYtWc&oauth_token_secret=x6qpRnlEmW9JbQn4PQVVeVG8ZLPEx6A0TOebgwcuA&oauth_callback_confirmed=true

可以看到响应里面已经包含oauth_token和oauth_token_secret了,存贮之以备后面使用。

二、用户认证

拿到了oauth_token之后就需要用户对此oauth_token授权,也即对你的应用授权,具体做法就是发送oauth_token到服务商并请求用户对此oauth_token授权:实现方法为以oauth_token和oauth_callback为参数请求oauthorize_url,Servlet中的代码如下:

resp.sendRedirect(oauthorize_url+"?oauth_token="+oauth_token+"&oauth_callback="+oauth_callback);

这是用户就被带到了应用授权页面,并可以选择是否对该应用授权。如果用户授权之后就会被带到oauth_callback 地址。同时如果需要服务商会给oauth_callback返会一个名为oauth_verifier的参数(此参数用于无法跳转的桌面应用,不一定每个微博平台都会返回),这时候我们的oauth_token已经获得用户的授权了。

三、用oauth_token换取access_token

这一步跟第一步“获取Request_token”基本相同,也是需要准备 base string 对其签名,然后发送请求,可以参考第一步的代码实现:但是相应的参数有所不用,具体来讲就是第一步组装base string 时候8个部分中第二个部分中的url换为access_token_url 并去掉oauth_callback加上oauth_token(如果有oauth_verifier的话也需要一并加上),组装好之后需要签名以得到oauth_signature,本次签名的方法跟上次一样,但是key变为consumer_secret和oauth_token_secret以&连接的串。

下来需要向access_token_url发送请求,请求参数包括base string 里的除了请求方法(POST或GET)和请求地址外的所有参数及其值和签名后生成的oauth_signature。例子如下:

OAuth oauth_nonce="9zWH6qe0qG7Lc1telCn7FhUbLyVdjEaL3MO5uHxn8", oauth_signature_method="HMAC-SHA1", oauth_timestamp="1272323047", oauth_consumer_key="GDdmIQH6jhtmLUypg82g", oauth_token="8ldIZyxQeVrFZXFOZH5tAwj6vzJYuLQpl0WUEYtWc", oauth_verifier="pDNg57prOHapMbhv25RNf75lVRd6JDsni1AJJIDYoTY", oauth_signature="PUw%2FdHA4fnlJYM6RhXk5IU%2F0fCc%3D", oauth_version="1.0""

请求成功会服务商就会返回oauth_token和oaut_token_secret,这里的oauth_token和oaut_token_secret就是真正访问资源要用的access_token。

还需要说明的是以上过程只需要经行一次,就是说你拿到的access_token是不会过期的,除非用户手动将授权收回,因此作为access_token的oauth_token和oaut_token_secret要保存起来,以后访问受限资源的时候可以直接使用。至于如何访问受限资源,等以后有时间了再补上。

(0)

相关推荐

  • Java线程安全中的单例模式

    复制代码 代码如下: package net.kitbox.util; /**  *  * @author lldy  *  */ public class Singleton {     private Singleton(){     }     private static class SingletonHolder{         private static Singleton  instance = new Singleton();     }     public static

  • Java实现SSL双向认证的方法

    本文实例讲述了Java实现SSL双向认证的方法.分享给大家供大家参考,具体如下: 我们常见的SSL验证较多的只是验证我们的服务器是否是真实正确的,当然如果你访问的URL压根就错了,那谁也没有办法.这个就是所谓的SSL单向认证. 但是实际中,我们有可能还会验证客户端是否符合要求,也就是给我们每个用户颁发一个证书,比且每个数字证书都是唯一的,不公开的.这样就能通过这个数字证书保证当前访问我服务器的这个用户是经过服务器认可的,其他人不可访问. 双向认证 从第一个层面上 确保了服务器 与客户端 都是互相

  • java使用HttpSession实现QQ访问记录

    java如何使用HttpSession实现QQ的访问记录,本文为大家揭晓答案,具体内容如下 1. 编写QQ空间数据类(QQS.java) public class QQS { private static LinkedHashMap<Integer, String> qqs = new LinkedHashMap<Integer, String>(); static{ qqs.put(10001, "张三"); qqs.put(10002, "李四&q

  • JSP学习之Java Web中的安全控制实例详解

    本文实例讲述了JSP学习之Java Web中的安全控制.分享给大家供大家参考.具体如下: 一.目标: ① 掌握登录之后的一般处理过程: ② 能够为每个页面添加安全控制: ③ 能够共享验证代码: ④ 使用过滤器对权限进行验证: ⑤ 能够对文件的局部内容进行验证: ⑥ 掌握安全验证码的基本实现方式: ⑦ 通过异常处理增强安全性. 二.主要内容: ① 通过修改前面的登录功能,分别对管理员和普通用户的登录进行处理: ② 为管理员才能访问的页面添加控制: ③ 共享各个页面中的控制代码,使用专门的文件,然后

  • java编译时出现使用了未经检查或不安全的操作解决方法

    在本人用editplus写java文件时碰到的问题. 复制代码 代码如下: import java.util.*;class collection{    public static void main(String[] args) {        Collection c1=new ArrayList(25); c1.add(new String("one"));        c1.add(new String("two"));        String s

  • java struts2学习笔记之线程安全

    在说struts2的线程安全之前,先说一下,什么是线程安全?这是一个网友讲的. 如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码.如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的. 就是说,在一个进程中有多个线程并发执行,每个线程执行过程中,变量值是相同的,执行结果也是相同的,就是线程安全的.否则就是线程不安全的. 然后回顾一下servlet的线程安全问题,由于servlet是单例模式的,只会产生一个实例,当多个用户同

  • 使用asx3m与xstream配合解决flex与java利用httpservice传递xml数据问题

    后端也存在java user object类,利用xstream组件把list中的user对象序列化成xml数据.在flex端通过httpservice使用E4X format格式获取.刚开始自己准备通过e4x技术来解析xml,构造flex user object,类似这样的处理方式: 复制代码 代码如下: public function fromXML(currentNode:XML):void{ userID = new Number(currentNode.userID); userNam

  • 使用httpclient无需证书调用https的示例(java调用https)

    使用httpclient无需证书调用https的url地址,传输字节流. 复制代码 代码如下: package com.paic.hmreport.metaQ; import java.io.BufferedInputStream;import java.io.BufferedReader;import java.io.ByteArrayInputStream;import java.io.FileOutputStream;import java.io.IOException;import ja

  • 深入理解:Java是类型安全的语言,而C++是非类型安全的语言

    有过C++开发经验的人会发现,我们可以将0作为false,非零作为true.一个函数即使是bool类型的,但是我们还是可以返回int类型的,并且自动将0转换成false,非零转换成true.代码实例如下: 复制代码 代码如下: #include<iostream> #include<stdlib.h> using namespace std; bool fun()//函数返回类型是bool,但是我们在函数中可以返回int类型. {     return 1; } void main

  • 使用flex中的httpservice方法与java进行交互

    地球已经调至震动状态使用flex中的httpservice方法与java进行交互: 一.写服务器: 1.在myeclipse中建立web项目 2.写一个用来打印xml的servlet 3.当使用httpservice与java进行交互的时候不用改写remoting-config.xml文件 4.web.xml文件中配置servlet的访问地址(一般不用配置,当一个servlet建好之后myeclipse就会自动配置好web.xml文 件,不用去改动,但是需要注意的是,当你在导入blazeds开发

  • Java中HttpServletResponse响应中文出现乱码问题

    以字符串的形式输出. 1.response.getWriter().write("您好中国hello"); 如果这样输出的话.则浏览器结果为: 2.加上代码 response.setCharacterEncoding("UTF-8"); response.getWriter().write("您好中国hello"); 则浏览器结果为: 这是因为浏览器解析问题. 加上代码: response.setHeader("Content-type

随机推荐