1. 概述

Spring Security 5 引入了全新的 OAuth2LoginConfigurer 类,用于配置外部授权服务器。本文将深入探讨 oauth2Login() 元素的各种配置选项,带你玩转 OAuth2 登录集成。

2. Maven 依赖

Spring Boot 项目

直接添加 starter 依赖即可,简单粗暴:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-oauth2-client</artifactId>
    <version>2.3.3.RELEASE</version>
</dependency>

非 Spring Boot 项目

需要显式添加以下依赖:

<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-oauth2-client</artifactId>
    <version>5.3.4.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-oauth2-jose</artifactId>
    <version>5.3.4.RELEASE</version>
</dependency>

3. 客户端配置

3.1. 获取客户端凭证

Google OAuth2 配置

  1. 访问 Google API Console → "Credentials" 部分
  2. 创建 "OAuth2 Client ID" 类型的凭证
  3. 配置授权重定向 URI(Spring Boot 默认值):
    http://localhost:8081/login/oauth2/code/google
    

Facebook OAuth2 配置

  1. Facebook for Developers 注册应用
  2. 设置 "Valid OAuth redirect URI":
    http://localhost:8081/login/oauth2/code/facebook
    

3.2. 安全配置

application.properties 中添加凭证:

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.facebook.client-id=your-facebook-client-id
spring.security.oauth2.client.registration.facebook.client-secret=your-facebook-client-secret

✅ 添加这些属性会自动触发 Oauth2ClientAutoConfiguration,相当于以下配置:

@Configuration
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.authorizeRequests()
         .anyRequest().authenticated()
         .and()
         .oauth2Login();
        return http.build();
    }
}

访问受保护 URL 时,会显示自动生成的登录页:

oauth login default

3.3. 其他客户端

Spring Security 默认支持:

  • Google
  • Facebook
  • GitHub
  • Okta

⚠️ 使用其他提供商时需完整配置授权 URI、令牌 URI 等参数。参考 CommonOAuth2Provider 源码了解所需属性。

4. 非 Spring Boot 项目配置

4.1. 创建 ClientRegistrationRepository Bean

@Configuration
@EnableWebSecurity
@PropertySource("classpath:application.properties")
public class SecurityConfig {
    private static List<String> clients = Arrays.asList("google", "facebook");

    @Bean
    public ClientRegistrationRepository clientRegistrationRepository() {
        List<ClientRegistration> registrations = clients.stream()
          .map(c -> getRegistration(c))
          .filter(registration -> registration != null)
          .collect(Collectors.toList());
        
        return new InMemoryClientRegistrationRepository(registrations);
    }
}

4.2. 构建 ClientRegistration 对象

private static String CLIENT_PROPERTY_KEY 
  = "spring.security.oauth2.client.registration.";

@Autowired
private Environment env;

private ClientRegistration getRegistration(String client) {
    String clientId = env.getProperty(
      CLIENT_PROPERTY_KEY + client + ".client-id");

    if (clientId == null) {
        return null;
    }

    String clientSecret = env.getProperty(
      CLIENT_PROPERTY_KEY + client + ".client-secret");
 
    if (client.equals("google")) {
        return CommonOAuth2Provider.GOOGLE.getBuilder(client)
          .clientId(clientId).clientSecret(clientSecret).build();
    }
    if (client.equals("facebook")) {
        return CommonOAuth2Provider.FACEBOOK.getBuilder(client)
          .clientId(clientId).clientSecret(clientSecret).build();
    }
    return null;
}

4.3. 注册 ClientRegistrationRepository

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http.authorizeRequests().anyRequest().authenticated()
      .and()
      .oauth2Login()
      .clientRegistrationRepository(clientRegistrationRepository())
      .authorizedClientService(authorizedClientService());
    return http.build();
}

@Bean
public OAuth2AuthorizedClientService authorizedClientService() {
    return new InMemoryOAuth2AuthorizedClientService(
      clientRegistrationRepository());
}

❌ 此时需自定义登录页面(不再自动生成)

5. 自定义 oauth2Login()

5.1. 自定义登录页

配置登录 URL

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http.authorizeRequests()
      .antMatchers("/oauth_login")
      .permitAll()
      .anyRequest()
      .authenticated()
      .and()
      .oauth2Login()
      .loginPage("/oauth_login");
    return http.build();
}

创建控制器

@Controller
public class LoginController {
    private static String authorizationRequestBaseUri = "oauth2/authorization";
    Map<String, String> oauth2AuthenticationUrls = new HashMap<>();

