乐途乐途
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
  • 学习路径
  • 第1章 Spring Security 基础

    • 本章定位
    • Spring Security 是什么
    • DelegatingFilterProxy
    • 安全过滤器链
    • SecurityFilterChain
    • 过滤器执行顺序
  • 第2章 认证

    • 本章定位
    • Authentication
    • 认证流程
    • AuthenticationManager
    • ProviderManager
    • DaoAuthenticationProvider
    • UserDetails
    • UserDetailsService
    • PasswordEncoder
    • BCryptPasswordEncoder
    • DelegatingPasswordEncoder
    • 表单登录
    • SecurityContext
    • SecurityContextHolder
    • UsernamePasswordAuthenticationToken
  • 第3章 授权

    • 本章定位
    • 授权模型
    • GrantedAuthority
    • AccessDecisionManager
    • AccessDecisionVoter
    • URL 级别授权
    • 方法级别安全
    • @PreAuthorize
    • @PostAuthorize
    • @PreFilter
    • @PostFilter
    • @Secured
    • RoleHierarchy
  • 第4章 过滤器链

    • 本章定位
    • FilterChainProxy
    • SecurityContextHolderFilter
    • LogoutFilter
    • BasicAuthenticationFilter
    • CsrfFilter
    • CorsFilter
    • HeaderWriterFilter
    • AnonymousAuthenticationFilter
    • RequestCacheAwareFilter
    • ExceptionTranslationFilter
    • FilterSecurityInterceptor
  • 第5章 会话管理

    • 本章定位
    • 会话管理
    • SessionFixation
    • 会话并发控制
    • SessionCreationPolicy
    • RememberMe
  • 第6章 JWT

    • 本章定位
    • JWT
    • JwtDecoder
    • JWT 认证
    • JwtAuthenticationConverter
  • 第7章 OAuth2

    • 本章定位
    • OAuth2 基础
    • OAuth2 Client
    • OAuth2 Resource Server
    • 第三方登录配置
  • 第8章 攻击防护

    • 本章定位
    • CSRF 跨站请求伪造防护
    • CORS 跨域防护
    • Clickjacking 点击劫持防护
    • 安全响应头
    • Session Fixation 会话固定防护
  • 第9章 测试

    • 本章定位
    • 安全测试
    • @WithMockUser
    • 最佳实践

OAuth2 Client

定义与作用

OAuth2 Client 是指在 OAuth2 授权流程中代表资源所有者(用户)发起资源访问请求的客户端应用。在 Spring Security 5.x 中,OAuth2 Client 模块负责处理用户向授权服务器(如 Google、GitHub、企业自建 OIDC 服务)发起登录请求、接收授权码回调、换取 Access Token 以及后续获取用户信息的完整链路。

对比自建用户体系的痛点

痛点自建用户体系OAuth2 Client 方案
用户注册转化率低用户需要填写用户名、密码、邮箱验证,流程长、流失率高点击"使用 GitHub 登录"即可,复用已有平台账号,3 步完成注册
密码管理成本高需实现密码加密、找回密码、密码强度策略、定期改密提醒密码管理完全由授权服务器承担,客户端无需存储密码
多因素认证(MFA)实现复杂自建短信/邮箱 MFA 开发成本高,第三方库集成复杂授权服务器(如 Google)原生支持 MFA,客户端零成本获得
社交关系导入自建体系无法获取用户的社交图谱,难以做好友推荐通过授权获取用户的基本信息、联系人、组织成员等社交数据
安全事件响应一旦发生密码泄露,需要批量重置密码并通知用户授权服务器负责安全事件响应,客户端只需撤销 Token 绑定关系

Spring Security 5.x 的 spring-boot-starter-oauth2-client 自动配置模块提供了 ClientRegistration(客户端注册信息)和 ClientRegistrationRepository(注册仓库),开发者只需在 application.yml 中填写 registration 和 provider 配置即可接入第三方登录。


核心原理

OAuth2 Client 登录流程(Authorization Code 模式)

registration 与 provider 配置模型

Spring Security 5.x 将 OAuth2 Client 配置抽象为两个层级:

