GitHub OAuth授權登錄
OAuth
第三方登入主要基於OAuth 2.0。OAuth(開放授權)協議是一個開放標準,爲用戶資源的授權提供了一個安全的、開放而又簡易的標準。與以往的授權方式不同之處是OAUTH的授權不會使第三方觸及到用戶的帳號信息(如用戶名與密碼),即第三方無需使用用戶的用戶名與密碼就可以申請獲得該用戶資源的授權,因此OAUTH是安全的
---- 百度百科
實現步驟
GitHub OAuth API官方文檔
- 創建OAuth App
這裏的Authorization callback URL在後文中會介紹
這裏僅做本地開發,所以填了本地地址,如果項目部署好了,應該填http://xxx/callback
打開OAuth documentation查看官方文檔
GitHub授權流程
- 這裏介紹瞭如何使自己的項目獲取GitHub授權的流程:
第一步:跳轉至GitHub去請求認證
第二步:認證成功後,由GitHub跳轉回我自己的項目
第三步:我自己的項目通過GitHub跳轉回來時帶的access token
去訪問GitHub API
Step1.
這是第一步去GitHub認證的request所需帶的參數,通過
GET
方式發送該請求https://github.com/login/oauth/authorize
,並帶參數
註冊OAuth App後會提供一個client_id
認證成功後回跳轉到redirect_uri
頁面
login
參數指定一個賬戶去獲取授權
scope
是授權後我們想要拿到的信息,這裏我只需要user
state
參數非必需,用於防治跨域僞造請求攻擊
allow_signup
就是是否向未認證的用戶提供註冊GitHub的選項,默認是允許的
####Step2
如果GitHub用戶接受該授權請求,則GitHub將帶着
code
和state
參數重定向到自己之前註冊的那個callback地址,我這裏填的是http://localhost:8080/callback
,因此授權成功後會跳轉至下面這個網址,這裏看到帶了code
參數,state
就是上一步請求中帶的state
參數,原樣返回。這裏服務端需要接收這個code
和state
參數用於之後獲取access_token
。
http://localhost:8080/github/oauth/callback?code=14de2c737aa02037132d&state=1496989988474
拿到請求中的
code
參數後服務端向https://github.com/login/oauth/access_token
這個API發送POST
請求,並且在該請求中帶上client_id
,client_secret
,code
參數,請求成功後會返回帶有access_token
的信息。
這裏請求返回的Json格式信息是
access_token=e72e16c7e42f292c6912e7710c838347ae178b4a&token_type=bearer
,而我們需要的token是e72e16c7e42f292c6912e7710c838347ae178b4a
,因此在處理時要注意。
Step3.
獲取到
access_token
後, 再調用https://api.github.com/user?access_token=xxx
這個API,就可以獲取之前scope
中對應的GitHub用戶信息。 用戶的基本信息內容如下所示, 根據第一步傳入的不同的 scope,獲取到的用戶信息也是不同的,博客後臺使用 login 字段作爲用戶的唯一標示。
整個流程的順序圖
這裏再盜一張linwalker在第三方登入例子-GitHub授權登入(node-koa)中的圖
實現
Step1.
點擊“登錄”,向GitHub發送授權請求
<li th:if="${session.user == null}">
<a href="https://github.com/login/oauth/authorize?client_id=xxx&redirect_uri=http://localhost:8080/callback&scope=user&state=1">登錄</a>
</li>
Step2.
GitHub帶參數code回調callback地址
服務端拿到code參數POST請求GitHub獲取access_token
GitHub返回access_token
服務端拿到access_token請求用戶信息,之後將該用戶信息存到數據庫實現持久化
AuthorizeController
處理callback
頁面
@Controller
public class AuthorizeController {
@Autowired
private GithubProvider githubProvider;
//在application.properties中配置github.client.id等參數
@Value("${github.client.id}")
private String clientId;
@Value("${github.client.secret}")
private String clientSecret;
@Value("${github.redirect.uri}")
private String redirectUri;
@Autowired
private UserMapper userMapper;
@GetMapping("/callback")
public String callback(@RequestParam(name = "code") String code,
@RequestParam(name = "state") String state,
HttpServletResponse response) {
AccessTokenDTO accessTokenDTO = new AccessTokenDTO();
accessTokenDTO.setClient_id(clientId);
accessTokenDTO.setClient_secret(clientSecret);
accessTokenDTO.setCode(code);
accessTokenDTO.setRedirect_uri(redirectUri);
accessTokenDTO.setState(state);
//通過accessTokenDTO獲取相應的access_token
String accessToken = githubProvider.getAccessToken(accessTokenDTO);
//再由獲取的access_token獲取GitHub用戶GithubUser對象
GithubUser githubUser = githubProvider.getUser(accessToken);
/**
* 登陸成功,寫Cookie
* 新建一個論壇用戶並將其信息和該GitHub用戶信息綁定起來,並將相關信息保存到數據庫
*/
if (githubUser != null && githubUser.getId() != null) {
User user = new User();
String token = UUID.randomUUID().toString();
user.setToken(token);
user.setName(githubUser.getName());
user.setAccountId(String.valueOf(githubUser.getId()));
user.setGmtCreate(System.currentTimeMillis());
user.setGmtModified(user.getGmtCreate());
user.setAvatarUrl(githubUser.getAvatarUrl());
//將新用戶對象插入到數據庫中
userMapper.insert(user);
/**
* 並將token作爲Cookie加入到response中
*/
response.addCookie(new Cookie("token", token));
//重定向到首頁
return "redirect:/";
} else {
// 登錄失敗,重新登錄,重定向到首頁
return "redirect:/";
}
}
}
GithubProvider
是用於處理相關GET
、POST
請求的類
@Component
public class GithubProvider {
//getAccessToken通過參數以POST方式得到相應的access_token
public String getAccessToken(AccessTokenDTO accessTokenDTO) {
MediaType mediaType = MediaType.get("application/json; charset=utf-8");
//用OkHttp模擬Http的Post、Get請求
OkHttpClient client = new OkHttpClient();
//用FastJson將accessTokenDTO對象轉化爲Json字符串
RequestBody body = RequestBody.create(mediaType, JSON.toJSONString(accessTokenDTO));
Request request = new Request.Builder()
.url("https://github.com/login/oauth/access_token")
.post(body)
.build();
try (Response response = client.newCall(request).execute()) {
String string = response.body().string();
//對請求返回的Json字符串進行分割從而獲取token
String token = string.split("&")[0].split("=")[1];
return token;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
//getUser方法通過獲取的accessToken信息以GET方式去調API獲取GitHub用戶信息
public GithubUser getUser(String accessToken) {
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url("https://api.github.com/user?access_token=" + accessToken)
.build();
try {
Response response = client.newCall(request).execute();
String string = response.body().string();
//將接受到的包含GitHub user信息的Json字符串通過FastJson轉化爲GithubUser對象
GithubUser githubUser = JSON.parseObject(string, GithubUser.class);
return githubUser;
} catch (IOException e) {
}
return null;
}
}
OkHttp
本項目中採用OkHttp來模擬Http的GET
、POST
請求
Get a URL
OkHttpClient client = new OkHttpClient();
String run(String url) throws IOException {
Request request = new Request.Builder()
.url(url)
.build();
try (Response response = client.newCall(request).execute()) {
return response.body().string();
}
}
Post to a Server
public static final MediaType JSON
= MediaType.get("application/json; charset=utf-8");
OkHttpClient client = new OkHttpClient();
String post(String url, String json) throws IOException {
RequestBody body = RequestBody.create(json, JSON);
Request request = new Request.Builder()
.url(url)
.post(body)
.build();
try (Response response = client.newCall(request).execute()) {
return response.body().string();
}
}
這是通過獲取的accessToken信息去調
https://api.github.com/user?access_token=
這個API獲取GitHub用戶信息
我的論壇項目所需的信息包括id
作爲用戶唯一標識,name
作爲論壇暱稱,bio
作爲用戶簡述以及avatarUrl
作爲用戶頭像