在 Spring Boot 项目中实现 OAuth2 支持可通过 Spring Security OAuth2 或 Spring Authorization Server 实现。以下是基于 Spring Security OAuth2 的完整实现方案:

1. 添加依赖

<!-- Spring Security OAuth2 客户端 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>

<!-- Spring Security OAuth2 资源服务器(可选,保护 API) -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>

2. 配置 OAuth2 客户端

application.properties 中配置第三方服务提供商(如 GitHub、Google):

# 启用 OAuth2 客户端
spring.security.oauth2.client.registration.github.client-id=your-client-id
spring.security.oauth2.client.registration.github.client-secret=your-client-secret
spring.security.oauth2.client.registration.github.scope=read:user,user:email

# Google 配置示例
spring.security.oauth2.client.registration.google.client-id=your-google-client-id
spring.security.oauth2.client.registration.google.client-secret=your-google-client-secret
spring.security.oauth2.client.registration.google.scope=openid,profile,email

3. 配置安全策略

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
public class SecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .authorizeRequests(authorize -> authorize
                .antMatchers("/", "/login").permitAll()  // 公开路径
                .anyRequest().authenticated()            // 其他路径需要认证
            )
            .oauth2Login(oauth2 -> oauth2
                .loginPage("/login")                   // 自定义登录页
                .defaultSuccessUrl("/dashboard")       // 登录成功后跳转
                .failureUrl("/login?error")            // 登录失败后跳转
            )
            .logout(logout -> logout
                .logoutSuccessUrl("/")                 // 登出后跳转
                .invalidateHttpSession(true)
                .deleteCookies("JSESSIONID")
            );
        
        return http.build();
    }
}

4. 获取用户信息

方式1:使用 OAuth2AuthenticationToken

import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Map;

@RestController
public class UserController {

    @GetMapping("/user")
    public Map<String, Object> getUserInfo(OAuth2AuthenticationToken authentication) {
        // 获取用户属性(不同提供商字段可能不同)
        return authentication.getPrincipal().getAttributes();
    }
}

方式2:使用 @AuthenticationPrincipal 注解

import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class UserController {

    @GetMapping("/user")
    public OAuth2User getUser(@AuthenticationPrincipal OAuth2User oauth2User) {
        return oauth2User;
    }
}

5. 自定义用户信息映射

处理不同提供商的用户信息结构差异:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.user.OAuth2User;

@Configuration
public class OAuth2Config {

    @Bean
    public DefaultOAuth2UserService userService() {
        DefaultOAuth2UserService service = new DefaultOAuth2UserService();
        
        // 自定义用户信息映射
        service.setUserInfoExtractors(extractors -> {
            extractors.put("github", this::extractGithubUser);
            extractors.put("google", this::extractGoogleUser);
            // 可添加更多提供商
        });
        
        return service;
    }

    private OAuth2User extractGithubUser(OAuth2UserRequest userRequest) {
        // 处理 GitHub 特定的用户信息
        return service.loadUser(userRequest);
    }

    private OAuth2User extractGoogleUser(OAuth2UserRequest userRequest) {
        // 处理 Google 特定的用户信息
        return service.loadUser(userRequest);
    }
}

6. 保护 API(资源服务器)

配置项目作为资源服务器:

# 配置 JWT 验证
spring.security.oauth2.resourceserver.jwt.issuer-uri=https://your-auth-server.com
@Configuration
public class ResourceServerConfig {

    @Bean
    public SecurityFilterChain resourceServerFilterChain(HttpSecurity http) throws Exception {
        http
            .authorizeRequests(authorize -> authorize
                .antMatchers("/public").permitAll()
                .anyRequest().authenticated()
            )
            .oauth2ResourceServer(oauth2 -> oauth2
                .jwt()  // 使用 JWT 验证
            );
        
        return http.build();
    }
}

7. 自定义登录页面

创建 login.html

<!DOCTYPE html>
<html>
<head>
    <title>Login</title>
</head>
<body>
    <h2>Login with OAuth2</h2>
    <a href="/oauth2/authorization/github">Login with GitHub</a>
    <a href="/oauth2/authorization/google">Login with Google</a>
</body>
</html>

8. 多提供商支持

在配置文件中添加多个提供商:

# Facebook 配置
spring.security.oauth2.client.registration.facebook.client-id=your-facebook-id
spring.security.oauth2.client.registration.facebook.client-secret=your-facebook-secret
spring.security.oauth2.client.registration.facebook.scope=email,public_profile

9. 测试 OAuth2 集成

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.web.servlet.MockMvc;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@WebMvcTest(UserController.class)
public class UserControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @Test
    @WithMockUser
    public void testAuthenticatedUser() throws Exception {
        mockMvc.perform(get("/user"))
            .andExpect(status().isOk());
    }
}

10. 注意事项

  1. 注册应用:需在各提供商(GitHub、Google 等)后台注册应用,获取 Client ID 和 Secret。
  2. 重定向 URI:确保回调 URL 与提供商配置一致(如 http://localhost:8080/login/oauth2/code/github)。
  3. 状态验证:Spring Security 自动处理 CSRF 和状态验证,防止攻击。
  4. 刷新令牌:对于长期访问,需处理刷新令牌逻辑(可通过 OAuth2AuthorizedClientService)。

总结

通过 Spring Security OAuth2 客户端,可轻松实现第三方认证和授权,主要步骤包括:配置提供商信息、定义安全策略、获取和处理用户信息。如需保护 API,可同时配置资源服务器。这种方式使应用能够无缝集成 GitHub、Google 等身份提供商,提供更好的用户体验。