配置层级字段说明
registrationclient-id客户端在授权服务器注册时获得的标识
registrationclient-secret客户端密钥,用于后端向授权服务器换取 Token(必须保密)
registrationauthorization-grant-type授权模式:authorization_code 或 client_credentials
registrationredirect-uri授权服务器回调地址,Spring Security 默认模板为 {baseUrl}/login/oauth2/code/{registrationId}
registrationscope请求权限范围,如 read:user(GitHub)、profile email(Google)
providerauthorization-uri授权端点,用户在此页面确认授权
providertoken-uriToken 端点,客户端用授权码换取 Access Token
provideruser-info-uri用户信息端点,获取用户头像、邮箱等公开信息
provideruser-name-attribute从 UserInfo 响应中提取用户唯一标识的字段名(如 id、sub)
providerjwk-set-uri用于 JWT 签名校验的公钥端点(OIDC 场景)

完整示例

示例一:GitHub 社交登录配置

场景说明:一个开发者社区网站需要支持 GitHub 账号登录,方便开发者一键注册。需要获取用户的 GitHub ID、用户名、头像和公开邮箱。

操作前配置(在 GitHub 注册 OAuth App):

  1. 登录 GitHub → Settings → Developer settings → OAuth Apps → New OAuth App
  2. Application name: DevCommunity
  3. Homepage URL: http://localhost:8080
  4. Authorization callback URL: http://localhost:8080/login/oauth2/code/github
  5. 生成 Client ID 和 Client Secret

操作后配置(application.yml):

spring:
  security:
    oauth2:
      client:
        registration:
          github:
            client-id: Ov23lixxxxxxxxxxxx
            client-secret: 1a2b3c4d5e6f7g8h9i0jxxxxxxxxxxxxxxxxx
            authorization-grant-type: authorization_code
            redirect-uri: "{baseUrl}/login/oauth2/code/{registrationId}"
            scope: read:user, user:email
            client-name: GitHub
        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: id

操作后配置(Spring Security 5.x 配置类):

@Configuration
@EnableWebSecurity
public class GitHubOAuth2ClientConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests(authz -> authz
                .antMatchers("/", "/login", "/css/**", "/js/**").permitAll()
                .antMatchers("/api/public/**").permitAll()
                .antMatchers("/developer/**").hasAnyRole("USER", "ADMIN")
                .antMatchers("/admin/**").hasRole("ADMIN")
                .anyRequest().authenticated()
            )
            .oauth2Login(oauth2 -> oauth2
                .loginPage("/login")
                .defaultSuccessUrl("/developer/dashboard", true)
                .failureUrl("/login?error=true")
                .permitAll()
            )
            .logout(logout -> logout
                .logoutSuccessUrl("/")
                .invalidateHttpSession(true)
                .deleteCookies("JSESSIONID")
                .permitAll()
            );
    }
}

结果分析:

  1. 用户访问 /developer/dashboard 被拦截,重定向到 /login
  2. 登录页提供"GitHub 登录"按钮(Spring Security 自动根据 registration 列表生成)
  3. 用户点击后,浏览器被重定向到 https://github.com/login/oauth/authorize?client_id=Ov23lixxxxxxxxxxxx&redirect_uri=http://localhost:8080/login/oauth2/code/github&response_type=code&scope=read:user%20user:email&state=...
  4. 用户在 GitHub 授权页确认后,GitHub 重定向回 http://localhost:8080/login/oauth2/code/github?code=...&state=...
  5. Spring Security 的 OAuth2LoginAuthenticationFilter 拦截回调,用 code + client_secret 向 https://github.com/login/oauth/access_token 换取 Token
  6. 获取 Token 后,自动访问 https://api.github.com/user 获取用户信息,构造 OAuth2User
  7. 用户登录成功,重定向到 /developer/dashboard,Authentication 中 principal 为 OAuth2User,权限包含 ROLE_USER

示例二:Google 社交登录配置(OIDC 场景)

场景说明:企业内部工具需要接入 Google Workspace 登录,利用 Google 的身份基础设施实现单点登录。Google 是标准的 OIDC 提供商,支持 openid scope 和 ID Token。

操作前配置(在 Google Cloud Console 注册):

  1. 访问 https://console.cloud.google.com/apis/credentials
  2. 创建 OAuth 2.0 Client ID
  3. Authorized redirect URIs: http://localhost:8080/login/oauth2/code/google
  4. 启用 Google+ API 和 People API

操作后配置(application.yml):

spring:
  security:
    oauth2:
      client:
        registration:
          google:
            client-id: 123456789012-abcdefghijklmnopqrstuvwxyz.apps.googleusercontent.com
            client-secret: GOCSPX-xxxxxxxxxxxxxxxxxxxxxxxx
            authorization-grant-type: authorization_code
            redirect-uri: "{baseUrl}/login/oauth2/code/{registrationId}"
            scope: openid, profile, email
            client-name: Google
        provider:
          google:
            authorization-uri: https://accounts.google.com/o/oauth2/v2/auth
            token-uri: https://oauth2.googleapis.com/token
            user-info-uri: https://openidconnect.googleapis.com/v1/userinfo
            user-name-attribute: sub
            jwk-set-uri: https://www.googleapis.com/oauth2/v3/certs

