1. OAuth2.0核心架构与SpringSecurity角色定位
OAuth2.0协议就像小区门禁系统:业主(资源所有者)通过物业(授权服务器)给访客(客户端)发放临时门禁卡(Token),访客凭卡在指定区域(资源服务器)活动。Spring Security在这个体系中扮演着智能门禁管理员的角色,负责整套流程的安全管控。
典型交互流程:
- 客户端携带业主授权凭证到授权服务器
- 授权服务器验证通过后颁发Token
- 客户端持Token访问资源服务器
- 资源服务器校验Token有效性后返回资源
在Spring生态中,这两个服务器通常表现为:
- 授权服务器:
@EnableAuthorizationServer - 资源服务器:
@EnableResourceServer
2. 授权服务器深度配置实战
2.1 基础环境搭建
首先引入关键依赖(Spring Boot 2.7.x示例):
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.security.oauth.boot</groupId> <artifactId>spring-security-oauth2-autoconfigure</artifactId> <version>2.7.5</version> </dependency>2.2 客户端配置的两种模式
内存模式(开发常用):
@Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { clients.inMemory() .withClient("payment-app") .secret("{bcrypt}$2a$10$NlqJ2HvY.5Z7D5K7vR5XBez8vZ7JQY9W8nLm6X5tJkZr1VYH9dWbK") .authorizedGrantTypes("authorization_code", "refresh_token") .scopes("read", "write") .redirectUris("https://callback.example.com") .accessTokenValiditySeconds(3600); }JDBC模式(生产推荐):
@Bean public JdbcClientDetailsService clientDetailsService(DataSource dataSource) { return new JdbcClientDetailsService(dataSource); } @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { clients.withClientDetails(clientDetailsService); }需要提前建好oauth_client_details表,字段包含client_id、client_secret等关键信息。
2.3 令牌管理策略对比
| 存储类型 | 实现类 | 适用场景 | 优缺点 |
|---|---|---|---|
| 内存存储 | InMemoryTokenStore | 测试环境 | 简单但重启丢失 |
| JDBC存储 | JdbcTokenStore | 生产单机环境 | 持久化但性能一般 |
| JWT令牌 | JwtTokenStore | 分布式系统 | 无状态但无法主动失效 |
| Redis存储 | RedisTokenStore | 高并发场景 | 高性能需维护Redis |
JWT配置示例:
@Bean public JwtAccessTokenConverter accessTokenConverter() { JwtAccessTokenConverter converter = new JwtAccessTokenConverter(); converter.setSigningKey("your-secret-key"); // 生产环境建议使用RSA return converter; } @Bean public TokenStore tokenStore() { return new JwtTokenStore(accessTokenConverter()); }3. 端点安全精细化控制
3.1 默认端点清单
| 端点路径 | 作用 | 默认访问规则 |
|---|---|---|
| /oauth/authorize | 授权端点 | 需认证 |
| /oauth/token | 令牌端点 | 需客户端认证 |
| /oauth/check_token | 令牌校验 | 需认证 |
| /oauth/token_key | 提供公钥 | 公开或认证 |
3.2 安全配置实战
@Override public void configure(AuthorizationServerSecurityConfigurer security) { security .tokenKeyAccess("isAuthenticated()") // 公钥端点需认证 .checkTokenAccess("permitAll()") // 校验端点开放 .allowFormAuthenticationForClients(); // 允许表单认证 }生产环境建议:
- 对/oauth/token启用HTTPS
- 限制check_token访问频次
- 使用IP白名单保护管理端点
4. 资源服务器关键配置
4.1 基础配置模板
@Configuration @EnableResourceServer public class ResourceConfig extends ResourceServerConfigurerAdapter { @Override public void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers("/api/public/**").permitAll() .antMatchers("/api/admin/**").hasRole("ADMIN") .anyRequest().authenticated(); } @Bean public ResourceServerTokenServices tokenServices() { RemoteTokenServices services = new RemoteTokenServices(); services.setCheckTokenEndpointUrl("http://auth-server/oauth/check_token"); services.setClientId("resource-server"); services.setClientSecret("secret"); return services; } }4.2 JWT验证方案
# application.yml spring: security: oauth2: resourceserver: jwt: issuer-uri: http://auth-server jwk-set-uri: http://auth-server/oauth2/jwks对应的安全配置:
@Bean SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http.oauth2ResourceServer(oauth2 -> oauth2 .jwt(jwt -> jwt .decoder(jwtDecoder()) ) ); return http.build(); } @Bean JwtDecoder jwtDecoder() { return NimbusJwtDecoder.withJwkSetUri(jwkSetUri).build(); }5. 实战中的避坑指南
跨服务Token验证问题:
- 方案1:共享TokenStore数据库
- 方案2:使用JWT+公钥验证
- 方案3:配置RemoteTokenServices
常见异常处理:
@ControllerAdvice public class OAuthExceptionHandler { @ExceptionHandler(InvalidTokenException.class) public ResponseEntity<String> handleInvalidToken(InvalidTokenException e) { return ResponseEntity.status(401).body("Token验证失败: " + e.getMessage()); } }性能优化建议:
- 对JWT验证结果进行缓存
- 使用Redis存储令牌时设置合理TTL
- 启用HTTP/2减少握手开销
记得在网关层统一处理跨域和预检请求,避免OPTIONS请求被拦截。实际项目中我曾遇到前端拿不到CORS头的问题,最后发现是资源服务器配置遗漏了OPTIONS方法的放行。