随着互联网的发展,第三方登录已经成为现代网站不可或缺的功能。它不仅简化了用户的注册和登录流程,还提高了用户体验。作为国内领先的代码托管平台,Gitee提供了OAuth2.0认证服务,允许开发者在自己的网站中集成Gitee账号登录功能。本文将详细介绍如何在网站中实现Gitee第三方登录,以及如何将Gitee账号与网站主账号进行绑定。
在实现Gitee登录之前,我们需要在Gitee平台上创建一个OAuth应用。
回调地址是用户在Gitee上授权后,Gitee将用户重定向回您网站的URL。这个URL必须与您在代码中设置的redirect_uri完全一致,否则授权将失败。
Gitee登录的实现基于OAuth 2.0协议,主要包括以下步骤:
下面是具体的实现代码:
function giteeLogin() {
const clientId = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX';
const redirectUri = encodeURIComponent('您的回调地址');
window.location.href = `https://gitee.com/oauth/authorize?client_id=${clientId}&redirect_uri=${redirectUri}&response_type=code`;
}
以下是Java实现的Gitee登录处理方法:
@Override
public HashMap<String, Object> giteeLogin(String code) {
// 第一步:使用授权码获取访问令牌
String url = "https://gitee.com/oauth/token";
HashMap<String, Object> paramMap = new HashMap<>();
paramMap.put("grant_type", "authorization_code");
paramMap.put("client_id", "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX");
paramMap.put("client_secret", "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX");
paramMap.put("code", code);
paramMap.put("redirect_uri", redirectUrl);
String result = HttpUtil.post(url, paramMap);
JSONObject tokenInfo = new JSONObject(result);
if (tokenInfo.get("access_token") != null) {
// 第二步:使用访问令牌获取用户信息
String url2 = "https://gitee.com/api/v5/user";
HashMap<String, Object> paramMap2 = new HashMap<>();
paramMap2.put("access_token", tokenInfo.get("access_token"));
String result2 = HttpUtil.get(url2, paramMap2);
JSONObject giteeInfo = new JSONObject(result2);
// 第三步:检查该Gitee账号是否已绑定系统用户
UserThirdParty userThirdPartyInfo = userThirdPartyService.getByPlatformAndOpenId("gitee", giteeInfo.getStr("id"));
if (userThirdPartyInfo == null) {
// 第四步A:如果未绑定,生成绑定令牌并返回用户信息,等待绑定
String bindToken = UUID.randomUUID().toString().replaceAll("-", "");
HashMap<String, Object> responseData = new HashMap<>();
responseData.put("needBind", true);
responseData.put("bindToken", bindToken);
responseData.put("platform", "gitee");
responseData.put("openId", giteeInfo.getStr("id"));
responseData.put("nickname", giteeInfo.getStr("name"));
responseData.put("avatar", giteeInfo.getStr("avatar_url"));
responseData.put("remark", giteeInfo.getStr("bio"));
// 将绑定信息存入Redis,设置10分钟过期时间
redisUtil.hmset("USER:BAND:" + bindToken, responseData, 600);
return responseData;
} else {
// 第四步B:如果已绑定,更新第三方账号信息并直接登录
userThirdPartyInfo.setNickname(giteeInfo.getStr("name"));
userThirdPartyInfo.setAvatar(giteeInfo.getStr("avatar_url"));
userThirdPartyInfo.setRemark(giteeInfo.getStr("bio"));
userThirdPartyService.updateById(userThirdPartyInfo);
// 获取绑定的系统用户并设置登录信息
User user = userService.getById(userThirdPartyInfo.getUserId());
return setUserInfo(user);
}
} else {
// 获取访问令牌失败
throw new RuntimeException(tokenInfo.getStr("error_description"));
}
}
当用户使用Gitee登录,但系统中没有对应的绑定记录时,我们需要提供一种机制让用户将Gitee账号与已有的系统账号绑定,或者创建一个新账号。
<div v-if="needBind">
<h2>绑定Gitee账号</h2>
<div class="gitee-info">
<img :src="giteeInfo.avatar" alt="头像">
<p>Gitee昵称: {{giteeInfo.nickname}}</p>
</div>
<div class="bind-options">
<div class="bind-existing">
<h3>绑定已有账号</h3>
<form @submit.prevent="bindExisting">
<input type="text" v-model="username" placeholder="用户名">
<input type="password" v-model="password" placeholder="密码">
<button type="submit">绑定并登录</button>
</form>
</div>
<div class="bind-new">
<h3>注册新账号并绑定</h3>
<button @click="goToRegister">去注册</button>
</div>
</div>
</div>
@Override
public HashMap<String, Object> bindGiteeAccount(String bindToken, Long userId) {
// 从Redis获取绑定信息
Map<Object, Object> bindInfo = redisUtil.hmget("USER:BAND:" + bindToken);
if (bindInfo == null || bindInfo.isEmpty()) {
throw new RuntimeException("绑定信息已过期,请重新授权");
}
// 检查该用户是否已绑定其他Gitee账号
UserThirdParty existingBinding = userThirdPartyService.getByUserIdAndPlatform(userId, "gitee");
if (existingBinding != null) {
throw new RuntimeException("该账号已绑定其他Gitee账号,请先解绑");
}
// 检查该Gitee账号是否已绑定其他用户
UserThirdParty giteeBinding = userThirdPartyService.getByPlatformAndOpenId(
"gitee", (String) bindInfo.get("openId"));
if (giteeBinding != null) {
throw new RuntimeException("该Gitee账号已绑定其他用户,请先解绑");
}
// 创建绑定关系
UserThirdParty userThirdParty = new UserThirdParty();
userThirdParty.setUserId(userId);
userThirdParty.setPlatform("gitee");
userThirdParty.setOpenId((String) bindInfo.get("openId"));
userThirdParty.setNickname((String) bindInfo.get("nickname"));
userThirdParty.setAvatar((String) bindInfo.get("avatar"));
userThirdParty.setRemark((String) bindInfo.get("remark"));
userThirdParty.setCreateTime(new Date());
userThirdPartyService.save(userThirdParty);
// 删除Redis中的临时绑定信息
redisUtil.del("USER:BAND:" + bindToken);
// 获取用户信息并返回登录结果
User user = userService.getById(userId);
return setUserInfo(user);
}
有时用户可能需要解除Gitee账号与系统账号的绑定,我们也需要提供相应的接口:
@Override
public boolean unbindGiteeAccount(Long userId) {
// 检查是否存在绑定关系
UserThirdParty binding = userThirdPartyService.getByUserIdAndPlatform(userId, "gitee");
if (binding == null) {
throw new RuntimeException("未找到绑定的Gitee账号");
}
// 检查用户是否只有Gitee登录方式
// 如果是,则不允许解绑,以防用户无法登录
boolean hasPassword = userService.checkHasPassword(userId);
boolean hasOtherThirdParty = userThirdPartyService.countByUserId(userId) > 1;
if (!hasPassword && !hasOtherThirdParty) {
throw new RuntimeException("您当前只有Gitee登录方式,解绑后将无法登录,请先设置密码");
}
// 执行解绑
return userThirdPartyService.removeById(binding.getId());
}
在实现第三方登录时,安全性是非常重要的考虑因素:
为了提供更好的用户体验,可以考虑以下几点:
在前端实现Gitee登录时,我们需要创建一个登录按钮,并在用户点击时将其重定向到Gitee的授权页面。以下是一个基于Vue的实现示例:
<template>
<div class="login-container">
<!-- 常规登录表单 -->
<div class="normal-login">
<h3>账号密码登录</h3>
<el-form>
<!-- 常规登录表单内容 -->
</el-form>
</div>
<!-- 第三方登录区域 -->
<div class="third-party-login">
<h3>第三方账号登录</h3>
<div class="login-icons">
<div class="login-item" @click="handleGiteeLogin">
<img src="/static/images/gitee-logo.png" alt="Gitee登录">
<span>Gitee登录</span>
</div>
<!-- 其他第三方登录图标 -->
</div>
</div>
<!-- 绑定弹窗 -->
<el-dialog title="绑定Gitee账号" :visible.sync="bindDialogVisible" width="500px">
<div class="bind-dialog-content">
<div class="gitee-user-info">
<img :src="giteeUserInfo.avatar" class="avatar">
<div class="info">
<p class="nickname">{{giteeUserInfo.nickname}}</p>
<p class="bio">{{giteeUserInfo.remark || '这个人很懒,什么都没留下'}}</p>
</div>
</div>
<div class="bind-options">
<div class="option-item">
<h4>绑定已有账号</h4>
<el-form>
<el-form-item label="用户名">
<el-input v-model="bindForm.username"></el-input>
</el-form-item>
<el-form-item label="密码">
<el-input type="password" v-model="bindForm.password"></el-input>
</el-form-item>
<el-button type="primary" @click="bindExistingAccount">绑定并登录</el-button>
</el-form>
</div>
<div class="option-item">
<h4>注册新账号并绑定</h4>
<el-button @click="goToRegister">去注册</el-button>
</div>
</div>
</div>
</el-dialog>
</div>
</template>
<script>
export default {
data() {
return {
bindDialogVisible: false,
giteeUserInfo: {
nickname: '',
avatar: '',
remark: '',
bindToken: '',
openId: ''
},
bindForm: {
username: '',
password: ''
}
}
},
created() {
// 检查URL中是否有code参数,有则说明是从Gitee回调回来的
const code = this.$route.query.code;
if (code) {
this.handleGiteeCallback(code);
}
},
methods: {
// 跳转到Gitee授权页面
handleGiteeLogin() {
const clientId = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX';
const redirectUri = encodeURIComponent(window.location.origin + '/login'); // 回调到当前登录页
const state = this.generateRandomString(16); // 生成随机state防止CSRF攻击
localStorage.setItem('gitee_auth_state', state);
window.location.href = `https://gitee.com/oauth/authorize?client_id=${clientId}&redirect_uri=${redirectUri}&response_type=code&state=${state}`;
},
// 处理Gitee回调
async handleGiteeCallback(code) {
// 验证state参数防止CSRF攻击
const savedState = localStorage.getItem('gitee_auth_state');
const callbackState = this.$route.query.state;
if (savedState !== callbackState) {
this.$message.error('安全验证失败,请重新登录');
return;
}
try {
// 调用后端接口处理Gitee登录
const res = await this.$api.login.giteeLogin({ code });
if (res.data.needBind) {
// 需要绑定账号
this.giteeUserInfo = res.data;
this.bindDialogVisible = true;
} else {
// 已绑定账号,直接登录成功
this.$store.commit('SET_TOKEN', res.data.token);
this.$store.commit('SET_USER_INFO', res.data.userInfo);
this.$router.push('/dashboard');
}
} catch (error) {
this.$message.error('Gitee登录失败: ' + error.message);
}
},
// 绑定已有账号
async bindExistingAccount() {
try {
// 先验证用户名密码
const loginRes = await this.$api.login.login({
username: this.bindForm.username,
password: this.bindForm.password
});
// 验证成功后,绑定Gitee账号
const bindRes = await this.$api.user.bindGiteeAccount({
bindToken: this.giteeUserInfo.bindToken,
userId: loginRes.data.userInfo.id
});
// 绑定成功,设置登录状态
this.$store.commit('SET_TOKEN', bindRes.data.token);
this.$store.commit('SET_USER_INFO', bindRes.data.userInfo);
this.bindDialogVisible = false;
this.$router.push('/dashboard');
this.$message.success('账号绑定成功');
} catch (error) {
this.$message.error('绑定失败: ' + error.message);
}
},
// 去注册页面
goToRegister() {
// 将bindToken传递到注册页面,注册成功后自动绑定
this.$router.push({
path: '/register',
query: { bindToken: this.giteeUserInfo.bindToken }
});
this.bindDialogVisible = false;
},
// 生成随机字符串用于state参数
generateRandomString(length) {
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
let result = '';
for (let i = 0; i < length; i++) {
result += chars.charAt(Math.floor(Math.random() * chars.length));
}
return result;
}
}
}
</script>
为了支持第三方账号登录和绑定,我们需要设计相应的数据库表结构:
CREATE TABLE `user_third_party` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`user_id` bigint(20) NOT NULL COMMENT '用户ID',
`platform` varchar(50) NOT NULL COMMENT '平台(gitee, github, wechat等)',
`open_id` varchar(100) NOT NULL COMMENT '平台用户唯一标识',
`nickname` varchar(100) DEFAULT NULL COMMENT '平台用户昵称',
`avatar` varchar(255) DEFAULT NULL COMMENT '平台用户头像',
`remark` varchar(500) DEFAULT NULL COMMENT '备注信息',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_platform_openid` (`platform`,`open_id`),
KEY `idx_user_id` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户第三方账号绑定表';
在用户表中,我们可能需要添加一些字段来标识用户的注册来源和登录方式:
ALTER TABLE `user`
ADD COLUMN `register_source` varchar(50) DEFAULT 'system' COMMENT '注册来源(system, gitee, github等)',
ADD COLUMN `last_login_type` varchar(50) DEFAULT NULL COMMENT '最后登录方式(password, gitee, github等)',
ADD COLUMN `last_login_time` datetime DEFAULT NULL COMMENT '最后登录时间';
下面是一个完整的Gitee登录流程示例,包括前端和后端的交互:
前端将用户重定向到Gitee授权页面:
https://gitee.com/oauth/authorize?client_id=XXXXXXXXXXXX&redirect_uri=https://your-website.com/login&response_type=code&state=random_state_string
用户在Gitee页面上点击"授权"按钮后,Gitee将用户重定向回您的网站,并附带授权码:
https://your-website.com/login?code=authorization_code_here&state=random_state_string
// 从URL中获取code参数
const urlParams = new URLSearchParams(window.location.search);
const code = urlParams.get('code');
const state = urlParams.get('state');
// 验证state参数
if (state !== localStorage.getItem('gitee_auth_state')) {
alert('安全验证失败');
return;
}
// 调用后端接口
fetch('/api/login/gitee', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ code })
})
.then(response => response.json())
.then(data => {
if (data.needBind) {
// 显示绑定界面
showBindDialog(data);
} else {
// 登录成功,保存token和用户信息
localStorage.setItem('token', data.token);
localStorage.setItem('userInfo', JSON.stringify(data.userInfo));
window.location.href = '/dashboard';
}
})
.catch(error => {
console.error('Gitee登录失败:', error);
alert('登录失败,请重试');
});
后端接收到前端传来的授权码后,执行以下步骤:
如果用户未绑定,前端显示绑定界面,用户可以选择:
通过本文的介绍,我们详细了解了如何在网站中实现Gitee第三方登录功能,以及如何处理账号绑定的相关问题。集成第三方登录不仅可以简化用户的注册和登录流程,还能提高用户体验和网站的专业性。希望本文对您实现Gitee登录功能有所帮助。
在实际开发中,还可以根据具体需求进行更多的定制和优化,例如添加更多的第三方登录平台,或者实现更复杂的账号关联逻辑。无论如何,确保安全性和用户体验始终是最重要的考虑因素。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。