1. 前言

在微服务与前后端分离架构中,第三方社交登录已成为提升用户体验的重要功能。社交登录可以有效降低用户注册成本,同时利用第三方平台的账号体系,实现快速认证与信息获取。Spring Security 6 作为 Java 生态中的安全框架,通过 OAuth2 协议简化了第三方认证的集成流程。

本章节笔者将通过完整代码案例,讲解如何基于 Spring Security 6 实现 GitHubGitee 的社交登录功能。


2. 原理分析

回顾上一章节介绍的 OAuth 2.0 授权码流程(最新 Spring Security 实战教程(十四)OAuth2.0 精讲 - 四种授权模式与资源服务器搭建):

OAuth2 授权码流程示意图

其主要流程包括:

  • 跳转授权:客户端(我们的网站)将用户重定向至第三方授权服务器的授权端点,携带 client_idredirect_uriscope 等参数。
  • 用户登录并同意:用户在第三方平台完成登录后,同意授权给客户端指定权限。
  • 回调获取授权码:授权服务器重定向回客户端注册的 redirect_uri,并在查询参数中附带 code
  • 交换令牌:客户端后端使用 codeclient_secret 等向授权服务器的令牌端点发起 POST 请求,获取 access_token
  • 获取用户信息:客户端携带 access_token 调用用户信息端点,解析并登录或注册用户。

3. 开发准备

3.1 初始化项目

继续基于之前的项目构建子项目 social-login-spring-security,pom 文件引入项目依赖:

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-oauth2-client</artifactId>
    </dependency>
</dependencies>

GitHub/Gitee 开发者账号:用于注册 OAuth 应用。

3.2 注册 GitHub OAuth 应用

访问 GitHub 设置 → Developer settingsOAuth AppsNew OAuth App,填写应用名、主页 URL、授权回调 URL。

GitHub 创建 OAuth 应用

填写应用信息:默认的重定向 URI 模板为 {baseUrl}/login/oauth2/code/{registrationId}registrationIdClientRegistration 的唯一标识符。

例如 GitHub 固定写法则为:{baseUrl}/login/oauth2/code/github

GitHub 回调地址配置

获取应用程序 ID,生成应用程序密钥:保存好生成的 Client ID 与 Client Secret

GitHub Client ID 与 Secret

3.3 注册 Gitee OAuth 应用

登录 Gitee,访问 Gitee 应用管理,创建新应用。

Gitee 创建 OAuth 应用

提交创建应用后会得到 Client IDClient Secret


4. 实战案例

完成上述准备工作后,我们开始对项目进行配置。

4.1 配置 application.yml

# 最新 Spring Security 实战教程(十五)快速集成 GitHub 与 Gitee 的社交登录
spring:
  application:
    name: social-login-spring-security
  security:
    oauth2:
      client:
        registration:
          github:
            client-id: YOUR_CLIENT_ID
            client-secret: YOUR_CLIENT_SECRET
            scope: user:email
            redirect-uri: "{baseUrl}/login/oauth2/code/{registrationId}"
            authorization-grant-type: authorization_code
            client-name: github
          gitee:
            client-id: YOUR_CLIENT_ID
            client-secret: YOUR_CLIENT_SECRET
            scope: user_info
            client-name: Gitee
            authorization-grant-type: authorization_code
            redirect-uri: "{baseUrl}/login/oauth2/code/{registrationId}"
            provider: gitee
        provider:
          gitee:
            authorization-uri: https://gitee.com/oauth/authorize
            token-uri: https://gitee.com/oauth/token
            user-info-uri: https://gitee.com/api/v5/user
            user-name-attribute: name
server:
  port: 8089

4.2 自定义用户信息处理

GitHubGitee 返回的用户数据结构不同,需统一处理:

public class CustomOAuth2UserService implements OAuth2UserService<OAuth2UserRequest, OAuth2User> {

    @Override
    public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
        DefaultOAuth2UserService delegate = new DefaultOAuth2UserService();
        OAuth2User oAuth2User = delegate.loadUser(userRequest);

        String registrationId = userRequest.getClientRegistration().getRegistrationId();
        Map<String, Object> attributes = oAuth2User.getAttributes();

        // 根据平台解析用户信息
        if ("github".equals(registrationId)) {
            return new DefaultOAuth2User(
                oAuth2User.getAuthorities(),
                attributes,
                "login"  // GitHub 的用户名字段
            );
        } else if ("gitee".equals(registrationId)) {
            return new DefaultOAuth2User(
                oAuth2User.getAuthorities(),
                attributes,
                "name"   // Gitee 的用户名字段
            );
        }
        throw new OAuth2AuthenticationException("Unsupported platform");
    }
}

