Spring Security OAuth 2 客户端配置

FreeGuideOnline 最新 2026-07-10

Spring Security OAuth 2 客户端配置完全指南

在微服务架构和第三方登录横行的今天,OAuth 2.0 已成为授权协议的事实标准。Spring Security 对其提供了深度支持,特别是客户端模式,让我们的应用能够安全地接入认证服务器并访问受保护资源。本教程将手把手带你从零开始,配置一个 OAuth 2 客户端,重点讲解最新 Spring Security 6.x 的最佳实践。

1. OAuth 2 客户端核心概念

在开始编码前,我们需要明确三种角色的区别:

  • 资源所有者:通常是用户,拥有受保护资源(如头像、数据)。
  • 客户端:你的 Spring Boot 应用,需要代表用户或自身去访问资源。
  • 资源服务器:托管受保护资源的服务器,通过令牌验证请求。
  • 授权服务器:认证用户并颁发令牌的服务器(如 Keycloak、Auth0、自建 Spring Authorization Server)。

本教程聚焦于客户端的配置,即你的应用如何从授权服务器获取令牌,并使用令牌去调用资源服务器的 API。主要涉及的授权模式为 授权码模式(Authorization Code)客户端凭证模式(Client Credentials)

2. 项目环境准备

创建一个标准的 Spring Boot 项目,记得添加以下关键依赖。

Maven 依赖 (pom.xml):

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-webflux</artifactId>
    <!-- 如果准备使用 WebClient 进行令牌中继调用 -->
</dependency>

spring-boot-starter-oauth2-client 是核心,它会自动引入 Spring Security 及 OAuth 2 客户端所需的所有库。如果你需要实现“客户端凭证”模式并通过 WebClient 无缝带上令牌,还需要 spring-boot-starter-webflux

3. 客户端注册信息配置 (application.yml)

Spring Security 通过 ClientRegistration 来表示一个已注册的客户端。在 application.yml 中配置是最简单的方式。我们以连接 GitHub 作为授权服务器为例(授权码模式)。

spring:
  security:
    oauth2:
      client:
        registration:
          github:
            client-id: your-github-client-id
            client-secret: your-github-client-secret
            scope:
              - read:user
              - user:email
            redirect-uri: "{baseUrl}/login/oauth2/code/{registrationId}"
            authorization-grant-type: authorization_code
        provider:
          github:
            authorization-uri: https://github.com/login/oauth/authorize
            token-uri: https://github.com/login/oauth/access_token
            user-info-uri: https://api.github.com/user
            user-name-attribute: login

3.1 关键属性解释

配置项 说明
registration.[id] 客户端唯一标识,例如 github,将用于自动生成回调地址。
client-id / client-secret 在授权服务器注册应用时获得的凭据。
scope 请求的权限范围,多个用逗号或 YAML 列表。
redirect-uri 授权服务器回调地址,{baseUrl} 占位符代表当前应用地址,{registrationId} 就是 github。默认模板值正好是 /login/oauth2/code/{registrationId},可省略不写。
authorization-grant-type 授权模式,默认是 authorization_code
provider.[id] 指定授权服务器元信息。Spring Security 内置了 Google、GitHub、Facebook 等常用提供商的默认配置,如果 providerid 与已知提供商匹配,则无需手动填写 authorization-uri 等。但我们仍可像上面那样显式覆盖。

对于客户端凭证模式,配置会略有不同,后面会具体说明。

4. 安全配置:启用 OAuth2 登录

简单的安全配置类,融入 OAuth2 登录支持。

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.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(authorize -> authorize
                .requestMatchers("/", "/public/**").permitAll()
                .anyRequest().authenticated()
            )
            .oauth2Login(oauth2 -> oauth2
                .defaultSuccessUrl("/dashboard", true)
            );
        return http.build();
    }
}

