11月03日
OAuth 2.0 第三方平台授权及 OAuth Scribe 库介绍

在网上写 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 的授权流程。(下文编号与图中编号对应)

  1. 用户在当前平台发送一个通过第三方登录的请求。
  2. 当前平台跳转到第三方平台的授权请求 URL,在 Request 中携带当前平台在第三方平台注册的应用 id, 应用 secret 以及回调地址信息。
  3. 第三方平台在确认 Request 中包含的应用 id, 应用 secret,回调地址与此前注册的一致后,给当前用户提供登录界面。
  4. 用户在第三方平台完成登录及对当前平台的授权。

    授权内容通常包括通过当前平台访问第三方平台的用户信息,以及操作第三方平台内容的权限。
  5. 第三方平台携带验证码回调当前平台。
  6. 当前平台携带验证码获取 Access token。
  7. 第三方平台验证码通过,生成 Access token 回传给当前平台。
  8. 当前平台携带 Access token 访问第三方平台的受保护内容链接。
  9. 第三方平台返回受保护的内容。
  10. 当前平台保存受保护的内容。

    通常会让用户在当前平台注册一个新账号,然后将新账号与第三方平台的账号绑定,绑定过后,用户在当前平台会成功登录。

有关 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获取的信息
		}
    }
}

最后……

感谢耐心观看。
鄙人才疏学浅,一点点经验,如有谬误,欢迎指正。

4条评论

Super inimaortfve writing; keep it up.
匿名4 年前回复
简单明了,领教了
匿名5 年前回复
Scribe库中的API还有这个参数,现在request token都传EMPTY_TOKEN了,也就是null
匿名5 年前回复
Token accessToken = service.getAccessToken(requestToken, verifier); 中的requestToken怎么来的?
匿名5 年前回复