操作后配置(Spring Security 5.x 配置类,提取 Google 用户组织信息):

@Configuration
@EnableWebSecurity
public class GoogleOAuth2ClientConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests(authz -> authz
                .antMatchers("/", "/login", "/oauth2/**", "/error").permitAll()
                .antMatchers("/workspace/**").hasAnyRole("USER", "ADMIN")
                .anyRequest().authenticated()
            )
            .oauth2Login(oauth2 -> oauth2
                .loginPage("/login")
                .defaultSuccessUrl("/workspace/home", true)
                .userInfoEndpoint(userInfo -> userInfo
                    .userAuthoritiesMapper(this::mapGoogleAuthorities)
                )
            );
    }

    private Collection<? extends GrantedAuthority> mapGoogleAuthorities(
            Collection<? extends GrantedAuthority> authorities) {
        Set<GrantedAuthority> mappedAuthorities = new HashSet<>(authorities);
        
        // Google 返回的 scope 权限会映射为 OAUTH2_USER 或 SCOPE_ 前缀
        // 这里为所有 Google 登录用户追加 ROLE_WORKSPACE_USER
        mappedAuthorities.add(new SimpleGrantedAuthority("ROLE_WORKSPACE_USER"));
        return mappedAuthorities;
    }
}

结果分析:

  1. Google 使用标准 OIDC 流程,scope 包含 openid 时授权服务器会返回 id_token(JWT 格式)
  2. Spring Security 自动解析 id_token 中的 sub(用户唯一标识)、email、name、picture 等 claims
  3. user-name-attribute: sub 确保无论用户修改邮箱,系统都能用 sub 稳定识别同一用户
  4. jwk-set-uri 用于校验 id_token 的 RSA 签名,Spring Security 自动从 Google 的证书端点获取公钥
  5. 自定义 userAuthoritiesMapper 为所有 Google 登录用户附加 ROLE_WORKSPACE_USER,方便后续授权判断
  6. 登录成功后,OAuth2User.getAttributes() 包含 sub, name, given_name, family_name, picture, email, email_verified, locale 等字段

易错场景与面试考点

易错场景:scope 配置遗漏导致获取不到用户邮箱或头像

不同授权服务器对 scope 的默认值和必填项要求不同。若配置中遗漏关键 scope,会导致授权成功但用户信息端点返回的数据不完整。

错误配置(GitHub):

spring:
  security:
    oauth2:
      client:
        registration:
          github:
            client-id: xxx
            client-secret: xxx
            scope: ""  # 或遗漏 scope 字段

现象:用户成功登录,但 OAuth2User.getAttributes() 中只返回 login 和 id,email 字段为 null,avatar_url 缺失。

原因:GitHub 默认 scope 为空时只授权访问用户基本信息,不返回邮箱和头像。必须显式声明 read:user(获取用户资料)和 user:email(获取邮箱)。

正确配置:

scope: read:user, user:email

常见平台 scope 对照表:

平台获取基本信息获取邮箱获取头像/照片
GitHubread:useruser:emailread:user 已包含 avatar_url
Googleprofileemailprofile 已包含 picture
Facebookpublic_profileemailpublic_profile 已包含 picture

面试考点:

  • 面试官常问:Spring Security 5.x 的 OAuth2LoginAuthenticationFilter 默认拦截哪个路径?
  • 答:OAuth2LoginAuthenticationFilter 默认拦截 /login/oauth2/code/{registrationId},这是授权服务器回调的默认地址模板。如果自定义了 redirect-uri,则必须确保该 URI 被 OAuth2LoginAuthenticationFilter 拦截,否则 Spring Security 无法完成授权码换取 Token 的操作。可以通过 http.oauth2Login().loginProcessingUrl("/custom/callback") 修改拦截路径,但对应的 redirect-uri 也必须同步修改。

版本说明:本节基于 Spring Security 5.x 编写。5.x 使用 WebSecurityConfigurerAdapter 和 antMatchers 作为标准配置方式。升级到 6.x 后,需改用 SecurityFilterChain Bean 和 requestMatchers,同时 userInfoEndpoint() 的 userAuthoritiesMapper 配置方式保持不变。

上一页
OAuth2 基础
下一页
OAuth2 Resource Server