1. 概述
本文将演示如何使用 Spring Boot 和 Spring Security OAuth 构建一个应用,实现将用户认证委托给第三方(如 GitHub)或自定义授权服务器(Authorization Server)。
✅ 核心重点是:如何通过 Spring 提供的 PrincipalExtractor
和 AuthoritiesExtractor
接口,自定义提取用户身份(Principal)和权限(Authorities)。
如果你对 Spring Security OAuth2 基础还不熟悉,建议先阅读相关入门文章。
⚠️ 注意:本文基于 Spring Security OAuth2 Client 模块,适用于 OAuth2 登录场景。该模块在较新版本中已被 Spring Security 5.7+ 的原生 OAuth2 支持逐步取代,但目前仍广泛使用。
2. Maven 依赖
首先,引入关键依赖:
<dependency>
<groupId>org.springframework.security.oauth.boot</groupId>
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
<version>2.6.8</version>
</dependency>
这个依赖会自动配置 OAuth2 客户端所需的核心组件,包括登录流程、令牌获取、用户信息拉取等。
3. 使用 GitHub 实现 OAuth 认证
我们先配置基于 GitHub 的 OAuth 登录。
安全配置类
@Configuration
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.antMatcher("/**")
.authorizeRequests()
.antMatchers("/login**")
.permitAll()
.anyRequest()
.authenticated()
.and()
.formLogin()
.disable()
.oauth2Login();
return http.build();
}
}
📌 简单解释下关键点:
antMatcher("/**")
:拦截所有请求/login**
允许匿名访问(GitHub 回调地址通常是/login/oauth2/code/github
)oauth2Login()
:启用 OAuth2 登录,Spring 会自动处理登录跳转和回调
配置 GitHub 客户端信息
在 application.yml
或 application.properties
中添加:
spring.security.oauth2.client.registration.github.client-id=89a7c4facbb3434d599d
spring.security.oauth2.client.registration.github.client-secret=9b3b08e4a340bd20e866787e4645b54f73d74b6a
spring.security.oauth2.client.registration.github.scope=read:user,user:email
spring.security.oauth2.client.provider.github.token-uri=https://github.com/login/oauth/access_token
spring.security.oauth2.client.provider.github.authorization-uri=https://github.com/login/oauth/authorize
spring.security.oauth2.client.provider.github.user-info-uri=https://api.github.com/user
📌 说明:
client-id
和client-secret
是你在 GitHub Developer Settings 中注册应用时获得的scope
指定需要访问的资源范围,read:user
和user:email
可获取用户名和邮箱- 三个 URI 分别对应 OAuth2 流程中的授权、令牌获取和用户信息接口
通过这种方式,我们把用户认证甩锅给 GitHub,省去了账号系统开发的麻烦,专注业务逻辑。
4. 自定义提取 Principal 与 Authorities
当你的应用作为 OAuth 客户端时,整个流程大致分为三步:
- ✅ 用户在第三方平台完成认证(如 GitHub 登录)
- ✅ 用户授权你的应用访问其数据(通过 scope 控制)
- ✅ 使用获取到的 access token 调用
user-info-uri
获取用户信息
Spring 默认会根据返回的用户信息自动构建 Principal
和 Authorities
,但往往不够用。比如:
- 默认 Principal 是一个
OAuth2User
对象,不好直接用 - Authorities 通常是空的或固定值,无法体现用户角色
解决方案:自定义 Extractor
Spring 提供了两个接口让你接管提取逻辑:
PrincipalExtractor
:自定义 Principal 内容AuthoritiesExtractor
:自定义 Authorities(权限/角色)
✅ 注意:这两个接口属于
spring-security-oauth2-autoconfigure
,不是 Spring Security 核心模块。
默认实现是 FixedPrincipalExtractor
和 FixedAuthoritiesExtractor
,策略固定,灵活性差。我们可以通过注册自定义 Bean 来覆盖默认行为。
4.1 自定义 GitHub 认证逻辑
我们知道 GitHub 的用户接口返回结构(参考 GitHub API 文档),可以据此定制。
自定义 PrincipalExtractor
我们希望 Principal 就是用户的 GitHub 用户名(login
字段):
public class GithubPrincipalExtractor implements PrincipalExtractor {
@Override
public Object extractPrincipal(Map<String, Object> map) {
return map.get("login");
}
}
简单粗暴,直接从用户信息 JSON 中取 login
字段作为 Principal。
自定义 AuthoritiesExtractor
根据用户是否为付费账户(plan 字段),分配不同权限:
public class GithubAuthoritiesExtractor implements AuthoritiesExtractor {
List<GrantedAuthority> GITHUB_FREE_AUTHORITIES =
AuthorityUtils.commaSeparatedStringToAuthorityList(
"GITHUB_USER,GITHUB_USER_FREE");
List<GrantedAuthority> GITHUB_SUBSCRIBED_AUTHORITIES =
AuthorityUtils.commaSeparatedStringToAuthorityList(
"GITHUB_USER,GITHUB_USER_SUBSCRIBED");
@Override
public List<GrantedAuthority> extractAuthorities(Map<String, Object> map) {
if (Objects.nonNull(map.get("plan"))) {
LinkedHashMap<String, Object> plan = (LinkedHashMap<String, Object>) map.get("plan");
if (!"free".equals(plan.get("name"))) {
return GITHUB_SUBSCRIBED_AUTHORITIES;
}
}
return GITHUB_FREE_AUTHORITIES;
}
}
📌 踩坑提醒:map.get("plan")
返回的是 LinkedHashMap
,注意类型转换。
注册为 Spring Bean
在 SecurityConfig
中注册这两个组件:
@Configuration
public class SecurityConfig {
// ... 其他配置
@Bean
public PrincipalExtractor githubPrincipalExtractor() {
return new GithubPrincipalExtractor();
}
@Bean
public AuthoritiesExtractor githubAuthoritiesExtractor() {
return new GithubAuthoritiesExtractor();
}
}
只要 Bean 名称或类型匹配,Spring 会自动注入并替换默认实现。
4.2 使用自定义授权服务器
你也可以不依赖 GitHub,而是使用自己的授权服务器(例如基于 Spring Security OAuth2 搭建的 SSO 系统)。
配置自定义客户端
spring.security.oauth2.client.registration.baeldung.client-id=SampleClientId
spring.security.oauth2.client.registration.baeldung.client-secret=secret
spring.security.oauth2.client.provider.baeldung.token-uri=http://localhost:8081/auth/oauth/token
spring.security.oauth2.client.provider.baeldung.authorization-uri=http://localhost:8081/auth/oauth/authorize
spring.security.oauth2.client.provider.baeldung.user-info-uri=http://localhost:8081/auth/user/me
这里我们注册了一个名为 baeldung
的客户端,指向本地运行的授权服务器。
自定义 PrincipalExtractor
假设用户信息接口返回的 JSON 中包含 name
字段,我们用它作为 Principal:
public class BaeldungPrincipalExtractor implements PrincipalExtractor {
@Override
public Object extractPrincipal(Map<String, Object> map) {
return map.get("name");
}
}
自定义 AuthoritiesExtractor
假设授权服务器在 /user/me
接口中返回了用户的权限列表(格式为 List<Map<String, String>>
),我们可以将其转换为 GrantedAuthority
:
public class BaeldungAuthoritiesExtractor implements AuthoritiesExtractor {
@Override
public List<GrantedAuthority> extractAuthorities(Map<String, Object> map) {
return AuthorityUtils.commaSeparatedStringToAuthorityList(asAuthorities(map));
}
private String asAuthorities(Map<String, Object> map) {
List<String> authorities = new ArrayList<>();
authorities.add("BAELDUNG_USER"); // 所有通过该服务器登录的用户都拥有此权限
List<LinkedHashMap<String, String>> authz =
(List<LinkedHashMap<String, String>>) map.get("authorities");
for (LinkedHashMap<String, String> entry : authz) {
authorities.add(entry.get("authority"));
}
return String.join(",", authorities);
}
}
注册自定义 Extractor Bean
@Configuration
public class SecurityConfig {
// ...
@Bean
public PrincipalExtractor baeldungPrincipalExtractor() {
return new BaeldungPrincipalExtractor();
}
@Bean
public AuthoritiesExtractor baeldungAuthoritiesExtractor() {
return new BaeldungAuthoritiesExtractor();
}
}
✅ 提示:Spring 会根据当前使用的 OAuth2 客户端自动选择对应的 Extractor。多个 Extractor 可共存,按需生效。
5. 总结
本文演示了:
- 如何通过 Spring Security OAuth2 实现 GitHub 第三方登录
- 如何对接自定义授权服务器
- 如何通过
PrincipalExtractor
和AuthoritiesExtractor
接口,完全掌控用户身份和权限的提取逻辑
✅ 这种方式在需要精细化权限控制、与内部系统集成时非常实用。
示例代码已上传至 GitHub:https://github.com/eugenp/tutorials/tree/master/spring-security-modules/spring-security-oauth2
本地运行时,访问 http://localhost:8082
即可测试。