本文以Java Springboot和一般前端技術爲例,且前後端分離,讓你的博客能夠使用Github賬號授權登錄
OAuth介紹
概述
OAuth
是一個關於授權(authorization)的開放網絡標準,現在廣泛使用的版本是2.0
最近學校讓我們下了各種網課軟件,很煩,不過好在這些網課軟件不用我們再新註冊一個賬號,我們可以使用微信一鍵登錄,登錄之後這個平臺就能從微信獲取到我們同意了的一部分公開信息,來作爲這個平臺賬戶的基本信息,這就是OAuth
而Github OAuth也給個人用戶提供了這個功能,讓我們能夠在自己的服務中使用Github授權登錄
由於網上概念比較多,就不詳講了,具體可以參見阮一峯大佬的博客
工作流程
相比於之前的token,session等認證方式,OAuth最大的特點就是,確認你身份的信息是來源於第三方服務器的
在OAuth的工作流程中,用上面網課的例子,就會有這樣幾個對象:
- 用戶
- 騰訊課堂(resource owner)
- 微信(authorization server)
畫個圖表示,就是這樣:
在這之前,其實資源服務器需要去認證服務器那裏申請這個功能,具體會在後面的Github OAuth中講,這裏就只關心用戶階段了
流程如下:
- 用戶試圖使用微信來登錄騰訊課堂
- 騰訊課堂會讓用戶跳轉到微信的界面去授權(如果沒有登錄要先登錄),由於我這裏暫時不好用騰訊課堂截屏舉例子,所以看一個用新浪微博登錄的頁面,大概是這樣:
- 用戶授權成功後,就會迴轉到資源服務器(這個url是資源服務器事先和微信約定好的),同時攜帶一個token,資源服務器取到這個token後就去執行後臺工作了(很快,對於用戶來說就是等了一下然後就登錄成功了)
- 再說資源服務器拿着這個token:他會去用token和自己服務器之前註冊時獲得的一些參數去找微信服務器請求這個用戶的資源
- 微信返回資源,騰訊課堂成功登錄
這樣的話,對於用戶來說,不但登錄很方便,無需接觸密碼,而且對資源服務器來說,也就把授權的操作拆分出去了一部分(我是這樣理解的,不對請指教)
Github OAuth申請
像我們常見的QQ登錄,微信登錄這種,都必須是企業去找他們登記註冊,纔可以用,但是,Github OAuth就可以個人直接註冊,這樣你就可以給你寫的博客添加Github賬號快速登錄等功能了,爽翻!
接下來是申請步驟:
Github登錄後去這個鏈接:https://github.com/settings/developers
然後點開OAuth Apps
,右邊New OAuth App
填寫信息:
比如我之前前端後端本地測試的時候,是這樣填寫的:
也就是說,我的主頁是在localhost:8080,然後用戶點擊github授權完成後就會跳轉回到localhost:9999/accounts/githubAuth這個地方
申請完成後,你會拿到兩個參數:
Client_id
和Client_Secret
保管好,後面會用到
到此,Github這邊就申請完畢了
前端配置
跳轉鏈接
前端首先需要給用戶提供一個按鈕,問他要不要github授權登錄,就給他一個超鏈接就對了:
<a href="https://github.com/login/oauth/authorize?client_id=你剛剛的那個client_id寫上&redirect_uri=你剛剛的那個重定向鏈接寫上">
這裏注意,這個redirect_uri必須和上面的一樣,不然會報錯的!
用戶點了這個鏈接後就會跳轉到授權頁面:
彈出輸入賬號密碼的頁面前提是你先登錄了,如果登錄了的話就直接登入了不會有這個頁面
然後用戶完成後,就會被跳轉回到之前寫的redirect_uri
獲取參數
跳轉回來後,鏈接後面會帶上一個token參數:
http://localhost:9999/accounts/githubAuth?code=05e32e2365dca6117eff
像這樣
所以我們需要的就是讓後臺獲得這個code然後找服務器要資源就ok了
前端的任務就完成了
這裏的話,如果要做成前後端分離的話,其實是可以把這個redirect_uri設置成前端站點的一個uri,然後這個頁面通過js代碼自動去獲取code參數然後向後端發送一條請求,等着後端的響應成功就代表用戶成功登錄了
後端配置
Github OAuth中,用戶登錄成功後會獲取到code,後臺需要拿着這個code,同時攜帶自己之前在註冊時候的Client_id
和Client_Secret
,就能夠找github獲取到一個“獲取這個用戶的信息的許可”的令牌
接下來,後臺再拿着這個令牌發送另外一個請求,獲取到用戶信息了
大致思路如下:
接着上面說道的,獲取code之後向後端發送這個參數,所以後端就會去執行以下的操作
獲取code並拿到token
以Springboot爲例子,寫個接口搞定:
@GetMapping("/githubAuth")
public void githubAuth(String code,HttpServletRequest req)
{
accountService.getTokenFromGithub(code);
}
然後後端給Github服務器發起一次請求
String USER_AGENT = "Mozilla/5.0";
HttpClient client = HttpClientBuilder.create().build();
HttpPost request = new HttpPost(GithubUtils.getTokenUrl(code));
request.addHeader("User-Agent", USER_AGENT);
HttpResponse response = client.execute(request);
BufferedReader rd = new BufferedReader(
new InputStreamReader(response.getEntity().getContent()));
String responseStr = rd.readLine();
String accessToken = responseStr.substring(responseStr.indexOf('=')+1,responseStr.indexOf('&'));
其中,我這裏寫了一個GithubUtils工具類,來自動拼接出url
拼接出來的url需要帶上3個參數,大概是這樣:
"https://github.com/login/oauth/access_token?client_id="+CLINENT_ID+"&client_secret="+CLIENT_SECRET+"&code="+code;
這樣我們就獲取到token的值了(但是他發回來的值有點奇怪,所以我就沒用JSON解析)
通過token拿到用戶信息
然後我們要做的就是用token請求另外一個信息接口就對了,代碼如下:
//添加請求頭
HttpClient client2 = HttpClientBuilder.create().build();
HttpGet request2 = new HttpGet(GithubUtils.getInfoUrl(accessToken));
HttpResponse response2 = client.execute(request2);
BufferedReader rd2 = new BufferedReader(
new InputStreamReader(response2.getEntity().getContent()));
String line = "";
StringBuilder result = new StringBuilder();
while ((line = rd2.readLine()) != null) {
result.append(line);
}
String infos = result.toString();
System.out.println(infos);
JSONObject info = JSONObject.parseObject(infos);
System.out.println(info.getString("login"));
System.out.println(info.getString("id"));
System.out.println(info.getString("node_id"));
System.out.println(info.getString("avatar_url"));
System.out.println(info.getString("bio"));
System.out.println(info.getString("name"));
System.out.println(info.getString("html_url"));
請求的url大概是這樣:
"https://api.github.com/user?access_token="+token;
我在阮一峯大佬的教程裏看,他說的是把token放到請求頭裏,但我那樣做是失敗了的,直接放到url裏當參數我就成功了
這樣,就能獲取到用戶的信息了,信息大概是這樣一個JSON對象:
{
“login”:“Lehr”,
“id”:xxx,
“node_id”:“xxx”,
“avatar_url”:“xxx”,
“url”:“xxx”,
“html_url”:“https://github.com/Lehr”
…
}
我用的是阿里的fastjson直接解析了就對了
後續
基本上,Github OAuth的工作流程就結束了,你只需要結合自己的代碼給用戶返回個登錄成功就行了
比如我的代碼,拿到Github的id,和我數據庫裏綁定的用戶信息比較,找到這個用戶的信息,是如果用戶登錄成功了,就返回一個jwt給他
🎉完成
附代碼
最後附一下我的GithubUtils和Service裏完整的代碼(不含我上面說的後續操作的代碼)
GithubUtils
package com.imlehr.internship.utils;
/**
* @author Lehr
* @create: 2020-03-10
*/
public class GithubUtils {
private final static String CLIENT_SECRET = "你的";
private final static String CLINENT_ID = "你的";
private final static String TOKEN_URL = "https://github.com/login/oauth/access_token";
private final static String INFO_URL = "https://api.github.com/user";
/**
* 返回一條完整的帶有參數的token請求鏈接
* @param code
* @return
*/
public static String getTokenUrl(String code)
{
return TOKEN_URL+"?client_id="+CLINENT_ID+"&client_secret="+CLIENT_SECRET+"&code="+code;
}
public static String getInfoUrl(String token)
{
return INFO_URL+"?access_token="+token;
}
}
Service
@SneakyThrows
public void getTokenFromGithub(String code) {
System.out.println(code);
String USER_AGENT = "Mozilla/5.0";
HttpClient client = HttpClientBuilder.create().build();
HttpPost request = new HttpPost(GithubUtils.getTokenUrl(code));
request.addHeader("User-Agent", USER_AGENT);
HttpResponse response = client.execute(request);
BufferedReader rd = new BufferedReader(
new InputStreamReader(response.getEntity().getContent()));
String responseStr = rd.readLine();
String accessToken = responseStr.substring(responseStr.indexOf('=')+1,responseStr.indexOf('&'));
//添加請求頭
HttpClient client2 = HttpClientBuilder.create().build();
HttpGet request2 = new HttpGet(GithubUtils.getInfoUrl(accessToken));
HttpResponse response2 = client.execute(request2);
BufferedReader rd2 = new BufferedReader(
new InputStreamReader(response2.getEntity().getContent()));
String line = "";
StringBuilder result = new StringBuilder();
while ((line = rd2.readLine()) != null) {
result.append(line);
}
String infos = result.toString();
System.out.println(infos);
JSONObject info = JSONObject.parseObject(infos);
System.out.println(info.getString("login"));
System.out.println(info.getString("id"));
System.out.println(info.getString("node_id"));
System.out.println(info.getString("avatar_url"));
System.out.println(info.getString("bio"));
System.out.println(info.getString("name"));
System.out.println(info.getString("html_url"));
}
🎉完成!