4.3 SecurityConfig 配置

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
                .authorizeHttpRequests(authorize -> authorize
                        .requestMatchers("/", "/public/**").permitAll()
                        .anyRequest().authenticated()
                )
                .oauth2Login(oauth2 -> oauth2
                        .userInfoEndpoint(userInfo -> userInfo
                                .userService(customOAuth2UserService())
                        )
                )
                .csrf(AbstractHttpConfigurer::disable);
        return http.build();
    }

    @Bean
    public OAuth2UserService<OAuth2UserRequest, OAuth2User> customOAuth2UserService() {
        return new CustomOAuth2UserService();
    }
}

4.4 控制器与页面

编写自定义的登录页,授权登录后返回授权的用户信息。

@Controller
public class IndexController {
    @GetMapping("/")
    public String index(Model model, @RegisteredOAuth2AuthorizedClient OAuth2AuthorizedClient authorizedClient,
                        @AuthenticationPrincipal OAuth2User oauth2User) {
        model.addAttribute("userName", oauth2User.getName());
        model.addAttribute("clientName", authorizedClient.getClientRegistration().getClientName());
        model.addAttribute("userAttributes", oauth2User.getAttributes());
        // TODO 可以增加与项目用户注册、绑定等业务 service
        return "index";
    }

    @GetMapping("/user")
    @ResponseBody
    public Map<String, Object> userInfo(OAuth2AuthenticationToken authentication) {
        OAuth2User user = authentication.getPrincipal();
        return Map.of(
                "username", user.getName(),
                "authorities", user.getAuthorities().stream()
                        .map(GrantedAuthority::getAuthority)
                        .collect(Collectors.toList())
        );
    }
}

Thymeleaf 模板页面:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org" xmlns:sec="https://www.thymeleaf.org/thymeleaf-extras-springsecurity5">
<head>
    <title>Spring Security - OAuth 2.0 Login</title>
    <meta charset="utf-8" />
</head>
<body>
<div style="float: right" th:fragment="logout" sec:authorize="isAuthenticated()">
    <div style="float:left">
        <span style="font-weight:bold">User: </span><span sec:authentication="name"></span>
    </div>
    <div style="float:none">&nbsp;</div>
    <div style="float:right">
        <form action="#" th:action="@{/logout}" method="post">
            <input type="submit" value="Logout" />
        </form>
    </div>
</div>
<h1>OAuth 2.0 Login with Spring Security</h1>
<div>
    You are successfully logged in <span style="font-weight:bold" th:text="${userName}"></span>
    via the OAuth 2.0 Client <span style="font-weight:bold" th:text="${clientName}"></span>
</div>
<div>&nbsp;</div>
<div>
    <span style="font-weight:bold">User Attributes:</span>
    <ul>
        <li th:each="userAttribute : ${userAttributes}">
            <span style="font-weight:bold" th:text="${userAttribute.key}"></span>: <span th:text="${userAttribute.value}"></span>
        </li>
    </ul>
</div>
</body>
</html>

5. 测试验证

  • 访问首页:http://localhost:8089,跳转至 Spring Security 默认登录页。
注:应用回调地址如果填写的是 localhost,本地访问项目也需保持 localhost 访问;若使用 127.0.0.1 会导致回调地址不一致,出现回调地址错误的异常。
  • 选择登录方式:点击 GitHubGitee 按钮,跳转至对应授权页面。

GitHub 授权页
GitHub 授权页

Gitee 授权页
Gitee 授权页

  • 授权登录:同意授权后,自动跳转回应用,显示用户信息。
  • 访问 http://localhost:8089/user 显示经过处理的用户信息。
{
  "authorities": [
    "OAUTH2_USER",
    "SCOPE_user:email"
  ],
  "username": "授权返回的用户名"
}

6. 常见问题与扩展

  • 回调地址不匹配:确保 redirect-uri 与注册应用时填写的一致。
  • 跨域问题:前端分离项目需配置 CorsFilter
  • 用户信息字段缺失:检查 user-info-uriuser-name-attribute 配置。

7. 总结

通过 Spring Security 6OAuth2 Client 模块,开发者可以快速集成主流社交登录功能。本章节案例以 GitHubGitee 为例,展示了从应用注册到用户信息处理的完整流程,适用于需要第三方登录的 Web 应用场景。

后续章节笔者将继续扩展支持微信、支付宝、QQ、微博等更多平台,进一步提升用户体验。

最后修改:2026 年 03 月 12 日
如果觉得我的文章对你有用,请随意赞赏