Gitee快捷登录配置
在《Google快捷登录配置》中,我们接入了 Google 登录。这一篇接入 Gitee(码云)登录。对于面向国内开发者的网站来说,Gitee 登录不需要科学上网,授权页加载快,是非常实用的第三方登录方式。
和 Google Identity Services 直接在前端返回 ID Token 不同,Gitee 使用的是标准的 OAuth2 授权码模式(Authorization Code),核心区别是:
1 2
| Google:前端直接拿到 ID Token,后端只负责校验 Gitee:前端只能拿到授权码 code,必须由后端用 code + Client Secret 换取 Access Token,再调 Gitee API 获取用户信息
|
本文主要讲的是”使用 Gitee 账号登录网站”,不是调用 Gitee 的仓库、Issue 等开放 API。如果只是做快捷登录,拿到用户信息后就不需要长期保存 Access Token 和 Refresh Token。
一、整体登录流程
Gitee 快捷登录的核心流程如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| 用户点击"使用 Gitee 登录" ↓ 前端跳转到 Gitee 授权页 https://gitee.com/oauth/authorize ↓ 用户在 Gitee 登录并同意授权 ↓ Gitee 重定向回你配置的回调地址,URL 上带着 code 和 state ↓ 前端回调页把 code 发给后端 ↓ 后端用 code + Client Secret 向 Gitee 换取 Access Token ↓ 后端用 Access Token 调用 Gitee API 获取用户信息 ↓ 后端根据 Gitee 用户唯一 ID 查询或创建本地用户 ↓ 后端生成自己系统的登录态,比如 Session、JWT、Sa-Token ↓ 用户登录成功
|
和 Google 登录一样,最重要的原则不变:
1 2
| Gitee 只负责证明"这个 Gitee 用户是谁" 你自己的系统负责决定"这个用户在我系统里是谁、有什么权限、登录态怎么维护"
|
二、几个核心概念
1. Client ID 和 Client Secret
在 Gitee 创建第三方应用后,会得到一对凭证:
1 2
| Client ID:应用的公开标识,可以出现在前端跳转链接里 Client Secret:应用密钥,只能保存在后端,绝不能出现在前端代码、前端环境变量或 Git 仓库中
|
授权码模式中 Client Secret 是必须的,因为换取 Access Token 这一步必须由后端完成。
2. 授权码 code
用户在 Gitee 同意授权后,Gitee 会重定向回你的回调地址,并在 URL 上附带一个临时授权码:
1
| https://你的回调地址?code=xxxxxx&state=yyyyyy
|
code 的特点:
1 2 3
| 1. 一次性,用过即作废 2. 有效期很短,几分钟内必须使用 3. 单独拿到 code 没有用,还需要 Client Secret 才能换 token
|
3. Access Token
后端用 code 换到的访问令牌,用于调用 Gitee API。Gitee 的 Access Token 有效期是 86400 秒(一天),同时会返回 refresh_token 用于刷新。
做快捷登录时,换到 token 后马上调用一次用户信息接口即可,通常不需要把 Access Token 和 Refresh Token 落库。
4. state
state 是防 CSRF 攻击的随机字符串:
1 2 3
| 1. 前端跳转授权页之前生成随机 state,存入 sessionStorage 2. Gitee 回调时会原样带回 state 3. 前端(或后端)比对 state 是否一致,不一致直接拒绝
|
不带 state 的 OAuth 登录存在被伪造回调的风险,正式项目建议都带上。
5. Gitee 用户唯一 ID
Gitee 用户信息接口返回的 id 字段(数字)是用户唯一标识。
1 2 3
| id:数字,永远不变,作为绑定标识 login:用户名,用户可以修改,不能作为唯一标识 email:邮箱,可能为空(取决于用户隐私设置),不能作为唯一标识
|
第三方账号绑定表应该保存 id。
三、在 Gitee 创建第三方应用
1. 进入第三方应用管理
登录 Gitee,点击右上角头像,进入:
或者直接访问:
1
| https://gitee.com/oauth/applications
|
点击”创建应用”。
2. 填写应用信息
1 2 3 4
| 应用名称:你的应用名,例如 Yao Admin,用户在授权页会看到 应用描述:可选 应用主页:例如 https://yao-admin.com 应用回调地址:授权完成后 Gitee 重定向的地址
|
回调地址是最关键的一项,开发环境示例:
1
| http://localhost:5173/oauth/gitee/callback
|
生产环境示例:
1
| https://yao-admin.com/oauth/gitee/callback
|
注意事项:
1 2 3
| 1. 回调地址必须和后续请求中的 redirect_uri 完全一致,包括协议、域名、端口、路径 2. Gitee 一个应用支持添加多个回调地址(创建页面点 + 号),开发和生产地址可以同时填上 3. 回调地址指向前端页面(推荐)或后端接口都可以,本文使用前端回调页模式
|
3. 勾选权限范围
做快捷登录只需要勾选:
如果需要读取用户邮箱(用户设置了隐私时 user_info 拿不到邮箱),再勾选:
不要勾选 projects、pull_requests 等仓库权限,权限越少,用户授权时的心理负担越小。
4. 获取 Client ID 和 Client Secret
创建完成后,应用详情页会显示:
Client Secret 只保存在后端配置或环境变量中。
四、前端接入:跳转授权与回调页
前端要做两件事:登录按钮负责”跳过去”,回调页负责”接回来”。
1. 配置前端环境变量
以 Vite 项目为例,.env.development:
1 2
| VITE_GITEE_CLIENT_ID=你的GiteeClientID VITE_GITEE_REDIRECT_URI=http://localhost:5173/oauth/gitee/callback
|
.env.production:
1 2
| VITE_GITEE_CLIENT_ID=你的GiteeClientID VITE_GITEE_REDIRECT_URI=https://yao-admin.com/oauth/gitee/callback
|
2. 登录按钮:拼接授权地址并跳转
Gitee 授权地址格式:
1
| https://gitee.com/oauth/authorize?client_id=CLIENT_ID&redirect_uri=REDIRECT_URI&response_type=code&scope=user_info&state=STATE
|
React 示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| function buildState(): string { const state = crypto.randomUUID(); sessionStorage.setItem('gitee_oauth_state', state); return state; }
export default function GiteeLoginButton() { function handleClick() { const params = new URLSearchParams({ client_id: import.meta.env.VITE_GITEE_CLIENT_ID, redirect_uri: import.meta.env.VITE_GITEE_REDIRECT_URI, response_type: 'code', scope: 'user_info', state: buildState() });
window.location.href = `https://gitee.com/oauth/authorize?${params.toString()}`; }
return ( <button onClick={handleClick}> 使用 Gitee 登录 </button> ); }
|
Vue3 写法基本一致,把 handleClick 绑定到按钮的 @click 即可。
3. 回调页:取出 code 并发给后端
用户授权后,浏览器会回到:
1
| http://localhost:5173/oauth/gitee/callback?code=xxxxxx&state=yyyyyy
|
回调页组件(React 示例):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| import { useEffect } from 'react';
export default function GiteeCallback() { useEffect(() => { const params = new URLSearchParams(window.location.search); const code = params.get('code'); const state = params.get('state'); const savedState = sessionStorage.getItem('gitee_oauth_state');
sessionStorage.removeItem('gitee_oauth_state');
if (!code) { console.error('未获取到授权码'); window.location.href = '/login'; return; }
if (!state || state !== savedState) { console.error('state 校验失败,可能存在 CSRF 风险'); window.location.href = '/login'; return; }
handleGiteeLogin(code); }, []);
async function handleGiteeLogin(code: string) { const res = await fetch('/api/auth/gitee/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ code }) });
if (!res.ok) { throw new Error('Gitee 登录失败'); }
const data = await res.json(); localStorage.setItem('token', data.token); window.location.href = '/'; }
return <div>正在登录,请稍候...</div>; }
|
注意:React 18 开发模式下 useEffect 会执行两次,而 code 是一次性的,第二次请求必然失败。可以用 useRef 加一个执行标记,或者在生产构建下验证完整流程。
五、后端实现:换取 Access Token 并获取用户信息
后端要做的事情:
1 2 3 4 5 6
| 1. 接收前端传来的 code 2. 用 code + Client Secret 向 Gitee 换取 Access Token 3. 用 Access Token 调用 Gitee 用户信息接口 4. 查询或创建本地用户 5. 生成本系统登录 token 6. 返回给前端
|
下面以 Spring Boot 为例,不需要任何第三方 SDK,用 RestTemplate 就够了。
1. 配置 application.yml
1 2 3 4 5
| gitee: oauth: client-id: ${GITEE_CLIENT_ID} client-secret: ${GITEE_CLIENT_SECRET} redirect-uri: ${GITEE_REDIRECT_URI}
|
生产环境通过环境变量注入:
1 2 3
| export GITEE_CLIENT_ID="你的ClientID" export GITEE_CLIENT_SECRET="你的ClientSecret" export GITEE_REDIRECT_URI="https://yao-admin.com/oauth/gitee/callback"
|
注意:换 token 时传的 redirect_uri 必须和前端跳转授权时用的完全一致,所以后端也要配置同一个值。
2. 请求对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| import jakarta.validation.constraints.NotBlank;
public class GiteeLoginRequest {
@NotBlank(message = "code不能为空") private String code;
public String getCode() { return code; }
public void setCode(String code) { this.code = code; } }
|
3. Controller 接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| import jakarta.validation.Valid; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController;
@RestController @RequestMapping("/api/auth/gitee") public class GiteeAuthController {
private final GiteeAuthService giteeAuthService;
public GiteeAuthController(GiteeAuthService giteeAuthService) { this.giteeAuthService = giteeAuthService; }
@PostMapping("/login") public LoginResponse login(@Valid @RequestBody GiteeLoginRequest request) { return giteeAuthService.login(request.getCode()); } }
|
LoginResponse 和《Google快捷登录配置》中的完全一样,这里不再重复。
4. Token 响应对象
Gitee 换取 token 接口返回的 JSON:
1 2 3 4 5 6 7 8
| { "access_token": "xxxxxx", "token_type": "bearer", "expires_in": 86400, "refresh_token": "xxxxxx", "scope": "user_info", "created_at": 1718160000 }
|
对应的 Java 对象:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
| import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty;
@JsonIgnoreProperties(ignoreUnknown = true) public class GiteeTokenResponse {
@JsonProperty("access_token") private String accessToken;
@JsonProperty("token_type") private String tokenType;
@JsonProperty("expires_in") private Long expiresIn;
@JsonProperty("refresh_token") private String refreshToken;
private String scope;
public String getAccessToken() { return accessToken; }
public void setAccessToken(String accessToken) { this.accessToken = accessToken; }
public String getTokenType() { return tokenType; }
public void setTokenType(String tokenType) { this.tokenType = tokenType; }
public Long getExpiresIn() { return expiresIn; }
public void setExpiresIn(Long expiresIn) { this.expiresIn = expiresIn; }
public String getRefreshToken() { return refreshToken; }
public void setRefreshToken(String refreshToken) { this.refreshToken = refreshToken; }
public String getScope() { return scope; }
public void setScope(String scope) { this.scope = scope; } }
|
5. 用户信息响应对象
Gitee 用户信息接口返回的主要字段:
1 2 3 4 5 6 7 8
| { "id": 1234567, "login": "yao-coder", "name": "yao", "avatar_url": "https://foruda.gitee.com/avatar/xxx.png", "email": "yao@example.com", "html_url": "https://gitee.com/yao-coder" }
|
对应的 Java 对象:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
| import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty;
@JsonIgnoreProperties(ignoreUnknown = true) public class GiteeUserInfo {
private Long id;
private String login;
private String name;
@JsonProperty("avatar_url") private String avatarUrl;
private String email;
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getLogin() { return login; }
public void setLogin(String login) { this.login = login; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getAvatarUrl() { return avatarUrl; }
public void setAvatarUrl(String avatarUrl) { this.avatarUrl = avatarUrl; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; } }
|
6. 调用 Gitee 接口的 Service
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
| import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.stereotype.Service; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.web.client.RestTemplate;
@Service public class GiteeApiService {
private static final String TOKEN_URL = "https://gitee.com/oauth/token"; private static final String USER_URL = "https://gitee.com/api/v5/user";
private final String clientId; private final String clientSecret; private final String redirectUri; private final RestTemplate restTemplate;
public GiteeApiService(@Value("${gitee.oauth.client-id}") String clientId, @Value("${gitee.oauth.client-secret}") String clientSecret, @Value("${gitee.oauth.redirect-uri}") String redirectUri) { this.clientId = clientId; this.clientSecret = clientSecret; this.redirectUri = redirectUri; this.restTemplate = new RestTemplate(); }
public String getAccessToken(String code) { HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
MultiValueMap<String, String> params = new LinkedMultiValueMap<>(); params.add("grant_type", "authorization_code"); params.add("code", code); params.add("client_id", clientId); params.add("client_secret", clientSecret); params.add("redirect_uri", redirectUri);
HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(params, headers);
GiteeTokenResponse response = restTemplate.postForObject(TOKEN_URL, request, GiteeTokenResponse.class);
if (response == null || response.getAccessToken() == null) { throw new RuntimeException("获取Gitee AccessToken失败"); }
return response.getAccessToken(); }
public GiteeUserInfo getUserInfo(String accessToken) { String url = USER_URL + "?access_token=" + accessToken;
GiteeUserInfo userInfo = restTemplate.getForObject(url, GiteeUserInfo.class);
if (userInfo == null || userInfo.getId() == null) { throw new RuntimeException("获取Gitee用户信息失败"); }
return userInfo; } }
|
常见错误处理建议:code 无效或过期时,Gitee 会返回 401 或错误 JSON,RestTemplate 默认会抛出 HttpClientErrorException,实际项目中建议捕获后转换成业务异常,给前端一个明确的”授权已过期,请重新登录”提示。
六、数据库表设计
直接复用《Google快捷登录配置》中的两张表:
1 2
| sys_user:用户表 sys_oauth_account:第三方账号绑定表
|
Gitee 登录时的绑定数据:
1 2
| provider = gitee provider_user_id = Gitee 用户信息中的 id(转成字符串保存)
|
这正是当初把 provider 设计成字段而不是写死的好处,接入第二、第三个登录方式时,表结构完全不用动。
七、登录业务逻辑
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84
| import cn.dev33.satoken.stp.StpUtil; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional;
@Service public class GiteeAuthService {
private static final String PROVIDER_GITEE = "gitee";
private final GiteeApiService giteeApiService; private final UserRepository userRepository; private final OAuthAccountRepository oAuthAccountRepository;
public GiteeAuthService(GiteeApiService giteeApiService, UserRepository userRepository, OAuthAccountRepository oAuthAccountRepository) { this.giteeApiService = giteeApiService; this.userRepository = userRepository; this.oAuthAccountRepository = oAuthAccountRepository; }
@Transactional(rollbackFor = Exception.class) public LoginResponse login(String code) { String accessToken = giteeApiService.getAccessToken(code);
GiteeUserInfo giteeUserInfo = giteeApiService.getUserInfo(accessToken);
OAuthAccount oauthAccount = oAuthAccountRepository.findByProviderAndProviderUserId( PROVIDER_GITEE, String.valueOf(giteeUserInfo.getId()) );
User user;
if (oauthAccount != null) { user = userRepository.findById(oauthAccount.getUserId()); if (user == null) { throw new RuntimeException("绑定的本地用户不存在"); } } else { user = createUser(giteeUserInfo);
OAuthAccount newOAuthAccount = new OAuthAccount(); newOAuthAccount.setUserId(user.getId()); newOAuthAccount.setProvider(PROVIDER_GITEE); newOAuthAccount.setProviderUserId(String.valueOf(giteeUserInfo.getId())); newOAuthAccount.setEmail(giteeUserInfo.getEmail()); newOAuthAccount.setNickname(giteeUserInfo.getName()); newOAuthAccount.setAvatar(giteeUserInfo.getAvatarUrl()); oAuthAccountRepository.save(newOAuthAccount); }
if (user.getStatus() == 0) { throw new RuntimeException("当前用户已被禁用"); }
StpUtil.login(user.getId()); String token = StpUtil.getTokenValue();
return new LoginResponse( token, new LoginResponse.UserInfo( user.getId(), user.getNickname(), user.getEmail(), user.getAvatar() ) ); }
private User createUser(GiteeUserInfo giteeUserInfo) { User newUser = new User(); newUser.setEmail(giteeUserInfo.getEmail()); newUser.setNickname(giteeUserInfo.getName() != null ? giteeUserInfo.getName() : giteeUserInfo.getLogin()); newUser.setAvatar(giteeUserInfo.getAvatarUrl()); newUser.setStatus(1); userRepository.save(newUser); return newUser; } }
|
和 Google 登录相比,唯一的区别在前两步:Google 是校验前端传来的 ID Token,Gitee 是用 code 换 token 再查用户信息。从第三步开始,绑定逻辑完全一致。
注意一点:Gitee 用户的邮箱可能为空(用户设置了隐私保护),所以 createUser 里不要假设 email 一定有值,用户表的 email 字段也要允许 NULL。
八、前后端联调
1. 本地启动
前端 5173 端口,后端 8080 端口,Vite 代理配置和 Google 那篇一样:
1 2 3 4 5 6 7 8 9 10 11 12
| export default defineConfig({ plugins: [react()], server: { proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true } } } });
|
2. 检查回调地址
确认 Gitee 应用配置中的回调地址包含:
1
| http://localhost:5173/oauth/gitee/callback
|
并且前端环境变量 VITE_GITEE_REDIRECT_URI 和后端 GITEE_REDIRECT_URI 都是这个值。
3. 测试登录流程
1 2 3 4 5
| 1. 打开登录页,点击"使用 Gitee 登录" 2. 浏览器跳转到 gitee.com 授权页,登录并点击"同意授权" 3. 浏览器回到 /oauth/gitee/callback?code=xxx&state=yyy 4. 回调页把 code 发给后端,后端返回本系统 token 5. 前端保存 token,跳转首页
|
如果第 4 步失败,优先检查后端日志中 Gitee 返回的错误信息,大概率是 redirect_uri 不一致或 code 已被使用。
九、生产环境部署注意事项
1. 回调地址加上生产域名
Gitee 应用配置中补充:
1
| https://yao-admin.com/oauth/gitee/callback
|
2. 必须使用 HTTPS
生产环境的回调地址必须是 HTTPS,否则授权过程中的 code 有被窃听的风险。
3. Client Secret 保护
1 2 3
| 1. 只通过环境变量注入,不进代码仓库 2. 怀疑泄露时,在 Gitee 应用设置中重置 Client Secret 3. 重置后记得同步更新服务器环境变量
|
4. Nginx 配置
前后端同域名部署时,Nginx 配置和《Google快捷登录配置》中完全一致,/ 指向前端静态文件,/api/ 反向代理到后端,回调地址 /oauth/gitee/callback 走前端路由即可,不需要额外配置。
注意:如果前端是单页应用,Nginx 必须配置 try_files $uri $uri/ /index.html;,否则用户刷新 /oauth/gitee/callback 这种前端路由地址会 404。
十、常见问题
1. 回调时报 redirect_uri_mismatch
授权请求里的 redirect_uri 和 Gitee 应用配置的回调地址不一致。检查:
1 2 3 4
| 1. 协议:http 和 https 不能混 2. 域名:localhost 和 127.0.0.1 是两个不同地址 3. 端口:5173 和 3000 不能混 4. 路径:必须完全一致,包括结尾是否有斜杠
|
2. 换取 token 时返回 invalid_grant
常见原因:
1 2 3 4
| 1. code 已经被使用过(code 是一次性的) 2. code 已过期,用户停留在回调页太久 3. 换 token 时传的 redirect_uri 和授权时的不一致 4. React 18 开发模式 useEffect 执行两次,第二次用的是已消费的 code
|
3. 拿到的用户信息里 email 是 null
用户在 Gitee 设置中隐藏了邮箱。处理方式:
1 2
| 方式一:允许 email 为空,用户登录后在个人中心自行补填 方式二:申请 emails 权限,调用 https://gitee.com/api/v5/emails 获取
|
对快捷登录来说,方式一通常就够用了。
4. 能不能把换 token 放在前端做
不能。
换 token 必须携带 Client Secret,放在前端等于把密钥公开。授权码模式的设计初衷就是让 code 经过前端、让 Secret 永远留在后端。
5. 需要保存 Gitee 的 Access Token 吗
如果只做登录,不需要。
换到用户信息、完成绑定之后,这个 token 的使命就结束了。只有当你后续还要代表用户调用 Gitee API(例如读取仓库列表)时,才需要考虑保存 access_token 和 refresh_token,并处理一天过期后的刷新逻辑。
6. 用户取消授权会发生什么
Gitee 会重定向回你的回调地址,但 URL 上没有 code,而是带着 error 参数。回调页要处理这种情况,给出”已取消登录”的提示并跳回登录页,而不是直接报错。
十一、最终落地清单
Gitee 平台需要完成:
1 2 3 4
| 1. 设置 → 第三方应用 → 创建应用 2. 填写应用名称、主页、回调地址 3. 勾选 user_info 权限 4. 复制 Client ID 和 Client Secret
|
前端需要完成:
1 2 3 4 5
| 1. 配置 VITE_GITEE_CLIENT_ID 和 VITE_GITEE_REDIRECT_URI 2. 登录按钮拼接授权地址并跳转,带上随机 state 3. 回调页校验 state、取出 code 4. 调用后端 /api/auth/gitee/login 5. 保存后端返回的本系统 token
|
后端需要完成:
1 2 3 4 5 6 7
| 1. 配置 client-id、client-secret、redirect-uri 2. 接收 code,POST https://gitee.com/oauth/token 换取 access_token 3. GET https://gitee.com/api/v5/user 获取用户信息 4. 根据 id 查询 sys_oauth_account 绑定关系 5. 查询或创建本地用户 6. 生成自己系统的登录态 7. 返回 token 和用户信息
|
十二、总结
Gitee 快捷登录是最标准的 OAuth2 授权码模式,核心就是三步:
1 2 3
| 1. 前端跳转 https://gitee.com/oauth/authorize 拿授权码 code 2. 后端用 code + Client Secret 换 Access Token 3. 后端调用用户信息接口,根据 id 完成本地用户绑定和登录
|
实际开发时,最容易出错的是:
1 2 3 4 5
| 1. redirect_uri 三处(Gitee 应用配置、前端跳转、后端换 token)不一致 2. code 被重复使用,尤其是 React 开发模式下 useEffect 执行两次 3. 把 Client Secret 放到了前端 4. 错误地用 login 或 email 作为唯一标识,应该用数字 id 5. 没有处理 email 为空的情况
|
理解了授权码模式之后,接入 GitHub、微博、QQ 等平台的登录都是同一套思路,区别只是接口地址和字段名。下一篇《Github快捷登录配置》会验证这一点。