應用場景
web應用通過人人網登錄授權實現第三方登錄。
操作步驟
1 注冊成為人人網開放平台開發者
2 准備一個可訪問的域名,如dev.foo.com
3 創建網頁應用,配置必要信息,其中包括根域名、圖標信息
4 獲取應用appID、appKey進行開發
登錄流程
采用server-side方式實現登錄授權,流程如下


流程描述
1 server端頁面跳轉到登錄授權頁面(Authorization code方式)
2 回調獲得code
3 置換accessToken,同時也得到uid、用戶資料信息
4 同步用戶信息並登錄
OAuth2.0 采用Authorization code方式將更為可靠、安全。
更多信息可參考人人網開放平台wiki:
案例實戰
本地開發環境准備
修改hosts文件將dev.foo.com映射到127.0.0.1;
本地服務器以80端口啟動, windows下可能會出現80端口被系統進程占用的情況,解決方法可參考 http://www.cnblogs.com/littleatp/p/4414578.html
本地服務器啟動后,以dev.foo.com的域名進行訪問,在登錄授權時可通過域名驗證這一步
前端登錄跳轉頁面
<html> <head> <title>人人網登錄跳轉</title> <script src="http://lib.sinaapp.com/js/jquery/1.7.2/jquery.min.js" type="text/javascript"></script> <script type="text/javascript"> //應用的APIKEY var apiKey = "3ce9cb1e264f4e93b1f38807be66e629"; //成功授權后的回調地址 var redirectUrl = "@@{openapi.Renrens.callback()}"; var authorizeUrl = "https://graph.renren.com/oauth/authorize?" var queryParams = ['client_id=' + apiKey,'redirect_uri=' + redirectUrl,'response_type=code']; var url = authorizeUrl + queryParams.join('&'); //打開授權登錄頁面 window.location.href= url; </script> </head> <body> </body> </html>
功能描述
頁面在打開時直接跳轉到人人網登錄授權頁面,此后授權成功后將回調至redirect_uri
server端處理回調,同步信息
授權回調的頁面處理
/** * 授權回調 * * @param code * @param error */ public static void callback(String code) { if (!StringUtils.isEmpty(code)) { error("授權失敗"); } // 根據code換取accesstoken,包括用戶信息 // ... String callbackUrl = RouteContext.getUrl("openapi.Renrens.callback", Collections.EMPTY_MAP, true); RenrenToken token = RenApi.getTokenInfo(code, callbackUrl); if (token == null) { error("授權失敗:無法獲取連接系統"); } render(code, token); }
數據對象
RenrenToken類
/** * 返回token數據對象 * * <pre> * * { * "token_type":"bearer", * "expires_in":2595096, * "refresh_token":"127021|0.KAS3b8doSitHk6RLDtitb2VY8PjktTRA.229819774.1376381303243", * "user":{ * "id":229819700, * "name":"二小姐", * "avatar":[ * { "type":"avatar", * "url":"http://hdn.xnimg.cn/photos/hdn121/20130805/2055/h_head_KFTQ_d536000000d0111b.jpg" * }, * { "type":"tiny", * "url":"http://hdn.xnimg.cn/photos/hdn221/20130805/2055/tiny_jYQe_ec4300051e7a113f.jpg" * }, * { "type":"main", * "url":"http://hdn.xnimg.cn/photos/hdn121/20130805/2055/h_main_ksPJ_d536000000d0111b.jpg"}, * { "type":"large", * "url":"http://hdn.xnimg.cn/photos/hdn121/20130805/2055/h_large_yxZz_d536000000d0111b.jpg" * } * ] * }, * "access_token":"127066|6.08718aa138db0578dda3250f33bads6e.2592000.1378976400-229819774" * "scope":"read_user_feed read_user_album", * </pre> * * @author littleatp * @createDate 2015年4月14日 * */ public class RenrenToken { public String token_type; public int expires_in; public String refresh_token; public String access_token; public String scope; public RenrenUser user; }
RenrenUser類
/** * 人人網用戶信息 * * <pre> * "user":{ * "id":229819700, * "name":"二小姐", * "avatar":[ * { "type":"avatar", * "url":"http://hdn.xnimg.cn/photos/hdn121/20130805/2055/h_head_KFTQ_d536000000d0111b.jpg" * }, * { "type":"tiny", * "url":"http://hdn.xnimg.cn/photos/hdn221/20130805/2055/tiny_jYQe_ec4300051e7a113f.jpg" * }, * { "type":"main", * "url":"http://hdn.xnimg.cn/photos/hdn121/20130805/2055/h_main_ksPJ_d536000000d0111b.jpg"}, * { "type":"large", * "url":"http://hdn.xnimg.cn/photos/hdn121/20130805/2055/h_large_yxZz_d536000000d0111b.jpg" * } * ] * }, * </pre> * * @author littleatp * @createDate 2015年4月14日 * */ public class RenrenUser { public long id; public String name; public List<RenrenAvatar> avatar; public String getAvatarUrl() { if (avatar == null || avatar.isEmpty()) { return ""; } return avatar.get(0).url; } public static class RenrenAvatar { public String type; public String url; } }
RenApi功能實現
/** * 人人網API * * <pre> * 登錄流程: * * 1 前端跳轉人人網授權(code方式) * 2 回調獲得authorize code * 3 通過code換取access_token * 4 獲得token及用戶信息 * * 參考文檔: * http://wiki.dev.renren.com/wiki/Authentication * </pre> * * * @author littleatp * @createDate 2015年4月10日 * */ public class RenApi { public static String apiKey = "xxx"; public static String secretKey = "xxx"; public static String baseUrl = "https://graph.renren.com/oauth"; protected static final String URL_GET_TOKEN = baseUrl + "/token?grant_type=authorization_code" + "&client_id=%s&client_secret=%s&code=%s&redirect_uri=%s"; protected static final long ACCESS_TIMEOUT = 15; protected static final String DEF_APP_TOKEN_EXPIRE = "3h";/** * 獲取token信息 * * <pre> * http://wiki.dev.renren.com/wiki/Authentication#.E5.AE.A2.E6.88.B7.E7.AB.AF.E6.8E.88.E6.9D.83 * 返回token的同時也附帶了用戶信息 * * 調用地址: * https://graph.renren.com/oauth/token * * 參數 * grant_type:使用Authorization Code 作為Access Grant時,此值固定為“authorization_code”; * client_id:在開發者中心注冊應用時獲得的API Key; * client_secret:在開發者中心注冊應用時獲得的Secret Key。Secret Key是應用的保密信息,請不要將其嵌入到服務端以外的代碼里; * redirect_uri:必須與獲取Authorization Code時傳遞的“redirect_uri”保持一致; * code:上述過程中獲得的Authorization Code。 * * 返回結果如下: * { * "token_type":"bearer", * "expires_in":2595096, * "refresh_token":"127021|0.KAS3b8doSitHk6RLDtitb2VY8PjktTRA.229819774.1376381303243", * "user":{ * "id":229819700, * "name":"二小姐", * "avatar":[ * { "type":"avatar", * "url":"http://hdn.xnimg.cn/photos/hdn121/20130805/2055/h_head_KFTQ_d536000000d0111b.jpg" * }, * { "type":"tiny", * "url":"http://hdn.xnimg.cn/photos/hdn221/20130805/2055/tiny_jYQe_ec4300051e7a113f.jpg" * }, * { "type":"main", * "url":"http://hdn.xnimg.cn/photos/hdn121/20130805/2055/h_main_ksPJ_d536000000d0111b.jpg"}, * { "type":"large", * "url":"http://hdn.xnimg.cn/photos/hdn121/20130805/2055/h_large_yxZz_d536000000d0111b.jpg" * } * ] * }, * "access_token":"127066|6.08718aa138db0578dda3250f33bads6e.2592000.1378976400-229819774" * "scope":"read_user_feed read_user_album", * } * * 錯誤返回: * { * "error": "invalid_grant", * "error_code": 20204 * "error_description": "Invalid authorization code: 9OCQp3IzRcwtSRPKOEUKiRRsz9SUNgdE" * } * http://wiki.dev.renren.com/wiki/%E9%94%99%E8%AF%AF%E5%93%8D%E5%BA%94 * </pre> * * @param accessToken * @return */ public static RenrenToken getTokenInfo(String code, String callbackUrl) { if (StringUtils.isEmpty(code)) { return null; } String url = String.format(URL_GET_TOKEN, apiKey, secretKey, code, callbackUrl); String resultString = DefaultHttp.get(url, ACCESS_TIMEOUT, GlobalConstants.UTF_8); Logger.debug("[sso-renren]get token. use url '%s'", url); RenrenToken token = JsonUtil.fromJson(resultString, RenrenToken.class); if (token == null || StringUtils.isEmpty(token.access_token)) { Logger.debug("[sso-renren]get token failed, with result of '%s'", resultString); return null; } Logger.debug("[sso-renren]get token success, with result of '%s'", resultString); return token; } }
關於CSRF
跨站攻擊問題CSRF
場景
A網站接入了人人網開放平台,但apikey和secretkey通過頁面泄露了出去;
B網站根據同樣的apikey和secretkey仿造authorize請求,獲得authorization code;
B網站直接跳轉到A網站的callback頁面;
A網站按授權流程獲得用戶信息並登錄;
這樣B網站便成功實現了仿造請求登錄A網站的功能;
解決方法
在向平台請求授權(authorize)時可帶上一個state參數,建議該參數由A網站動態生成。
平台調用callback時會回傳該state參數,此時A網站需要在callback處理時對該參數進行驗證。
於是B網站無法偽造state參數,也就無法偽造登錄場景了。
常見問題
網頁跳轉提示
redirect_uri_mismatch
通常是應用配置中的根域名與當前開發服務器訪問地址不一致導致
授權返回錯誤
SDK的使用
對於強依賴於人人網平台的應用,建議使用下平台的SDK,如JavaSDK;其封裝了大量api訪問及出錯處理細節,可提高開發效率。