快速接入 GitHub、QQ 第三方登录方式
本文提及第三方登錄涉及到 OAuth2.0,關于 OAuth2.0 的理論基礎參考阮一峰老師的《理解 OAuth 2.0》,其中關于授權碼模式就是本篇文章的重點,如想看這篇理論基礎自行百度即可。
本文著重于代碼,關于理論不再贅述,關于不同公司的三方登錄流程,只要遵循 OAuth2.0 規范,都大同小異。本文介紹 GitHub 和 QQ 兩種,因為這兩種無需審核,即可食用。歷史也發布過 Spring Boot 的其他實戰,可以關注微信公眾號「Java后端」回復「666」下載技術棧手冊。
一、GitHub 登錄
1.1 注冊應用
進入 Github 的 Setting 頁面,點擊 Developer settings,如圖所示:
進入后點擊 New Oauth App,如圖所示:
在其中填寫主頁 URL 和 回調 URL,回調 URL 尤為重要,如果不太明白可以先和我一致。
點擊注冊后,上方會生成 Client ID 和 Client Secret,這兩個后面要用到。
1.2 HTML 頁面
頁面十分簡單,只有兩個跳轉鏈接:
<!DOCTYPE html> <html?lang="en"> <head><meta?charset="UTF-8"><title>三方登錄</title> </head> <body><h1>三方登錄Demo</h1><div><a?href="/githubLogin">GitHub登錄</a><a?href="/qqLogin">QQ登錄</a></div> </body> </html>1.3 Github 登錄方法
在這個方法中,我們需要訪問 GitHub 的認證服務器,使用 Get 請求,這里使用重定向來實現。
遵循 Oauth 2.0 規范,需要攜帶以下參數:
response_type :對于授權碼模式,該值固定為 code
client_id :注冊應用時的 Client ID
state :回調時會原樣返回
redirect_uri : 回調 URL,注冊應用時填寫的
這里的 state 參數我要額外說明下,因為該參數會在后面的回調 URL 中被原樣攜帶回來,絕大多數的開發者會忽略該字段,阮一峰老師的文章也沒有著重提及這一點。但是忽略該參數是會導致 CSRF攻擊的,在回調函數中應當對該字段進行校驗!
關于如何校驗,我一開始的想法是使用 session 來存儲 state 進行校驗的,但是我發現使用重定向后 session 不是同一個 session,方案一失敗。
然后我想通過 ajax 請求,在頁面中使用 window.location.href 方法跳轉到認證服務器,使用 session 存儲,但是很不幸這樣也不是同一個 session,方案二失敗。
最后我的解決辦法是使用 redis 緩存,使用 set 存儲,回調時判斷是否存在。當然你也可以用 HashMap 來存儲,這也是一個解決辦法。
關于 Redis,可以參考:https://jitwxs.cn/e331e26a.html
private?static?String?GITHUB_CLIENT_ID = "0307dc634e4c5523cef2"; private?static?String?GITHUB_CLIENT_SECRET = "707647176eb3bef1d4c2a50fcabf73e0401cc877"; private?static?String?GITHUB_REDIRECT_URL = "http://127.0.0.1:8080/githubCallback";@RequestMapping("/githubLogin") public?void?githubLogin(HttpServletResponse response) throws Exception {// Github認證服務器地址String?url = "https://github.com/login/oauth/authorize";// 生成并保存state,忽略該參數有可能導致CSRF攻擊String?state = oauthService.genState();// 傳遞參數response_type、client_id、state、redirect_uriString?param = "response_type=code&"?+ "client_id="?+ GITHUB_CLIENT_ID + "&state="?+ state+ "&redirect_uri="?+ GITHUB_REDIRECT_URL;// 1、請求Github認證服務器response.sendRedirect(url + "?"?+ param); }1.4 Github 回調方法
在上一步中,瀏覽器會被跳轉到 Github 的授權頁,當用戶登錄并點擊確認后,GitHub認證服務器會跳轉到我們填寫的回調URL中,我們在程序中處理回調。
在回調方法中,步驟如下:
1. 首先驗證 state 與發送時是否一致,如果不一致,可能遭遇了 CSRF 攻擊。
2. 得到 code,向 GitHub 認證服務器申請令牌(token)
??這一步使用模擬的 POST 請求,攜帶參數包括:
grant_type :授權碼模式固定為 authorization_code
code :上一步中得到的 code
redirect_uri :回調URL
client_id :注冊應用時的Client ID
client_secret :注冊應用時的Client Secret
3. 得到令牌(access_token)和令牌類型(token_type),向GitHub資源服務器獲取資源(以 user_info 為例)
這一步使用模擬的 GET 請求,攜帶參數包括:
access_token :令牌
token_type :令牌類型
4. 輸出結果
/*** GitHub回調方法* @param code 授權碼* @param state 應與發送時一致* @author jitwxs* @since 2018/5/21 15:24*/ @RequestMapping("/githubCallback") public?void?githubCallback(String?code, String?state, HttpServletResponse response) throws Exception {// 驗證state,如果不一致,可能被CSRF攻擊if(!oauthService.checkState(state)) {throw?new?Exception("State驗證失敗");}// 2、向GitHub認證服務器申請令牌String?url = "https://github.com/login/oauth/access_token";// 傳遞參數grant_type、code、redirect_uri、client_idString?param = "grant_type=authorization_code&code="?+ code + "&redirect_uri="?+GITHUB_REDIRECT_URL + "&client_id="?+ GITHUB_CLIENT_ID + "&client_secret="?+ GITHUB_CLIENT_SECRET;// 申請令牌,注意此處為post請求String?result = HttpClientUtils.sendPostRequest(url, param);/** result示例:* 失敗:error=incorrect_client_credentials&error_description=The+client_id+and%2For+client_secret+passed+are+incorrect.&* error_uri=https%3A%2F%2Fdeveloper.github.com%2Fapps%2Fmanaging-oauth-apps%2Ftroubleshooting-oauth-app-access-token-request-errors%2F%23incorrect-client-credentials* 成功:access_token=7c76186067e20d6309654c2bcc1545e41bac9c61&scope=&token_type=bearer*/Map<String, String> resultMap = HttpClientUtils.params2Map(result);// 如果返回的map中包含error,表示失敗,錯誤原因存儲在error_descriptionif(resultMap.containsKey("error")) {throw??new?Exception(resultMap.get("error_description"));}// 如果返回結果中包含access_token,表示成功if(!resultMap.containsKey("access_token")) {throw??new?Exception("獲取token失敗");}// 得到token和token_typeString?accessToken = resultMap.get("access_token");String?tokenType = resultMap.get("token_type");// 3、向資源服務器請求用戶信息,攜帶access_token和tokenTypeString?userUrl = "https://api.github.com/user";String?userParam = "access_token="?+ accessToken + "&token_type="?+ tokenType;// 申請資源String?userResult = HttpClientUtils.sendGetRequest(userUrl, userParam);// 4、輸出用戶信息response.setContentType("text/html;charset=utf-8");response.getWriter().write(userResult); }二、QQ 登錄
2.1 注冊應用
進入 QQ 互聯管理中心:https://connect.qq.com/manage.html,創建一個新應用(需要先審核個人身份):
然后注冊應用信息,和 GitHub 的步驟大差不差:
注冊后,可以看到應用的 APP ID、APP Key,以及你被允許的接口,當然只有一個獲取用戶信息。
官方開發文檔點擊這里:
http://wiki.connect.qq.com/%E5%BC%80%E5%8F%91%E6%94%BB%E7%95%A5_server-side
注意:審核狀態為審核中和審核失敗也是可以使用的,不用擔心(只是無法實際上線而已,作為 Demo 足夠了)。
2.2 QQ 登錄方法
private?static?String?QQ_APP_ID = "101474821"; private?static?String?QQ_APP_KEY = "00d91cc7f636d71faac8629d559f9fee"; private?static?String?QQ_REDIRECT_URL = "http://127.0.0.1:8080/qqCallback";@RequestMapping("/qqLogin") public?void?qqLogin(HttpServletResponse response) throws Exception {// QQ認證服務器地址String?url = "https://graph.qq.com/oauth2.0/authorize";// 生成并保存state,忽略該參數有可能導致CSRF攻擊String?state = oauthService.genState();// 傳遞參數response_type、client_id、state、redirect_uriString?param = "response_type=code&"?+ "client_id="?+ QQ_APP_ID + "&state="?+ state+ "&redirect_uri="?+ QQ_REDIRECT_URL;// 1、請求QQ認證服務器response.sendRedirect(url + "?"?+ param); }2.3 QQ 回調方法
/*** QQ回調方法* @param code 授權碼* @param state 應與發送時一致* @author jitwxs* @since 2018/5/21 15:24*/ @RequestMapping("/qqCallback") public?void?qqCallback(String?code, String?state, HttpServletResponse response) throws Exception {// 驗證state,如果不一致,可能被CSRF攻擊if(!oauthService.checkState(state)) {throw?new?Exception("State驗證失敗");}// 2、向QQ認證服務器申請令牌String?url = "https://graph.qq.com/oauth2.0/token";// 傳遞參數grant_type、code、redirect_uri、client_idString?param = "grant_type=authorization_code&code="?+ code + "&redirect_uri="?+QQ_REDIRECT_URL + "&client_id="?+ QQ_APP_ID + "&client_secret="?+ QQ_APP_KEY;// 申請令牌,注意此處為post請求// QQ獲取到的access token具有3個月有效期,用戶再次登錄時自動刷新。String?result = HttpClientUtils.sendPostRequest(url, param);/** result示例:* 成功:access_token=A24B37194E89A0DDF8DDFA7EF8D3E4F8&expires_in=7776000&refresh_token=BD36DADB0FE7B910B4C8BBE1A41F6783*/Map<String, String> resultMap = HttpClientUtils.params2Map(result);// 如果返回結果中包含access_token,表示成功if(!resultMap.containsKey("access_token")) {throw??new?Exception("獲取token失敗");}// 得到tokenString?accessToken = resultMap.get("access_token");// 3、使用Access Token來獲取用戶的OpenIDString?meUrl = "https://graph.qq.com/oauth2.0/me";String?meParams = "access_token="?+ accessToken;String?meResult = HttpClientUtils.sendGetRequest(meUrl, meParams);// 成功返回如下:callback( {"client_id":"YOUR_APPID","openid":"YOUR_OPENID"} );// 取出openidString?openid = getQQOpenid(meResult);// 4、使用Access Token以及OpenID來訪問和修改用戶數據String?userInfoUrl = "https://graph.qq.com/user/get_user_info";String?userInfoParam = "access_token="?+ accessToken + "&oauth_consumer_key="?+ QQ_APP_ID + "&openid="?+ openid;String?userInfo = HttpClientUtils.sendGetRequest(userInfoUrl, userInfoParam);// 5、輸出用戶信息response.setContentType("text/html;charset=utf-8");response.getWriter().write(userInfo); }/*** 提取Openid* @param str 形如:callback( {"client_id":"YOUR_APPID","openid":"YOUR_OPENID"} );* @author jitwxs* @since 2018/5/22 21:37*/ private?String?getQQOpenid(String?str) {// 獲取花括號內串String?json = str.substring(str.indexOf("{"), str.indexOf("}") + 1);// 轉為MapMap<String, String> map = JsonUtils.jsonToPojo(json, Map.class);return?map.get("openid"); }三、項目源碼
QQ 登錄的具體流程我就不啰嗦了,都差不多。代碼只列出了關鍵方法,具體程序還包含工具類和 redis 的配置。具體請參考文章開頭源碼,該項目采用 SpringBoot 搭建,需要 Redis 支持。
文章作者: Jitwxs
文章鏈接: https://jitwxs.cn/33ad9e35.html
版權聲明: 本博客所有文章除特別聲明外,均采用 CC BY-NC-SA 4.0 許可協議。轉載請注明來自 Jitwxs!
推薦文章基于SpringBoot的ERP系統,自帶進銷存+財務+生產功能
2020年國內互聯網公司的薪酬排名!
不要再封裝各種Util工具類了,這個神級框架值得擁有!
寫博客能月入10K?
一款基于 Spring Boot 的現代化社區(論壇/問答/社交網絡/博客)
這或許是最美的Vue+Element開源后臺管理UI
推薦一款高顏值的 Spring Boot 快速開發框架
一款基于 Spring Boot 的現代化社區(論壇/問答/社交網絡/博客)
13K點贊都基于 Vue+Spring 前后端分離管理系統ELAdmin,大愛
想接私活時薪再翻一倍,建議根據這幾個開源的SpringBoot項目
總結
以上是生活随笔為你收集整理的快速接入 GitHub、QQ 第三方登录方式的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 遇到一个git的大坑 src refsp
- 下一篇: 通俗讲解分布式锁,看完不懂算我输