    @Autowired
    private ClientRegistrationRepository clientRegistrationRepository;

    @GetMapping("/oauth_login")
    public String getLoginPage(Model model) {
        Iterable<ClientRegistration> clientRegistrations = null;
        ResolvableType type = ResolvableType.forInstance(clientRegistrationRepository)
          .as(Iterable.class);
        if (type != ResolvableType.NONE && 
          ClientRegistration.class.isAssignableFrom(type.resolveGenerics()[0])) {
            clientRegistrations = (Iterable<ClientRegistration>) clientRegistrationRepository;
        }

        clientRegistrations.forEach(registration -> 
          oauth2AuthenticationUrls.put(registration.getClientName(), 
          authorizationRequestBaseUri + "/" + registration.getRegistrationId()));
        model.addAttribute("urls", oauth2AuthenticationUrls);

        return "oauth_login";
    }
}

创建登录页模板

<h3>Login with:</h3>
<p th:each="url : ${urls}">
    <a th:text="${url.key}" th:href="${url.value}">Client</a>
</p>

效果示例: login

5.2. 自定义认证成功/失败行为

重定向配置

.oauth2Login()
  .defaultSuccessUrl("/loginSuccess")
  .failureUrl("/loginFailure");

强制跳转(忽略原始请求)

.defaultSuccessUrl("/loginSuccess", true)

自定义处理器

实现 AuthenticationSuccessHandler/AuthenticationFailureHandler 接口,通过 successHandler()/failureHandler() 注册。

5.3. 自定义授权接口

.oauth2Login() 
  .authorizationEndpoint()
  .baseUri("/oauth2/authorize-client")
  .authorizationRequestRepository(authorizationRequestRepository());

@Bean
public AuthorizationRequestRepository<OAuth2AuthorizationRequest> 
  authorizationRequestRepository() {
    return new HttpSessionOAuth2AuthorizationRequestRepository();
}

5.4. 自定义令牌接口

.oauth2Login()
  .tokenEndpoint()
  .accessTokenResponseClient(accessTokenResponseClient());

@Bean
public OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> 
  accessTokenResponseClient() {
    return new NimbusAuthorizationCodeTokenResponseClient();
}

5.5. 自定义重定向接口

.oauth2Login()
  .redirectionEndpoint()
  .baseUri("/oauth2/redirect")

⚠️ 需同步更新:

  1. 每个客户端的 redirectUriTemplate 属性
  2. 授权服务器中的授权重定向 URI

5.6. 自定义用户信息接口

通过 userInfoEndpoint() 方法配置:

  • userService():修改用户信息获取逻辑
  • customUserType():自定义用户类型映射

6. 获取用户信息

@Autowired
private OAuth2AuthorizedClientService authorizedClientService;

@GetMapping("/loginSuccess")
public String getLoginInfo(Model model, OAuth2AuthenticationToken authentication) {
    OAuth2AuthorizedClient client = authorizedClientService
      .loadAuthorizedClient(
        authentication.getAuthorizedClientRegistrationId(), 
          authentication.getName());

    String userInfoEndpointUri = client.getClientRegistration()
      .getProviderDetails().getUserInfoEndpoint().getUri();

    if (!StringUtils.isEmpty(userInfoEndpointUri)) {
        RestTemplate restTemplate = new RestTemplate();
        HttpHeaders headers = new HttpHeaders();
        headers.add(HttpHeaders.AUTHORIZATION, "Bearer " + client.getAccessToken()
          .getTokenValue());
        HttpEntity entity = new HttpEntity("", headers);
        ResponseEntity<Map> response = restTemplate
          .exchange(userInfoEndpointUri, HttpMethod.GET, entity, Map.class);
        Map userAttributes = response.getBody();
        model.addAttribute("name", userAttributes.get("name"));
    }
    return "loginSuccess";
}

效果示例: welcome

可获取的用户属性包括:

  • name(姓名)
  • email(邮箱)
  • family_name(姓氏)
  • picture(头像)
  • locale(地区)

7. 总结

本文系统讲解了 Spring Security 5 中 oauth2Login() 的核心配置与自定义方案,覆盖了:

  • 快速集成主流 OAuth2 提供商
  • 登录流程各环节的深度定制
  • 用户信息获取的最佳实践

完整示例代码请访问 GitHub 仓库。踩坑经验已融入各章节,助你高效实现 OAuth2 登录集成。


原始标题:Spring Security 5 - OAuth2 Login