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 配置
- 访问 Google API Console → "Credentials" 部分
- 创建 "OAuth2 Client ID" 类型的凭证
- 配置授权重定向 URI(Spring Boot 默认值):
http://localhost:8081/login/oauth2/code/google
Facebook OAuth2 配置
- 在 Facebook for Developers 注册应用
- 设置 "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 时,会显示自动生成的登录页:
3.3. 其他客户端
Spring Security 默认支持:
- 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>
效果示例:
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")
⚠️ 需同步更新:
- 每个客户端的
redirectUriTemplate
属性 - 授权服务器中的授权重定向 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";
}
效果示例:
可获取的用户属性包括:
name
(姓名)email
(邮箱)family_name
(姓氏)picture
(头像)locale
(地区)
7. 总结
本文系统讲解了 Spring Security 5 中 oauth2Login()
的核心配置与自定义方案,覆盖了:
- 快速集成主流 OAuth2 提供商
- 登录流程各环节的深度定制
- 用户信息获取的最佳实践
完整示例代码请访问 GitHub 仓库。踩坑经验已融入各章节,助你高效实现 OAuth2 登录集成。