oauth2Login() 方法会自动添加一个 OAuth2LoginAuthenticationFilter,拦截 /login/oauth2/code/* 的回调请求,完成用户认证。用户首次访问受保护页面时,会被重定向到授权服务器的登录页面。成功登录后,应用会跳转到 /dashboard

4.1 获取认证后的用户信息

你可以通过注入 OAuth2AuthorizedClientService 来获取令牌,也可以直接从 OAuth2User 中读取授权服务器返回的用户属性。

import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.oauth2.core.user.OAuth2User;
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> user(@AuthenticationPrincipal OAuth2User principal) {
        return principal.getAttributes();
    }
}

5. OAuth2 客户端与服务间调用 (令牌中继)

OAuth2 客户端最常见的需求是:代表当前登录用户去调用另一个受保护的资源服务器。这就叫令牌中继(Token Relay)。Spring 提供 ServletOAuth2AuthorizedClientExchangeFilterFunction 来让 WebClient 自动携带当前的访问令牌。

5.1 配置 WebClient Bean

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientProvider;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientProviderBuilder;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.oauth2.client.web.DefaultOAuth2AuthorizedClientManager;
import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository;
import org.springframework.security.oauth2.client.web.reactive.function.client.ServletOAuth2AuthorizedClientExchangeFilterFunction;
import org.springframework.web.reactive.function.client.WebClient;

@Configuration
public class WebClientConfig {

    @Bean
    public OAuth2AuthorizedClientManager authorizedClientManager(
            ClientRegistrationRepository clientRegistrationRepository,
            OAuth2AuthorizedClientRepository authorizedClientRepository) {

        OAuth2AuthorizedClientProvider authorizedClientProvider =
                OAuth2AuthorizedClientProviderBuilder.builder()
                        .authorizationCode()
                        .refreshToken()
                        .clientCredentials()
                        .build();

        DefaultOAuth2AuthorizedClientManager authorizedClientManager =
                new DefaultOAuth2AuthorizedClientManager(
                        clientRegistrationRepository, authorizedClientRepository);
        authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
        return authorizedClientManager;
    }

    @Bean
    public WebClient webClient(OAuth2AuthorizedClientManager authorizedClientManager) {
        ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2Client =
                new ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
        // 设置默认的 OAuth2 客户端注册id (如果在请求时不显式指定)
        oauth2Client.setDefaultOAuth2AuthorizedClient(true);
        oauth2Client.setDefaultClientRegistrationId("github");

        return WebClient.builder()
                .apply(oauth2Client.oauth2Configuration())
                .build();
    }
}

这个配置的核心是 OAuth2AuthorizedClientManager,它会自动管理令牌的获取、刷新等。我们将其注入给 ServletOAuth2AuthorizedClientExchangeFilterFunction,然后应用到 WebClient 上。oauth2Client.setDefaultOAuth2AuthorizedClient(true) 表示如果请求中没有明确指定,则尝试从当前登录的用户上下文中获取 github 客户端对应的令牌。

5.2 发起远程调用

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.reactive.function.client.WebClient;

@RestController
public class ResourceController {

    private final WebClient webClient;

    public ResourceController(WebClient webClient) {
        this.webClient = webClient;
    }

    @GetMapping("/repos")
    public String repos() {
        return this.webClient
                .get()
                .uri("https://api.github.com/user/repos")
                .retrieve()
                .bodyToMono(String.class)
                .block();
    }
}

此时访问 /reposWebClient 会自动在 Authorization: Bearer xxx 请求头中附加当前用户的 GitHub 访问令牌。整个过程对开发者透明,极其方便。

6. 客户端凭证模式配置 (机器对机器)

如果你的应用并非代表用户,而是以自己的身份去访问资源(例如后台服务调用),那么需要使用 客户端凭证模式。此时没有用户上下文,配置与授权码模式略有不同。

6.1 application.yml 添加新注册

spring:
  security:
    oauth2:
      client:
        registration:
          my-backend-api:
            client-id: backend-client
            client-secret: secret
            authorization-grant-type: client_credentials
            scope: read,write
            provider: my-auth-server
        provider:
          my-auth-server:
            token-uri: https://auth.example.com/oauth2/token

注意 authorization-grant-type: client_credentials,并且不需要 redirect-uriauthorization-uri 等。

6.2 使用 WebClient 以客户端自身名义调用

在前面的 WebClientConfig 中,OAuth2AuthorizedClientProviderBuilder 已经 .clientCredentials(),所以管理器已经支持该模式。我们只需在调用时显式指定使用哪个客户端注册,并且告知无需用户主体。

import org.springframework.security.oauth2.client.OAuth2AuthorizeRequest;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.reactive.function.client.WebClient;

@RestController
public class MachineController {

    private final WebClient webClient;
    private final OAuth2AuthorizedClientManager authorizedClientManager;

    public MachineController(WebClient webClient,
                             OAuth2AuthorizedClientManager authorizedClientManager) {
        this.webClient = webClient;
        this.authorizedClientManager = authorizedClientManager;
    }

    @GetMapping("/machine-data")
    public String getData() {
        // 手动获取令牌并手动设置到请求头,或者使用另一种更简洁的方法:
        // 借助 ServletOAuth2AuthorizedClientExchangeFilterFunction 的快捷方式
        // 但需要 ClientRequest,这里展示通用方法
        String token = authorizedClientManager.authorize(
                OAuth2AuthorizeRequest.withClientRegistrationId("my-backend-api")
                        .principal("backend")  // 任意唯一名称,代表客户端自身
                        .build())
                .getAccessToken().getTokenValue();

        return webClient.get()
                .uri("https://resource-server.com/api/data")
                .headers(h -> h.setBearerAuth(token))
                .retrieve()
                .bodyToMono(String.class)
                .block();
    }
}

不过更好的方式是直接在 WebClient 调用中传入 attributes 来触发自动获取令牌。只需在请求前设置属性:

// 在 WebClient 调用的基础上添加 .attributes(...)
webClient.get()
    .uri("https://resource-server.com/api/data")
    .attributes(ServletOAuth2AuthorizedClientExchangeFilterFunction
            .clientRegistrationId("my-backend-api"))
    .retrieve()
    .bodyToMono(String.class)
    .block();

这样 ServletOAuth2AuthorizedClientExchangeFilterFunction 会检测到你需要 my-backend-api 的令牌,如果当前没有用户上下文,它会自动使用客户端凭证模式获取令牌并附加。记住,在这个场景下调用时不能有已认证的用户上下文,否则可能会意外使用用户令牌。

7. 常见问题与调试

  • 重定向 URI 不匹配:确保在授权服务器上注册的回调地址与 application.yml 中的 redirect-uri 完全一致(可带通配符)。本地开发时通常是 http://localhost:8080/login/oauth2/code/github
  • 令牌不自动刷新:确保 OAuth2AuthorizedClientProvider 链中包含了 .refreshToken(),并且授权服务器支持刷新令牌。
  • Spring Security 6 变化:旧的 AuthorizationRequestRepository 等 API 有所变化,遵循本文的配置可完全兼容 Spring Boot 3.x / Security 6.x。
  • CSRF 保护:如果你提供的是纯 API 服务,传统表单提交不多,可以在安全配置中禁用 CSRF:http.csrf(AbstractHttpConfigurer::disable),但请谨慎评估。

8. 总结

通过以上步骤,你已经完成了 Spring Security OAuth 2 客户端的主要配置:

  1. 引入依赖,配置客户端注册信息。
  2. 启用 oauth2Login 登录,获取用户身份。
  3. 配置支持令牌中继的 WebClient,代表用户调用下游服务。
  4. 配置客户端凭证模式,实现服务间的直接令牌获取。 这套模式足以应对绝大多数现代应用的安全需求。借助 Spring Security 的高度抽象,你无需手动处理令牌存储、刷新等繁琐细节,从而更专注于业务逻辑的开发。