1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        快速接入 GitHub、QQ 第三方登錄真有那么難嗎?

        共 8405字,需瀏覽 17分鐘

         ·

        2022-07-05 16:13

        大家好!


        最近接了一個(gè)項(xiàng)目要求接入Github和QQ,最后實(shí)現(xiàn)了,甲方也很滿意,給大家分享一下實(shí)現(xiàn)的具體的方法吧!


        本文提及第三方登錄涉及到 OAuth2.0,關(guān)于 OAuth2.0 的理論基礎(chǔ)參考阮一峰老師的《理解 OAuth 2.0》,其中關(guān)于授權(quán)碼模式就是本篇文章的重點(diǎn),如想看這篇理論基礎(chǔ)自行百度即可。

        本文著重于代碼,關(guān)于理論不再贅述,關(guān)于不同公司的三方登錄流程,只要遵循 OAuth2.0 規(guī)范,都大同小異。本文介紹 GitHub 和 QQ 兩種,因?yàn)檫@兩種無(wú)需審核,即可食用。


        # GitHub 登錄


        1、 注冊(cè)應(yīng)用


        進(jìn)入 Github 的 Setting 頁(yè)面,點(diǎn)擊 Developer settings,如圖所示:


        進(jìn)入后點(diǎn)擊 New Oauth App,如圖所示:




        在其中填寫主頁(yè) URL 和 回調(diào) URL,回調(diào) URL 尤為重要,如果不太明白可以先和我一致。




        點(diǎn)擊注冊(cè)后,上方會(huì)生成 Client ID 和 Client Secret,這兩個(gè)后面要用到。




        2 、HTML 頁(yè)面


        頁(yè)面十分簡(jiǎn)單,只有兩個(gè)跳轉(zhuǎn)鏈接:


        <!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>



        3 、Github 登錄方法


        在這個(gè)方法中,我們需要訪問(wèn) GitHub 的認(rèn)證服務(wù)器,使用 Get 請(qǐng)求,這里使用重定向來(lái)實(shí)現(xiàn)。


        遵循 Oauth 2.0 規(guī)范,需要攜帶以下參數(shù):


        • response_type :對(duì)于授權(quán)碼模式,該值固定為 code

        • client_id :注冊(cè)應(yīng)用時(shí)的 Client ID

        • state :回調(diào)時(shí)會(huì)原樣返回

        • redirect_uri : 回調(diào) URL,注冊(cè)應(yīng)用時(shí)填寫的


        這里的 state 參數(shù)我要額外說(shuō)明下,因?yàn)樵搮?shù)會(huì)在后面的回調(diào) URL 中被原樣攜帶回來(lái),絕大多數(shù)的開發(fā)者會(huì)忽略該字段,阮一峰老師的文章也沒(méi)有著重提及這一點(diǎn)。但是忽略該參數(shù)是會(huì)導(dǎo)致 CSRF攻擊的,在回調(diào)函數(shù)中應(yīng)當(dāng)對(duì)該字段進(jìn)行校驗(yàn)!

        關(guān)于如何校驗(yàn),我一開始的想法是使用 session 來(lái)存儲(chǔ) state 進(jìn)行校驗(yàn)的,但是我發(fā)現(xiàn)使用重定向后 session 不是同一個(gè) session,方案一失敗。


        然后我想通過(guò) ajax 請(qǐng)求,在頁(yè)面中使用 window.location.href 方法跳轉(zhuǎn)到認(rèn)證服務(wù)器,使用 session 存儲(chǔ),但是很不幸這樣也不是同一個(gè) session,方案二失敗。


        最后我的解決辦法是使用 redis 緩存,使用 set 存儲(chǔ),回調(diào)時(shí)判斷是否存在。當(dāng)然你也可以用 HashMap 來(lái)存儲(chǔ),這也是一個(gè)解決辦法。


        關(guān)于 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認(rèn)證服務(wù)器地址 String url = "https://github.com/login/oauth/authorize"; // 生成并保存state,忽略該參數(shù)有可能導(dǎo)致CSRF攻擊 String state = oauthService.genState(); // 傳遞參數(shù)response_type、client_id、state、redirect_uri String param = "response_type=code&" + "client_id=" + GITHUB_CLIENT_ID + "&state=" + state + "&redirect_uri=" + GITHUB_REDIRECT_URL;
        // 1、請(qǐng)求Github認(rèn)證服務(wù)器 response.sendRedirect(url + "?" + param);}



        4、 Github 回調(diào)方法


        在上一步中,瀏覽器會(huì)被跳轉(zhuǎn)到 Github 的授權(quán)頁(yè),當(dāng)用戶登錄并點(diǎn)擊確認(rèn)后,GitHub認(rèn)證服務(wù)器會(huì)跳轉(zhuǎn)到我們填寫的回調(diào)URL中,我們?cè)诔绦蛑刑幚砘卣{(diào)。


        在回調(diào)方法中,步驟如下:


        1. 首先驗(yàn)證 state 與發(fā)送時(shí)是否一致,如果不一致,可能遭遇了 CSRF 攻擊。


        2. 得到 code,向 GitHub 認(rèn)證服務(wù)器申請(qǐng)令牌(token)


          這一步使用模擬的 POST 請(qǐng)求,攜帶參數(shù)包括:


        • grant_type :授權(quán)碼模式固定為 authorization_code
        • code :上一步中得到的 code
        • redirect_uri :回調(diào)URL
        • client_id :注冊(cè)應(yīng)用時(shí)的Client ID
        • client_secret :注冊(cè)應(yīng)用時(shí)的Client Secret

        3. 得到令牌(access_token)和令牌類型(token_type),向GitHub資源服務(wù)器獲取資源(以 user_info 為例)


        這一步使用模擬的 GET 請(qǐng)求,攜帶參數(shù)包括:


        • access_token :令牌
        • token_type :令牌類型

        4. 輸出結(jié)果


        /** * GitHub回調(diào)方法 * @param code 授權(quán)碼 * @param state 應(yīng)與發(fā)送時(shí)一致 * @author jitwxs * @since 2018/5/21 15:24 */@RequestMapping("/githubCallback")public void githubCallback(String code, String state, HttpServletResponse response) throws Exception {    // 驗(yàn)證state,如果不一致,可能被CSRF攻擊    if(!oauthService.checkState(state)) {        throw new Exception("State驗(yàn)證失敗");    }
        // 2、向GitHub認(rèn)證服務(wù)器申請(qǐng)令牌 String url = "https://github.com/login/oauth/access_token"; // 傳遞參數(shù)grant_type、code、redirect_uri、client_id String param = "grant_type=authorization_code&code=" + code + "&redirect_uri=" + GITHUB_REDIRECT_URL + "&client_id=" + GITHUB_CLIENT_ID + "&client_secret=" + GITHUB_CLIENT_SECRET;
        // 申請(qǐng)令牌,注意此處為post請(qǐng)求 String result = HttpClientUtils.sendPostRequest(url, param);
        /* * result示例: * 失?。篹rror=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,表示失敗,錯(cuò)誤原因存儲(chǔ)在error_description if(resultMap.containsKey("error")) { throw new Exception(resultMap.get("error_description")); }
        // 如果返回結(jié)果中包含access_token,表示成功 if(!resultMap.containsKey("access_token")) { throw new Exception("獲取token失敗"); }
        // 得到token和token_type String accessToken = resultMap.get("access_token"); String tokenType = resultMap.get("token_type");
        // 3、向資源服務(wù)器請(qǐng)求用戶信息,攜帶access_token和tokenType String userUrl = "https://api.github.com/user"; String userParam = "access_token=" + accessToken + "&token_type=" + tokenType;
        // 申請(qǐng)資源 String userResult = HttpClientUtils.sendGetRequest(userUrl, userParam);
        // 4、輸出用戶信息 response.setContentType("text/html;charset=utf-8"); response.getWriter().write(userResult);}



        # QQ 登錄


        1、 注冊(cè)應(yīng)用


        進(jìn)入 QQ 互聯(lián)管理中心:https://connect.qq.com/manage.html,創(chuàng)建一個(gè)新應(yīng)用(需要先審核個(gè)人身份):微信搜索公眾號(hào):Java項(xiàng)目精選,回復(fù):java 領(lǐng)取資料 。




        然后注冊(cè)應(yīng)用信息,和 GitHub 的步驟大差不差:




        注冊(cè)后,可以看到應(yīng)用的 APP ID、APP Key,以及你被允許的接口,當(dāng)然只有一個(gè)獲取用戶信息。


        官方開發(fā)文檔點(diǎn)擊這里:

        http://wiki.connect.qq.com/%E5%BC%80%E5%8F%91%E6%94%BB%E7%95%A5_server-side

        注意:審核狀態(tài)為審核中和審核失敗也是可以使用的,不用擔(dān)心(只是無(wú)法實(shí)際上線而已,作為 Demo 足夠了)。




        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認(rèn)證服務(wù)器地址 String url = "https://graph.qq.com/oauth2.0/authorize"; // 生成并保存state,忽略該參數(shù)有可能導(dǎo)致CSRF攻擊 String state = oauthService.genState(); // 傳遞參數(shù)response_type、client_id、state、redirect_uri String param = "response_type=code&" + "client_id=" + QQ_APP_ID + "&state=" + state + "&redirect_uri=" + QQ_REDIRECT_URL;
        // 1、請(qǐng)求QQ認(rèn)證服務(wù)器 response.sendRedirect(url + "?" + param);}


        3、 QQ 回調(diào)方法


        /** * QQ回調(diào)方法 * @param code 授權(quán)碼 * @param state 應(yīng)與發(fā)送時(shí)一致 * @author jitwxs * @since 2018/5/21 15:24 */@RequestMapping("/qqCallback")public void qqCallback(String code, String state, HttpServletResponse response) throws Exception {    // 驗(yàn)證state,如果不一致,可能被CSRF攻擊    if(!oauthService.checkState(state)) {        throw new Exception("State驗(yàn)證失敗");    }
        // 2、向QQ認(rèn)證服務(wù)器申請(qǐng)令牌 String url = "https://graph.qq.com/oauth2.0/token"; // 傳遞參數(shù)grant_type、code、redirect_uri、client_id String param = "grant_type=authorization_code&code=" + code + "&redirect_uri=" + QQ_REDIRECT_URL + "&client_id=" + QQ_APP_ID + "&client_secret=" + QQ_APP_KEY;
        // 申請(qǐng)令牌,注意此處為post請(qǐng)求 // QQ獲取到的access token具有3個(gè)月有效期,用戶再次登錄時(shí)自動(dòng)刷新。 String result = HttpClientUtils.sendPostRequest(url, param);
        /* * result示例: * 成功:access_token=A24B37194E89A0DDF8DDFA7EF8D3E4F8&expires_in=7776000&refresh_token=BD36DADB0FE7B910B4C8BBE1A41F6783 */ Map<String, String> resultMap = HttpClientUtils.params2Map(result); // 如果返回結(jié)果中包含access_token,表示成功 if(!resultMap.containsKey("access_token")) { throw new Exception("獲取token失敗"); } // 得到token String accessToken = resultMap.get("access_token");
        // 3、使用Access Token來(lái)獲取用戶的OpenID String 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"} ); // 取出openid String openid = getQQOpenid(meResult);
        // 4、使用Access Token以及OpenID來(lái)訪問(wèn)和修改用戶數(shù)據(jù) 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) { // 獲取花括號(hào)內(nèi)串 String json = str.substring(str.indexOf("{"), str.indexOf("}") + 1); // 轉(zhuǎn)為Map Map<String, String> map = JsonUtils.jsonToPojo(json, Map.class); return map.get("openid");}


        # 項(xiàng)目源碼


        QQ 登錄的具體流程我就不啰嗦了,都差不多。代碼只列出了關(guān)鍵方法,具體程序還包含工具類和 redis 的配置。具體請(qǐng)參考文章開頭源碼,該項(xiàng)目采用 SpringBoot 搭建,需要 Redis 支持。

        作者: Jitwxs

        來(lái)源: https://jitwxs.cn/33ad9e35.html


        ——————END——————

        歡迎關(guān)注“Java引導(dǎo)者”,我們分享最有價(jià)值的Java的干貨文章,助力您成為有思想的Java開發(fā)工程師!

        瀏覽 32
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            国产1级成人毛片 | 久久老司机精品视频 | 狠狠干福利视频 | 成人无码看片 | av影音先锋 | 睡熟迷奷系列精品系列小说 | 免费人成视频网站 | 亚洲欧美精品AAAAAA片 | 又嫩又硬又黄又爽的视频 | 越南妇女毛茸茸高潮 |