前阵子给团队搭了一套统一认证,用Spring Boot和Spring Authorization Server实现OAuth2.0授权服务器,再对接公司现有的LDAP目录。整个过程从调研、编码到上线花了差不多两周,中间被授权码流程的回调、token校验、LDAP组同步这些问题轮番折腾了一遍。这篇文章就把整个方案从原理到落地的思路完整梳理一遍,重点说清楚OAuth2.0的核心机制、四种授权模式的取舍、SpringBoot实战里的关键配置,以及如何把LDAP作为用户源接入,适合正在做统一登录、想了解OAuth2.0完整落地细节的同学参考。文章里所有代码和配置都来自我实际跑通的版本,没有做简化处理,可以直接照着抄。
1. 为什么放着现成的IDaaS不用,非要在SpringBoot里自己搭
1.1 豆包项目里的登录困局
团队交付一个内部系统时,接了个硬骨头:统一认证。现状是内部有三个系统——知识库、审批流、BI看板,各自维护一套账号密码。员工每换一个系统就要重新登录,管理员每天收到一堆"我密码忘了"的工单。更麻烦的是,外部合作方偶尔要访问其中一个看板,我们不能把内部账号直接给出去。
当时第一反应是找个现成的IDaaS服务,但数据合规要求账号体系必须落在自己的目录服务里,不能出内网。调研一圈,结论很直接:自己做。技术栈选型其实没怎么纠结,团队本来就用Spring Boot 3.2,安全侧已经有Spring Security的成熟积累,自然的方案就是Spring Authorization Server,项目代号就叫豆包。开发过程中我大量借助AI编码助手来生成脚手架、排查配置错误,把原来可能要三周的排期压缩到了两周这段经历后面会具体讲到。
1.2 为什么OAuth2.0是这个场景的最优解
先回答一个很多人会问的问题:自己系统登录,用Session不就行了吗?为什么要上升到OAuth2.0?
如果只有一套系统,Session确实够用,最多加个Redis共享会话做集群。但这里的关键词是"外部应用接入"。内部知识库要接入统一认证中心,BI平台也要接入,未来新系统都会接入。OAuth2.0解决的是"第三方客户端怎么安全地访问受保护资源"的问题,它把认证和授权拆开了:你是谁(登录)和你能干什么(拿什么token访问什么接口)变成两个独立环节。
相比之下,Session模式的天然缺陷很明显:会话状态存在服务端,跨系统分享要么搞SSO单点登录的复杂改造,要么把Session拷来拷去,安全边界很模糊。JWT直接自签虽然轻量,但没有标准的"换token"协议,客户端怎么拿token、怎么续期全得自己定,很容易设计出漏洞。OAuth2.0的价值在于它把整个授权流程协议化了:客户端、授权服务器、资源服务器之间怎么交互,回调地址怎么校验,token怎么发,过期了怎么刷新,全是约定好的。而且Spring生态里有现成实现,对接成本比自研低得多。
2. 被讲烂的四个角色,这次用一次完整请求讲清楚
2.1 四个角色的职责边界
OAuth2.0里一共四个角色,名字都很反直觉,我第一次看也绕了很久:
| 角色 | 通俗理解 | 在我们项目里的对应物 |
|---|---|---|
| Resource Owner | 拥有数据的人 | 员工本人 |
| Client | 要访问数据的应用 | 知识库、BI看板 |
| Authorization Server | 发token的认证中心 | auth-server服务 |
| Resource Server | 存数据、校验token的服务 | 业务系统的后端API |
这四个角色在物理上可以是一个服务也可以分开部署,但逻辑上必须清晰区分。我们的拓扑是:auth-server单独一个服务,三个业务系统各配一套Security过滤链做资源服务器,登录页面统一由auth-server渲染。
2.2 授权码模式下的一次完整请求路径
把整个过程串起来看,授权码模式是最常用也最完整的流程,后续所有模式都是它的简化变体。以"用户从知识库点击登录"为例:
- 知识库前端发现用户未登录,跳转到auth-server的授权端点:
/oauth2/authorize?client_id=wiki&redirect_uri=https://wiki.example.com/login/callback&response_type=code&scope=read&state=abc123。 - auth-server检测到用户未认证,重定向到登录页。用户输入账号密码,认证通过。
- auth-server发现客户端wiki是第一次授权,渲染授权确认页——"知识库想要访问你的以下权限:读取文档",用户点击同意。
- 生成一个一次性授权码code,通过302把浏览器带回
redirect_uri,URL长这样:https://wiki.example.com/login/callback?code=8xPqd3&state=abc123。 - 知识库后端收到code之后,绝不是从浏览器再拿一次,而是后端直连auth-server的令牌端点:
POST /oauth2/token,带client_id、client_secret、code、redirect_uri,换取真正的access_token。 - auth-server校验client_secret和code有效后,下发热token。知识库后续请求都把它放在HTTP头里:
Authorization: Bearer <token>。 - 知识库带着token去请求BI平台的API,BI后端校验token签名和有效期,返回数据。
2.3 为什么授权码模式是最安全的
关键就在第5步:token没有经过浏览器。授权码只是临时凭证,一次性、几秒就过期。即使浏览器被恶意脚本截获了code,攻击者没有client_secret,也换不到token。这是隐式模式下做不到的,也是OAuth2.1草案直接废除隐式模式的原因。
还有一个容易忽略的细节:state参数。第1步带上随机state,第4步回调时校验它和发送前是否一致,可以有效防止CSRF攻击。攻击者伪造授权URL诱导用户点击,如果回调不带state,攻击者就能用用户已登录的会话完成授权,拿到code。带上state相当于给这次授权请求发了一个一次性令牌。
3. 四种授权模式怎么选:一张决策表和一条判断路径
3.1 一张表看清四种模式
先做一个对比,后面逐个展开。
| 模式 | 用户参与 | client_secret | token获取渠道 | 适用场景 | 安全等级 |
|---|---|---|---|---|---|
| 授权码模式 | 是 | 需要 | 后端直连授权服务器 | Web应用 | 最高 |
| 授权码+PKCE | 是 | 可以不要 | 后端直连授权服务器 | SPA、移动App | 高 |
| 隐式模式 | 是 | 不需要 | 直接通过浏览器回调 | 老式SPA | 低,已废弃 |
| 客户端凭证模式 | 否 | 需要 | 后端直连授权服务器 | 服务器到服务器 | 高 |
| 密码模式 | 是 | 需要 | 客户端代用户提交账号密码 | 第一方可信应用 | 中,不推荐 |
注意一个事实:Spring Authorization Server默认只支持授权码模式和客户端凭证模式,设计上就屏蔽了隐式模式和密码模式。这个取舍是有道理的,下面解释。
3.2 每种模式分别适合谁
授权码模式不多说,上一章完整讲过。如果客户端是纯前端SPA或者移动App,无法安全保存client_secret,建议在授权码模式之上加PKCE。原理是客户端先生成一个随机字符串verifier,再派生出一个Challenge一起发给授权服务器;换token的时候要带上verifier,授权服务器验证一致性。即使授权码在传输中被截获,没有verifier也换不到token。
隐式模式是OAuth2.0早期为SPA设计的,直接在浏览器重定向里返回access_token。它够简单,但token暴露在URL里,会进浏览器历史记录,还会被Referer头泄露。2019年OAuth2.1草案就把这个模式删了,新项目我强烈建议不要碰。
客户端凭证模式适合没有用户参与的服务间调用。比如一个定时任务要从BI平台拉数据,直接用client_id和client_secret去token端点换token。这种token通常不给refresh_token,过期了重新换就行,因为整个过程是自动化的。
密码模式允许用户把账号密码直接交给客户端,由客户端代为换token。这个设计违背了"第三方应用不应碰用户密码"的原则,只在绝对信任的第一方应用里才会考虑。SAS干脆不实现它,我建议任何项目都不要启用。
3.3 两个问题定位正确模式
实际选型不需要背表,问两个问题即可:
- 这个场景有没有用户参与?没有,选客户端凭证模式。
- 有用户参与,客户端能不能安全保存client_secret?能,选授权码模式;不能,选授权码+PKCE。
我们落地时就是用这条路径做决策的,内部分享时大家普遍觉得比死记模式名称有用得多。
4. SpringBoot实战:从零搭一个可用的授权服务器
4.1 技术选型和工程结构
先说两个容易翻车的点:版本和框架。
Spring Boot 2.x时代,社区大多在用Spring Security OAuth,这个项目已经停止维护了;或者自己基于Spring Security手写授权端点,配置非常痛苦。Spring Boot 3.x之后官方把OAuth2.0授权服务器整合进了Spring Security,就是Spring Authorization Server 1.x,artifactId是spring-security-oauth2-authorization-server。我这次用的是Spring Boot 3.2.5,对应Spring Security 6.2.x,SAS版本1.2.3,集成方式比老代码整洁一个数量级。开发的时候我用豆包把SAS的配置差异和旧版Spring Security OAuth做了一份对照表,省去大量翻文档的时间。
工程结构按职责拆成两个服务加一个demo客户端:
- auth-server:授权服务器,负责登录、授权码下发、token签发和存储。
- resource-server:资源服务器,负责校验token,提供受保护接口。
- client-demo:模拟第三方接入,演示授权码换token的完整流程。
一个原则放在前面:授权服务器和资源服务器必须分离部署,至少在逻辑上分离。很多人图省事把两者写在同一个服务里,开发阶段没问题,但生产上token签发和资源校验对扩容策略的要求完全不同,混在一起迟早要拆。
4.2 核心依赖和客户端注册
先看pom里最关键的依赖:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-oauth2-authorization-server</artifactId> <version>1.2.3</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>RegisteredClient是OAuth2.0里的"客户端"定义。我建议客户端信息不要写死在代码里,而是设计成可以动态维护的表结构,因为新应用接入是常态,每接入一个就发一次代码不现实。生产上我用一张oauth2_registered_client表存client_id、client_secret(BCrypt加密存)、redirect_uris、grant_types、scopes,用JdbcRegisteredClientRepository加载。演示阶段先用内存注册,最简单:
@Bean public RegisteredClientRepository registeredClientRepository() { RegisteredClient wikiClient = RegisteredClient.withId(UUID.randomUUID().toString()) .clientId("wiki") .clientSecret("{noop}wiki-secret") .redirectUri("https://wiki.example.com/login/callback") .redirectUri("http://127.0.0.1:8082/login/callback") .scope("read") .scope("write") .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC) .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN) .tokenSettings(TokenSettings.builder() .accessTokenFormat(OAuth2TokenFormat.REFERENCE) .accessTokenTimeToLive(Duration.ofMinutes(30)) .refreshTokenTimeToLive(Duration.ofDays(7)) .reuseRefreshTokens(false) .build()) .build(); return new InMemoryRegisteredClientRepository(wikiClient); }有个细节值得展开:tokenSettings里我把accessTokenFormat设成了REFERENCE而不是SELF_CONTAINED。SELF_CONTAINED是JWT,带签名可自己校验,性能好;REFERENCE是不透明字符串,授权服务器要在Redis里查token状态。生产环境我选了REFERENCE,因为企业内部服务大多是内网调用,校验token的那次Redis查询延迟在0.5ms以内,换来的是token可以随时撤销——JWT一旦签发,在过期之前几乎无法撤回,这是JWT最被诟病的地方。
4.3 授权服务器安全配置的关键代码
安全配置是整个授权服务器最容易写错的部分,很多人直接拿Spring Security默认配置来改,结果登录页渲染不出来、授权端点404。核心配置长这样:
@Configuration @EnableWebSecurity public class AuthorizationServerConfig { @Bean @Order(1) public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { http .securityMatcher("/oauth2/**", "/login/**") .authorizeHttpRequests(authorize -> authorize .requestMatchers("/login", "/oauth2/consent").permitAll() .anyRequest().authenticated()) .csrf(csrf -> csrf.ignoringRequestMatchers("/oauth2/token")) .oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults())) .formLogin(form -> form.loginPage("/login")); return http.build(); } }注意几个关键点:
@Order(1)很重要。授权服务器过滤链必须优先级最高,否则会被后面资源服务器的过滤链抢先匹配。/oauth2/token必须放行CSRF,因为客户端后端用它换token的请求不带浏览器Session,CSRF防护反而误伤。- 如果自定义了登录页,务必把
/login设成permitAll,否则登录页会被重定向死循环。
4.4 Token存储、登录页和一点小优化
Token默认存在内存里,生产环境重启即失效,必须落到Redis。SAS提供了OAuth2AuthorizationService接口,我把默认的InMemoryOAuth2AuthorizationService换成了Redis实现:
@Bean public OAuth2AuthorizationService authorizationService( ObjectMapper objectMapper, RedisConnectionFactory connectionFactory) { return new RedisOAuth2AuthorizationService(objectMapper, connectionFactory); }这个RedisService是我自己封装的一层,key设计成oauth2:authorization:{id}。Refresh Token轮换后旧token的删除要同时处理,不然Redis里会堆积死数据。7天有效期、每天换token的客户端,一个用户一年会产生几十条记录,不做清理三个月后内存会明显上涨。
登录页面我用Thymeleaf模板实现,自定义了一个简单的login.html,交互是:ajax提交、错误提示、加载状态。我做授权服务器和做业务系统有个明显感受:登录页属于安全链路的一部分,任何前端渲染错误影响的都是认证链路本身,所以没有引入前端工程化那一套,模板引擎足够。
4.5 资源服务器如何校验token
资源服务器配置简单很多,Spring Security已经把校验流程封装完。如果授权服务器签发的是JWT,配置是这个:
spring: security: oauth2: resourceserver: jwt: jwk-set-uri: http://auth-server.internal:9000/oauth2/jwks如果像我一样选择REFERENCE不透明token,改用 introspection 端点:
spring: security: oauth2: resourceserver: opaque-token: introspection-uri: http://auth-server.internal:9000/oauth2/introspect introspection-client-id: resource-server introspection-client-secret: resource-secret这里有一个非常关键的点:REFERENCE token的校验是同步调用授权服务器的introspect端点的。如果资源服务器和授权服务器之间网络抖动,所有带token的请求都会变慢甚至失败。所以要在资源服务器里加一个token校验结果缓存,缓存几分钟。我调的是5分钟,性能损失很小,同时保留了token可撤销的特性。如果不加缓存,秒级几千QPS的压测下,授权服务器的introspect接口会先扛不住。
5. LDAP联动:让组织架构直接变成OAuth2.0的用户源
5.1 LDAP到底是干什么的
说到LDAP,很多做业务开发的同事没怎么接触过。LDAP全称是Lightweight Directory Access Protocol,可以理解成一种专门为"读多写极少"场景设计的树形数据库。公司组织架构天然就是树:根节点是dc=example,dc=com,下面是部门OU,部门下面是用户CN。最常见的条目DN长这样:
uid=zhangsan,ou=研发部,ou=人员,dc=example,dc=comDN是树里每个节点的全路径,类似文件系统里的绝对路径。用户登录时,我们用这个DN和用户输入的密码去LDAP服务器做bind操作,成功就说明身份正确。这个过程很像去银行柜台办业务:报上客户号,出示证件,柜员验证通过就办后续业务。
我们公司LDAP里已经维护了所有员工账号和部门关系,Jira、Confluence这些系统早就用它在做认证。所以新做的OAuth2.0授权服务器完全没必要再建一套用户表,直接把LDAP当成UserDetailsService的数据源。
5.2 SpringBoot接入LDAP的配置
Spring Boot对LDAP支持很成熟,spring-boot-starter-data-ldap把自动配置都做完了。依赖就两个:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-ldap</artifactId> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-ldap</artifactId> </dependency>连接参数配好:
spring: ldap: urls: ldap://ldap.internal:389 base: dc=example,dc=com username: cn=admin,dc=example,dc=com password: admin-password pool: min-idle: 2 max-total: 20然后写一个LdapUserDetailsService,把LDAP里的人映射成Spring Security能识别的UserDetails:
@Component public class LdapUserDetailsService implements UserDetailsService { private final LdapTemplate ldapTemplate; public LdapUserDetailsService(LdapTemplate ldapTemplate) { this.ldapTemplate = ldapTemplate; } @Override public UserDetails loadUserByUsername(String username) { List<Attributes> attrs = ldapTemplate.search( "ou=人员", "(uid={0})", new String[]{username}, new String[]{"uid", "cn", "employeeType", "mail"} ); if (attrs.isEmpty()) { throw new UsernameNotFoundException("user not found: " + username); } return User.withUsername(username) .password("") .roles(loadRoles(username)) .build(); } }注意上面代码有个坑,我写代码的时候踩过:如果直接在UserDetailsService里把密码留空,Spring Security默认的DaoAuthenticationProvider验密码时会出问题。正确做法是配置LdapAuthenticationProvider,它内置了对LDAP bind的密码校验逻辑:
@Bean public AuthenticationProvider ldapAuthenticationProvider( LdapContextSource contextSource) { LdapAuthenticationProvider provider = new LdapAuthenticationProvider( new BindAuthenticator(contextSource), new DefaultAuthoritiesPopulator(contextSource) ); return provider; }这个改动看似只是换AuthenticationProvider,实际解决了一个很关键的架构问题:密码永远不落在应用服务器上。用户的明文密码只在浏览器到auth-server之间传递一次,之后auth-server拿它去LDAP做bind,绑定成功即认证通过,本地没有任何密码存储,也就不存在密码哈希库泄露的问题。这在合规审计时是很大的加分项。
5.3 组关系和角色的映射逻辑
LDAP里的组有两种常见结构:groupOfNames和groupOfUniqueNames。我这次用的是groupOfNames,成员通过member属性引用用户DN:
cn=wiki-users,ou=组,ou=人员,dc=example,dc=com member: uid=zhangsan,ou=研发部,ou=人员,dc=example,dc=com member: uid=lisi,ou=产品部,ou=人员,dc=example,dc=com同步组关系的核心是做一个定时任务,把LDAP里的组和成员拉下来,组织成"用户名 -> 角色列表"的映射。不需要实时同步,每天凌晨全量刷一次就够,因为内部系统的成员变化本来就不频繁。我是用ScheduledTask实现,搜索所有groupOfNames,再对每个组读取member属性,最后写入authorityMapping。
实际操作中几个Field映射细节值得先确认:有些LDAP服务用memberUid而不是member,格式是纯UID不带CN路径;有些环境采用嵌套组,组里有组,遍历时最好加深度限制。我踩过嵌套组的坑,最深套了5层,递归时不去重直接爆栈。
5.4 接入之后要额外注意的三个运维点
第一,权限粒度。LDAP的组往往跟组织架构绑定,比如"研发部"组,但业务系统需要更细的权限,比如"wiki管理员"。不要让Spring Security的角色名直接等于LDAP组名,中间加一层映射转换会灵活很多。我们配了一张权限映射表,把LDAP组wiki-admins映射成ROLE_WIKI_ADMIN,以后权限调整不动LDAP也能灵活配置。
第二,连接稳定性。LDAP连接池的timeout一定要配,尤其是跨网络的情况。我们遇到过LDAP服务器短暂网络抖动,默认TCP超时是几十秒,结果auth-server的登录线程全部卡在连接上,用户点击登录页面完全无响应。把连接超时设置成3000ms、读取超时设置成5000ms之后,最坏情况只是登录失败提示稍慢,不会拖垮整个服务。
第三,和Jira这类系统对比。如果你给Jira配过LDAP认证,思路其实完全一样,Jira里填的连接URL、base DN、用户过滤条件,在Spring Boot里对应spring.ldap.urls、base和LdapTemplate的search参数。参考Jira的LDAP配置页,可以快速确认你们的目录结构,比自己去ldapsearch摸底直观得多。
6. 真实跑通的完整链路检查与六个踩坑记录
6.1 一次完整登录请求的链路检查
全部配置做完之后,我从客户端demo出发模拟一次真实的授权码流程,每一步都加了检查点:
- 浏览器访问client-demo首页,点击"登录",demo服务把用户重定向到授权服务器的/authorize。
- 授权服务器跳转登录页,输入测试账号,LDAP bind成功,跳转到授权确认页。
- 点击同意,重定向回demo的callback,URL上带code和state。
- demo后端拿code发POST到/oauth2/token,带client_id、client_secret、redirect_uri、grant_type=authorization_code。
- 授权服务器返回access_token和refresh_token。
- demo拿着access_token调用resource-server的受保护接口,返回200和用户数据。
顺利的话,这个链路30秒内可以走通。但第一次跑的时候第5步就报错了,排查了半天,最终定位到client_secret的编码方式。这个坑值得记下来。
6.2 踩坑一:client_secret的明文和编码
报错信息很模糊,只说invalid_grant。我用Postman直接带client_id和client_secret发请求测试,发现用Basic认证的编码可以成功,但用表单方式传client_secret就失败。翻源码才明白,Spring Authorization Server对机密客户端的认证默认要求client_secret放在Authorization头里用Basic方式传递,而且如果配置了PasswordEncoder,注册值必须经过加密,否则就是{noop}前缀明文存。
修复方案:RegisteredClient的clientSecret字段配置BCrypt加密值,clientAuthenticationMethod保持CLIENT_SECRET_BASIC。客户端请求时用Basic Base64(client_id:client_secret)即可认证通过。如果某些老客户端非要用表单方式传client_secret,需要在客户端认证配置里显式放开ClientAuthenticationMethod.CLIENT_SECRET_POST,但新项目没必要这么干。
6.3 踩坑二:redirect_uri严格匹配的陷阱
另一个让我头疼的问题是回调地址。注册客户端时写的是http://127.0.0.1:8082/login/callback,demo里配置的跳转地址是http://localhost:8082/login/callback。浏览器地址栏看起来完全一样,但SAS的UriMatcher是精确匹配的,两个值不一致直接报invalid_request。
这个坑对测试环境尤其恶心:localhost和127.0.0.1是不同的值,http和https也不同。所有接入方对接的时候,我会先让他们把回调地址发我一份,精确配到RegisteredClient里,再让他们在客户端配置里原样复制。接入文档里特意加粗强调:把注册的redirect_uri复制粘贴到对接方配置里,不要手敲。
6.4 踩坑三:JWT公钥刷新导致的鉴权失败
资源服务器用JWT模式时,授权服务器重启重新生成了RSA密钥对,资源服务器缓存的旧公钥就解析不了新签发的token,出现大批401。表面看是token无效,实质是公钥已经换了。
排查方法:打开授权服务器的jwks端点,对比资源服务器内存里的JWK集合。生产环境我做了两件事:一是给授权服务器配置固定的RSA密钥,把私钥放在配置文件里而不是每次启动重新生成,这样重启不影响已下发的token;二是把资源服务器JwkSetUriReactor的缓存时间从默认5分钟调到1分钟,换钥后能快速拾取。
spring: security: oauth2: resourceserver: jwt: jwk-set-uri: http://auth-server.internal:9000/oauth2/jwks jwk-set-cache-ttl: 1m6.5 踩坑四:LDAP特殊字符转义
最后这个坑很细,排查起来很有意思:某个用户手机号字段里有加号,用电话号码搜索用户时怎么都搜不出来。LDAP过滤条件类似(&(objectClass=person)(telephoneNumber=+8613901234567)),这个加号在LDAP filter里可能被当作通配符,不转义就匹配异常。补救方案是所有过滤条件拼接前统一用LdapUtils.escapeValue处理一遍特殊字符。排查这个问题时我直接写了一个Java测试类,用同样的规则在LDAP服务器上反复验证,最后定位到是LDAP filter的转义优先级问题,又一次验证了生产环境里的协议细节不能想当然。
String escapedName = LdapUtils.escapeValue(inputUsername); String filter = "(uid=" + escapedName + ")";6.6 排查链路的方法论
回头看这几个坑,规律很清晰:每次实现一套新的安全协议,不要凭经验猜测根因,要沿着协议每一步留出的验证点去确认。OAuth2.0每一步都有非常明确的检查手段:授权URL对不对看浏览器地址栏,code换不到token就用Postman裸调token端点做对比,token无效就看jwks公钥和JWT头,LDAP匹配不到人就拿同样的filter去ldapsearch跑一遍。用这种方式排查,大多数问题半小时内都能定位。
如果有一天你接手一个别人写的SpringBoot OAuth2.0项目,第一件事不是读代码,而是把上面六个检查点全部跑一遍,你会比读一天代码更快地理解整个系统的状态。真到了需要深入排查的时候,如果有必要逆向他人的打包产物,反编译工具配合class文件对照也是一种可行手段,不过大多数情况下把配置和密钥对齐,问题就解决了一大半。
就我个人而言,这套认证体系上线之后,最大的成就感不是说架构有多牛,而是新系统接入从原来的两三天变成了一小时。先把授权码模式从客户端角度完整走通一遍,再去动LDAP和其他模式,这个顺序会帮你少走很多弯路。