GitHub 和 Gitee 第三方登录实现
现在很多站点都支持第三方登录。本文记录基于 OAuth2 授权码模式接入 GitHub 和 Gitee 登录的完整过程,以 Java + Spring Boot 为例。前提:先有一个 GitHub / Gitee 账号。
创建 OAuth 应用
GitHub
- 登录 GitHub,点击 Settings:

- 点击 Developer settings(开发者设置):

- 新建一个 OAuth App(授权应用):

- 填写应用信息(回调地址填自己服务的回调接口):

获取 Client ID 和 Client Secret
创建完成后即可看到:

提示:Client Secret 只在创建时展示一次,注意保存;不要提交到代码仓库或写在博客里(本文中一律用占位符代替)。
Gitee 的流程类似:设置 → 第三方应用 → 创建应用,同样会得到 Client ID 和 Client Secret。
授权流程
整个 OAuth2 授权码流程分三步:
-
跳转授权页:引导用户重定向到 GitHub 的授权页面:
https://github.com/login/oauth/authorize?client_id=xxxxxxxxxxxxxxxxxx&scope=user,public_repo
-
回调换 token:用户同意授权后,GitHub 跳转到回调地址并携带
code参数:http://localhost:8080/login?code=xxxxxxxxxxxxxxxxxxxx
后端拿
client_id、client_secret和code换取access_token: -
获取用户信息:用
access_token请求用户信息接口:
完整代码
登录页与跳转
先写一个简单的登录页面:
1<!DOCTYPE html>
2<html lang="en" xmlns:th="http://www.thymeleaf.org">
3<head>
4 <meta charset="UTF-8">
5 <title>登录</title>
6</head>
7<body>
8<form method="get" action="/oauth/login">
9 <input type="submit" th:name="type" th:value="github">
10 <input type="submit" th:name="type" th:value="gitee"/>
11</form>
12</body>
13</html>
controller 页面跳转:
1@GetMapping(value = "/login")
2public String login() {
3 return "login";
4}

