在网上写 OAuth 授权的文章有很多,不过其中内容质量很高的较少,以至于我自己在学习的过程中也走了不少弯路= =。借着这次发博客的机会,也做一个小结吧。
什么是 OAuth
近几年来,很多网站都默默地在自己的登录链接旁边加上了一个“用XXX平台账号登录”的链接,比如
使用第三方平台账号登录,当前平台并不会获取到用户的密码,登录的动作在第三方平台下完成。
在登录完成后,会跳出一个页面,由当前平台请求对第三方平台的操作授权。
用户同意之后,一般情况下,当前平台还会要求用户注册一个在当前平台使用的独立账号。
注册成功后,下次用户再进入当前平台时,就可以使用第三方平台账号登录了。登录后,当前平台及第三方平台的账号都会进入在线状态。
OAuth 就是为了提供跨平台访问而产生的一种标准。
OAuth 1.0 的标准在 2007 年发布,2.0 的标准则在 2011 年发布。其中 2.0 的标准取消了 Request token 和所有 Token 的加密过程,但因为强制使用 Https 协议,因此被认为安全性高于 1.0 的标准。显而易见的是,第三方平台授权有非常大的优点。
首先,用户可以只使用一个社交账号来登录众多平台,这就避免了对于很多非重度使用的平台,注册后忘记用户名和密码的情况。
其次,对于一些小的平台来说,通过 OAuth 授权,可以依托大平台的账号带来用户,对于服务提供方来说,因为账号会在授权双方平台同时在线,也会为己方平台带来可观的流量。
最后,对于用户来说,简化的注册环节以及跨平台访问,则带来了相当多的便利。
OAuth 2.0流程简介
因为 1.0 标准的 OAuth 在流程中有非常多的参数传递以及程序加密步骤。如今各大平台几乎已经全部切换到了 2.0 的授权标准。
上图是 OAuth 2.0 的授权流程。(下文编号与图中编号对应)
- 用户在当前平台发送一个通过第三方登录的请求。
- 当前平台跳转到第三方平台的授权请求 URL,在 Request 中携带当前平台在第三方平台注册的应用 id, 应用 secret 以及回调地址信息。
- 第三方平台在确认 Request 中包含的应用 id, 应用 secret,回调地址与此前注册的一致后,给当前用户提供登录界面。
- 用户在第三方平台完成登录及对当前平台的授权。
>授权内容通常包括通过当前平台访问第三方平台的用户信息,以及操作第三方平台内容的权限。 - 第三方平台携带验证码回调当前平台。
- 当前平台携带验证码获取 Access token。
- 第三方平台验证码通过,生成 Access token 回传给当前平台。
- 当前平台携带 Access token 访问第三方平台的受保护内容链接。
- 第三方平台返回受保护的内容。
- 当前平台保存受保护的内容。
>通常会让用户在当前平台注册一个新账号,然后将新账号与第三方平台的账号绑定,绑定过后,用户在当前平台会成功登录。
有关 OAuth Scribe 库的使用
Scribe是一个用 Java 开发的 OAuth 开源库,支持 OAuth 1.0a / OAuth 2.0 标准。
项目地址
在 Maven 的 pom.xml
中加入库依赖:
<repositories>
<repository>
<id>scribe-java-mvn-repo</id>
<url>https://raw.github.com/fernandezpablo85/scribe-java/mvn-repo/</url>
<snapshots>
<enabled>true</enabled>
<updatePolicy>always</updatePolicy>
</snapshots>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>org.scribe</groupId>
<artifactId>scribe</artifactId>
<version>1.3.6</version>
</dependency>
</dependencies>
初始化依赖之后,就可以使用 Scribe 库了。
此外,为了从当前平台访问第三方平台,我们还需要构建服务配置类(OAuthServiceConfig
)以及服务提供类(OAuthServiceProvider
)。
/**
* 服务配置类,配置向第三方平台请求的服务配置项
*/
public class OAuthServiceConfig {
//以下信息在消费平台配置的与第三方平台注册的要保持完全一致
private String apiKey; //在第三方平台注册后生成的消费平台应用id
private String apiSecret;//在第三方平台注册后生成的消费平台应用secret
private String callback;//第三方平台在用户登录及授权操作通过后,消费平台的回调地址。
private String scope;//申请的权限范围,可选
private Class apiClass;//记载获取第三方校验信息api地址的类,大平台的api类多在scribe中有封装
/**
* getters & setters
*/
public OAuthServiceConfig() {
}
public OAuthServiceConfig(String apiKey, String apiSecret, String callback, String scope,
Class apiClass) {
super();
this.apiKey = apiKey;
this.apiSecret = apiSecret;
this.callback = callback;
this.apiClass = apiClass;
this.scope = scope;
}
}
/**
* 服务提供类,通过获取服务配置类中的配置,生成发送给第三方平台的Request
*/
public class OAuthServiceProvider {
private final static Logger logger = LoggerFactory.getLogger(OAuthServiceProvider.class);
private OAuthServiceConfig config;
public OAuthServiceProvider() {
}
public OAuthServiceProvider(OAuthServiceConfig config) {
this.config = config;
}
public OAuthService getService() {
return new ServiceBuilder().provider(config.getApiClass())
.apiKey(config.getApiKey())
.apiSecret(config.getApiSecret())
.callback(config.getCallback())
.scope(config.getScope())
.build();
}
}
将服务配置的参数初始化配入 Spring 配置文件。
<!-- 在Spring 配置文件中初始化服务配置类的配置项,下面的EL表达式配置从.properties文件获取-->
<bean id="weiboServiceConfig" class="com.example.oauth.OAuthServiceConfig">
<constructor-arg value="${oauth.weibo.apikey}" /> <!-- client id -->
<constructor-arg value="${oauth.weibo.apisecret}"/> <!-- client secret -->
<constructor-arg value="${oauth.weibo.callback}"/> <!-- 回调地址 -->
<constructor-arg value="${oauth.weibo.scope}"/> <!-- 申请访问的第三方权限范围 -->
<constructor-arg value="com.example.oauth.SinaWeiboApi20"/> <!-- api类,以微博为例 -->
</bean>
<!-- 以服务配置项为参数,生成服务 -->
<bean id="weiboServiceProvider" class="com.example.oauth.OAuthServiceProvider"> <constructor-arg name="config" ref="weiboServiceConfig" /> </bean>
最后是访问接口类。/callback接口为用户同意授权后,Weibo回调当前平台的接口
/**
* Controller类
*/
@Controller
public class WeiboController {
@Autowired
@Qualifier("weiboServiceProvider")
private OAuthServiceProvider weiboServiceProvider;
//获取受保护内容的地址(例如某用户信息)
private static final String PROTECTED_RESOURCE_URL = "https://api.weibo.com/2/users/show.json";
//以下代码和授权流程中的步骤编码对应
//1.接收来自客户端的第三方登录请求
@RequestMapping(value = "/oauth/weibo/login", method = RequestMethod.GET)
public String loginByWeibo() {
OAuthService service = weiboServiceProvider.getService();
//2.重定向到第三方平台的授权请求URL,其中URL已经在OauthServiceConfig类中配置
return "redirect:" + service.getAuthorizationUrl(EMPTY_TOKEN);
}
//5.携带验证码回调当前平台,code参数为验证码
@RequestMapping(value = "/oauth/weibo/callback", method = RequestMethod.GET)
public String callback(
@RequestParam(value = "code") String oauthVerifier) throws CoreException {
OAuthService service = weiboServiceProvider.getService();
Verifier verifier = new Verifier(oauthVerifier);
//6.7.携带验证码获取access token
Token accessToken = service.getAccessToken(requestToken, verifier);
OAuthRequest oauthRequest = new OAuthRequest(Verb.GET, PROTECTED_RESOURCE_URL);
//8.access token作为参数发送获取受保护内容的request
service.signRequest(accessToken, oauthRequest);
//9.第三方平台response返回受保护的内容
Response oauthResponse = oauthRequest.send();
oauthResponse.getBody();
//...处理从response获取的信息
}
}
}
最后……
感谢耐心观看。
鄙人才疏学浅,一点点经验,如有谬误,欢迎指正。
Zhao Xiaoxi
Coding 官方技术博客:http://blog.coding.net/
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。