前言
最近在學(xué)習(xí)oauth2授權(quán)登錄流程,oauth2簡單來說就是接入第三方應(yīng)用(qq、微信、github、gitee等),不用在本站登錄,而是去請(qǐng)求第三方應(yīng)用的用戶信息完成登錄。
下面就一起來看一下如何接入github實(shí)現(xiàn)第三方登錄
前置操作
首先,我們需要在github中添加OAuth App,登錄你的github(如果還有無法登錄github的,請(qǐng)?jiān)谠u(píng)論區(qū)留言或私信我)
點(diǎn)擊注冊
如果你成功執(zhí)行到這里,那么就恭喜你已經(jīng)把前置工作完成了
實(shí)戰(zhàn)演練
下面就以我個(gè)人的博客代碼為例,和大家一起實(shí)現(xiàn)github第三方登錄
博客地址:www.yuuu.online,
項(xiàng)目地址:蔚澤華 (yuuu-zehua) - Gitee.com
業(yè)務(wù)流程
我這里先說一下我項(xiàng)目的業(yè)務(wù)流程:用戶第一次使用github登錄,那么我會(huì)讓其與我的網(wǎng)站進(jìn)行綁定(郵箱);如果是非第一次使用,那么就直接登錄
話不多說,直接進(jìn)入!
業(yè)務(wù)代碼
因?yàn)槲业捻?xiàng)目是前后端分離的,所以一切請(qǐng)求都要過一遍前端代理,因此我寫了一個(gè)Loading頁,這也是我添加的github回調(diào)地址。
// Loading頁的執(zhí)行方法
oauth() {
const urlParams = new URLSearchParams(window.location.search);
const code = urlParams.get('code');
const state = urlParams.get('state');
if (code && code != '') {
request.post('user/oauth?code=' + code + '&state=' + state).then(response => {
console.log(response);
if(response === undefined){
this.$router.push({ path: '/Login?login=9' });
}else{
setToken(response.token)
localStorage.setItem("userInfo", JSON.stringify(response.userInfo))
this.$router.push({ path: '/' });
}
})
}
},
在這里會(huì)根據(jù)后端的返回去判斷是第一次登錄還是非第一次登錄,下面是后端接口實(shí)現(xiàn)
public ResponseResult oauth(String code) {
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
BlogUserLoginVo blogUserLoginVo;
try {
String oauthToken = getOauthToken(code);
GithubUser githubUser = getUserInfoWithGithub(oauthToken);
queryWrapper.eq(User::getGithubId, githubUser.getId());
User one = getOne(queryWrapper);
if (one != null) {
// 已經(jīng)綁定過用戶信息,直接登錄
blogUserLoginVo = getBlogUserLoginVo(one);
return ResponseResult.okResult(blogUserLoginVo);
} else {
redisCache.setCacheObject(code,githubUser);//注意這里
Map<Integer,String> map = new HashMap<>();
map.put(999, "未綁定");
return ResponseResult.okResult(map);
}
} catch (IOException e) {
throw new SystemException(AppHttpCodeEnum.SYSTEM_ERROR);
}
}
private BlogUserLoginVo getBlogUserLoginVo(User user) {
// 獲取userId,生成token
String jwt = JwtUtil.createJWT(user.getId().toString());
// 把用戶信息存入redis
LoginUser loginUser = new LoginUser();
loginUser.setUser(user);
loginUser.setPermissions(null);
redisCache.setCacheObject(SystemConstants.BLOG_LOGIN_KEY + user.getId(), loginUser, 1800, TimeUnit.SECONDS);
// 把token和userInfo封裝返回
// 把user轉(zhuǎn)換為userInfo
UserInfoVo userInfoVo = BeanCopyUtils.copyBean(user, UserInfoVo.class);
BlogUserLoginVo blogUserLoginVo = new BlogUserLoginVo(jwt, userInfoVo);
return blogUserLoginVo;
}
private String getOauthToken(String code) throws IOException {
String url = "https://github.com/login/oauth/access_token";
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON));
String requestBody = String.format("{\"client_id\":\"%s\",\"client_secret\":\"%s\",\"code\":\"%s\"}",
clientId, clientSecret, code);
HttpEntity<String> request = new HttpEntity<>(requestBody, headers);
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.POST, request, String.class);
if (response.getStatusCode().is2xxSuccessful()) {
// 解析JSON響應(yīng),獲取access_token字段
String jsonString = JSON.toJSONString(response);
JSONObject jsonObject = JSON.parseObject(jsonString);
JSONObject body = jsonObject.getJSONObject("body");
Object token = body.get("access_token");
return token.toString();
} else {
throw new SystemException(AppHttpCodeEnum.SYSTEM_ERROR);
}
}
private GithubUser getUserInfoWithGithub(String oauthToken) throws IOException {
String url = "https://api.github.com/user";
HttpHeaders headers = new HttpHeaders();
headers.set("Authorization", "Bearer " + oauthToken);
headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON));
HttpEntity<Void> requestEntity = new HttpEntity<>(headers);
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<String> responseEntity = restTemplate.exchange(url, HttpMethod.GET, requestEntity, String.class);
if (responseEntity.getStatusCode().is2xxSuccessful()) {
String body = responseEntity.getBody();
cn.hutool.json.JSONObject jsonObject = JSONUtil.parseObj(body);
GithubUser githubUser = jsonObject.toBean(GithubUser.class);
return githubUser;
} else {
throw new SystemException(AppHttpCodeEnum.SYSTEM_ERROR);
}
}
相關(guān)實(shí)體類,其實(shí)內(nèi)容有很多,但是在我項(xiàng)目里我只抽出了其中返回的幾個(gè)字段
public class GithubUser {
private String login;
private Integer id;
private String nodeId;
private String avatarUrl;
}
到這里,如果用戶已經(jīng)綁定過郵箱信息,那么就可以直接登錄主頁。如果用戶沒有綁定,就會(huì)跳轉(zhuǎn)到綁定頁。
這是我的綁定頁
綁定的代碼也和前面的差不多
前端代碼
oauthWithGithub() {
const urlParams = new URLSearchParams(window.location.search);
const code = urlParams.get('code');
const state = urlParams.get('state');
if (!this.emailCaptchaUpdate) {
this.$message.error('驗(yàn)證碼不能為空');
} else {
request.post('/user/oauth/github?code='+code, {
emailPassword: this.emailPassword,
confirmNewPassword: this.confirmNewPassword,
emailCaptchaUpdate: this.emailCaptchaUpdate,
})
.then((response) => {
console.log(response)
this.$alert('綁定成功', '', {
confirmButtonText: '確定'
})
this.isButtonDisabled2 = false;
this.emailPassword = '';
this.emailCaptchaUpdate = '';
setToken(response.token)
localStorage.setItem("userInfo", JSON.stringify(response.userInfo))
this.$router.push({ path: '/' });
})
.catch((error) => {
console.log(error);
});
}
},
后端接口
public ResponseResult oauthGithub(ChangePassword user,String code) {
String email = user.getEmailPassword();
String captcha = user.getEmailCaptchaUpdate();
if (StrUtil.isBlank(email)) {
throw new SystemException(AppHttpCodeEnum.EMAIL_ISNULL);
}
if (StrUtil.isBlank(captcha)) {
throw new SystemException(AppHttpCodeEnum.CAPTCHA_ISNULL);
}
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(User::getEmail, email);
String cacheCaptcha = redisCache.getCacheObject(SystemConstants.MAIL_KEY + captcha);
if (!captcha.equals(cacheCaptcha)) {
return ResponseResult.errorResult(601, "請(qǐng)?jiān)贆z查一下驗(yàn)證碼~");
} else {
redisCache.deleteObject(SystemConstants.MAIL_KEY + captcha);
}
User existingUser = getOne(queryWrapper);
BlogUserLoginVo blogUserLoginVo;
// 還記得上面讓大家注意的地方嗎?如果忘記了速速回滾查看!
// 因?yàn)間ithub的code只能用一次,本來想使用ThreadLocal,但是實(shí)現(xiàn)起來總報(bào)錯(cuò),就直接用
// redis存儲(chǔ)
GithubUser githubUser = redisCache.getCacheObject(code);
if (existingUser == null) {
// 新增
User userByGithub = new User();
userByGithub.setGithubId(githubUser.getId());
userByGithub.setAvatar(githubUser.getAvatarUrl());
userByGithub.setEmail(user.getEmailPassword());
userByGithub.setPassword(passwordEncoder.encode(SystemConstants.DEFAULT_PASSWORD));
userByGithub.setNickName(githubUser.getNodeId());
userByGithub.setUserName(githubUser.getLogin());
save(userByGithub);
// 用戶登錄
blogUserLoginVo = getBlogUserLoginVo(userByGithub);
} else {
// 修改
existingUser.setGithubId(githubUser.getId());
updateById(existingUser);
// 用戶登錄
blogUserLoginVo = getBlogUserLoginVo(existingUser);
}
// 用完就及時(shí)刪除,避免資源浪費(fèi)
redisCache.deleteObject(code);
return ResponseResult.okResult(blogUserLoginVo);
}
到這里,就實(shí)現(xiàn)了全部的功能,大家可以去盡情的嘗試了!
總結(jié)
Oauth2實(shí)現(xiàn)的原理就是拿code去換第三方的token,然后再用token去獲取用戶信息,看起來很容易,但是實(shí)現(xiàn)起來有點(diǎn)麻煩,其實(shí)也不是難,就是麻煩,你需要去看每個(gè)廠商的api文檔,每一個(gè)還都不一樣,就比如github和gitee。
我想大家在實(shí)現(xiàn)時(shí)也會(huì)常常出現(xiàn)接口超時(shí),沒有辦法,我的代碼在本地不會(huì)超時(shí),但是部署在華為云服務(wù)器上就反復(fù)報(bào)接口超時(shí),后面我也會(huì)進(jìn)行優(yōu)化。謝謝大家觀看。
后續(xù)
后面也是成功解決了github超時(shí)的問題,給大家說一下方法。文章來源:http://www.zghlxwxcb.cn/news/detail-764279.html
我是自己在Cloudflare開了一個(gè)代理,把自己的域名代理到了github
如果有需要我的代理域名的小伙伴可以私信我。如果有需要出教程的也可以在評(píng)論區(qū)留言,需要的人多了就繼續(xù)手摸手實(shí)現(xiàn)github代理。文章來源地址http://www.zghlxwxcb.cn/news/detail-764279.html
到了這里,關(guān)于手摸手接入Github實(shí)現(xiàn)Oauth2第三方登錄的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!