OAuth 接口抽象
把三个平台的差异(授权地址、token 地址、用户信息地址)抽象成接口:
1public interface BaseOauth {
2
3 /**
4 * 授权地址
5 *
6 * @return 授权地址
7 */
8 String authorize();
9
10 /**
11 * 获取 accessToken
12 *
13 * @param code 请求编码
14 * @return accessToken
15 */
16 String accessToken(String code);
17
18 /**
19 * 获取用户信息
20 *
21 * @param accessToken token
22 * @return user
23 */
24 String userInfo(String accessToken);
25}
Gitee 实现:
1@Component
2public class GiteeOauth implements BaseOauth {
3
4 private static final String GITEE_CLIENT_ID = "[你的 Gitee Client ID]";
5 private static final String GITEE_CLIENT_SECRET = "[你的 Gitee Client Secret]";
6
7 private static final String REDIRECT_URI = "http://127.0.0.1:8012/gitee/callback";
8
9 @Override
10 public String authorize() {
11 return "https://gitee.com/oauth/authorize?client_id=" + GITEE_CLIENT_ID + "&response_type=code&redirect_uri=" + REDIRECT_URI;
12 }
13
14 @Override
15 public String accessToken(String code) {
16 return "https://gitee.com/oauth/token?grant_type=authorization_code&code=" + code + "&client_id=" + GITEE_CLIENT_ID + "&redirect_uri=" + REDIRECT_URI + "&client_secret=" + GITEE_CLIENT_SECRET;
17 }
18
19 @Override
20 public String userInfo(String accessToken) {
21 return "https://gitee.com/api/v5/user?access_token=" + accessToken;
22 }
23}
GitHub 实现:
1@Component
2public class GithubOauth implements BaseOauth {
3
4 private static final String GITHUB_CLIENT_ID = "[你的 GitHub Client ID]";
5 private static final String GITHUB_CLIENT_SECRET = "[你的 GitHub Client Secret]";
6
7 /**
8 * 登陆授权类型
9 */
10 @Override
11 public String authorize() {
12 return "https://github.com/login/oauth/authorize?client_id=" + GITHUB_CLIENT_ID + "&scope=user,public_repo";
13 }
14
15 @Override
16 public String accessToken(String code) {
17 return "https://github.com/login/oauth/access_token?client_id=" + GITHUB_CLIENT_ID + "&client_secret=" + GITHUB_CLIENT_SECRET + "&code=" + code;
18 }
19
20 @Override
21 public String userInfo(String accessToken) {
22 return "https://api.github.com/user?access_token=" + accessToken + "&scope=public_repo%2Cuser&token_type=bearer";
23 }
24}
Service 层
1public interface BaseOauthService {
2 String authorizeUri();
3
4 String getAccessToken(String code);
5
6 JSONObject getUserInfo(String accessToken);
7}
1@Service
2public class GiteeServiceImpl implements BaseOauthService {
3
4 @Autowired
5 GiteeOauth giteeOauth;
6
7 @Autowired
8 RestTemplate restTemplate;
9
10 @Override
11 public String authorizeUri() {
12 return giteeOauth.authorize();
13 }
14
15 @Override
16 public String getAccessToken(String code) {
17 String token = giteeOauth.accessToken(code);
18 ResponseEntity<Object> entity = restTemplate.postForEntity(token, httpEntity(), Object.class);
19 Object body = entity.getBody();
20 assert body != null;
21 String string = body.toString();
22 String[] split = string.split("=");
23 String accessToken = split[1].toString().split(",")[0];
24 return accessToken;
25 }
26
27 @Override
28 public JSONObject getUserInfo(String accessToken) {
29 String userInfo = giteeOauth.userInfo(accessToken);
30 ResponseEntity<JSONObject> forEntity = restTemplate.exchange(userInfo, HttpMethod.GET, httpEntity(), JSONObject.class);
31 return forEntity.getBody();
32 }
33
34 public static HttpEntity httpEntity() {
35 HttpHeaders headers = new HttpHeaders();
36 headers.add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36");
37
38 HttpEntity<HttpHeaders> request = new HttpEntity<>(headers);
39 return request;
40 }
41}
1@Service
2public class GithubServiceImpl implements BaseOauthService {
3 @Autowired
4 GithubOauth githubOauth;
5 @Autowired
6 private RestTemplate restTemplate;
7
8 @Override
9 public String authorizeUri() {
10 return githubOauth.authorize();
11 }
12
13 @Override
14 public String getAccessToken(String code) {
15 String token = githubOauth.accessToken(code);
16 ResponseEntity<Object> forEntity = restTemplate.exchange(token, HttpMethod.GET, httpEntity(), Object.class);
17 String[] split = Objects.requireNonNull(forEntity.getBody()).toString().split("=");
18 String accessToken = split[1].split(",")[0];
19 return accessToken;
20 }
21
22 @Override
23 public JSONObject getUserInfo(String accessToken) {
24 String userInfo = githubOauth.userInfo(accessToken);
25 ResponseEntity<JSONObject> entity = restTemplate.exchange(userInfo, HttpMethod.GET, httpEntity(), JSONObject.class);
26 return entity.getBody();
27 }
28}
GitHub 接口要求请求带 User-Agent,所以统一封装了 httpEntity()。
Controller
1@Controller
2public class IndexController {
3
4 @Autowired
5 private GiteeServiceImpl giteeService;
6
7 @Autowired
8 GithubServiceImpl githubService;
9
10 @GetMapping(value = "/")
11 public String index() {
12 return "index";
13 }
14
15 @GetMapping(value = "/login")
16 public String login() {
17 return "login";
18 }
19
20 @GetMapping(value = "/oauth/login")
21 public String login(@RequestParam("type") String type) {
22 if (type.toUpperCase().equals(LoginType.GITHUB.toString())) {
23 return "redirect:" + githubService.authorizeUri();
24 }
25 return "redirect:" + giteeService.authorizeUri();
26 }
27
28 @GetMapping(value = "/github/callback")
29 public String callback(HttpServletRequest request, Model model) {
30 String code = request.getParameter("code");
31 JSONObject userInfo = githubService.getUserInfo(githubService.getAccessToken(code));
32 UserInfo info = JSON.parseObject(userInfo.toString(), UserInfo.class);
33 model.addAttribute("github", info);
34 return "index";
35 }
36
37 @GetMapping(value = "/gitee/callback")
38 public String giteeCallBack(HttpServletRequest request, Model model) {
39 String code = request.getParameter("code");
40 JSONObject userInfo = giteeService.getUserInfo(giteeService.getAccessToken(code));
41 UserInfo info = JSON.parseObject(userInfo.toString(), UserInfo.class);
42 model.addAttribute("gitee", info);
43 return "index";
44 }
45}
用户信息实体
1@Data
2public class UserInfo {
3 private Integer id;
4 private String avatarUrl;
5 private String login;
6 private String bio;
7 private String createdAt;
8 private String htmlUrl;
9}
1public enum LoginType {
2 /**
3 * 登录类型
4 */
5 GITEE,
6 GITHUB;
7}
显示用户信息
1<!DOCTYPE html>
2<html lang="en" xmlns:th="http://www.thymeleaf.org">
3<head>
4 <meta charset="UTF-8">
5 <title>首页</title>
6 <style>
7 .container {
8 width: 500px;
9 margin: 0 auto;
10 }
11 </style>
12</head>
13<body>
14<div class="container">
15 <h1>Gitee</h1>
16 <p th:text="${gitee}"></p>
17
18 <h1>GitHub</h1>
19 <p th:text="${github}"></p>
20</div>
21</body>
22</html>

