基于Spring拆分OAuth2授权服务器与资源服务器的改造方法问询
拆分Spring OAuth2授权服务器与资源服务器的实现指南
我之前也做过类似的拆分改造,其实核心就是把原来单应用里的授权逻辑和资源保护逻辑彻底分开成两个独立Spring Boot项目,下面一步步给你讲具体怎么改:
第一步:搭建独立的授权服务器(Auth Server)
这个服务器专门负责用户认证、存储用户/客户端信息、颁发Token。
1. 项目依赖与初始化
新建一个Spring Boot项目,引入这些核心依赖:
spring-boot-starter-security:基础安全框架spring-boot-starter-oauth2-authorization-server(Spring Security 5.7+推荐)或旧版spring-security-oauth2-autoconfigurespring-boot-starter-web:提供必要的端点访问spring-boot-starter-data-jpa+ 数据库驱动(比如MySQL):存储用户、客户端、Token信息spring-boot-starter-security里的PasswordEncoder相关依赖
2. 核心配置改造
(1)用户认证配置:从数据库读取用户信息
原来教程可能用内存用户,现在改成从数据库查询,实现UserDetailsService:
@Service public class CustomUserDetailsService implements UserDetailsService { @Autowired private UserRepository userRepository; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { User user = userRepository.findByUsername(username) .orElseThrow(() -> new UsernameNotFoundException("用户不存在:" + username)); return User.withUsername(user.getUsername()) .password(user.getPassword()) .authorities(user.getRoles().split(",")) .build(); } }
同时配置密码编码器:
@Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); }
(2)授权服务器核心配置
继承AuthorizationServerConfigurerAdapter(旧版)或者用新的AuthorizationServerSettings(Spring Security 6+),这里给你旧版兼容的示例:
@Configuration @EnableAuthorizationServer public class AuthServerConfig extends AuthorizationServerConfigurerAdapter { @Autowired private AuthenticationManager authenticationManager; @Autowired private UserDetailsService userDetailsService; @Autowired private PasswordEncoder passwordEncoder; @Autowired private DataSource dataSource; // 客户端信息存储(用数据库存生产环境更安全) @Bean public ClientDetailsService clientDetailsService() { return new JdbcClientDetailsService(dataSource); } // Token存储(用数据库或Redis,不推荐内存) @Bean public TokenStore tokenStore() { return new JdbcTokenStore(dataSource); } @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { endpoints .authenticationManager(authenticationManager) .userDetailsService(userDetailsService) .tokenStore(tokenStore()); } @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { // 测试阶段可以用内存客户端,生产换成数据库 clients.inMemory() .withClient("my-client") .secret(passwordEncoder.encode("my-client-secret")) .authorizedGrantTypes("password", "refresh_token") .scopes("read", "write") .accessTokenValiditySeconds(3600) .refreshTokenValiditySeconds(86400); } @Override public void configure(AuthorizationServerSecurityConfigurer security) throws Exception { security .tokenKeyAccess("permitAll()") // 允许获取公钥(JWT场景用) .checkTokenAccess("isAuthenticated()") // 资源服务器验证Token需要认证 .allowFormAuthenticationForClients(); // 允许表单方式提交客户端信息 } }
(3)WebSecurity配置
需要暴露AuthenticationManager给授权服务器使用:
@Configuration @EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private UserDetailsService userDetailsService; @Autowired private PasswordEncoder passwordEncoder; @Bean @Override public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder); } @Override protected void configure(HttpSecurity http) throws Exception { http .csrf().disable() .authorizeRequests() .antMatchers("/oauth/**", "/login/**").permitAll() .anyRequest().authenticated() .and() .formLogin().permitAll(); } }
第二步:搭建独立的资源服务器(Resource Server)
这个服务器只负责保护API资源,验证Token的合法性。
1. 项目依赖
新建另一个Spring Boot项目,引入:
spring-boot-starter-securityspring-boot-starter-oauth2-resource-server(或旧版spring-security-oauth2)spring-boot-starter-web:提供API接口
2. 核心配置改造
(1)Token验证配置
分两种场景:
场景A:用JWT Token(推荐生产环境)
直接通过授权服务器的JWKS端点或公钥验证Token:
@Configuration @EnableWebSecurity public class ResourceServerConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .csrf().disable() .authorizeRequests() .antMatchers("/api/**").authenticated() .and() .oauth2ResourceServer() .jwt() .decoder(jwtDecoder()); } @Bean public JwtDecoder jwtDecoder() { // 替换成你的授权服务器地址 return JwtDecoders.fromIssuerLocation("http://localhost:8080"); } }
场景B:用普通Token(内存/数据库存储)
通过授权服务器的/oauth/check_token端点验证:
@Configuration @EnableWebSecurity public class ResourceServerConfig extends WebSecurityConfigurerAdapter { @Bean public ResourceServerTokenServices tokenServices() { RemoteTokenServices tokenServices = new RemoteTokenServices(); // 授权服务器的Token验证端点 tokenServices.setCheckTokenEndpointUrl("http://localhost:8080/oauth/check_token"); // 和授权服务器配置的客户端信息一致 tokenServices.setClientId("my-client"); tokenServices.setClientSecret("my-client-secret"); return tokenServices; } @Override protected void configure(HttpSecurity http) throws Exception { http .csrf().disable() .authorizeRequests() .antMatchers("/api/**").authenticated() .and() .resourceServer() .tokenServices(tokenServices()); } }
(2)添加测试API
比如写一个简单的受保护接口:
@RestController @RequestMapping("/api") public class ResourceController { @GetMapping("/user") public Principal getCurrentUser(Principal principal) { return principal; } }
第三步:关键注意事项
- 端口区分:授权服务器和资源服务器要用不同端口,比如授权服务器用8080,资源服务器用8081
- 数据库表结构:如果用JDBC存储Token和客户端信息,Spring Security OAuth2有默认的表结构(比如
oauth_client_details、oauth_access_token),可以直接生成 - 跨域配置:两个独立应用需要配置CORS,允许对方域名访问,比如在资源服务器添加:
@Configuration public class CorsConfig { @Bean public CorsFilter corsFilter() { UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); CorsConfiguration config = new CorsConfiguration(); config.setAllowCredentials(true); config.addAllowedOrigin("http://localhost:8080"); // 授权服务器地址 config.addAllowedHeader("*"); config.addAllowedMethod("*"); source.registerCorsConfiguration("/**", config); return new CorsFilter(source); } }
- 测试流程:先启动授权服务器,再启动资源服务器 → 用POST请求
http://localhost:8080/oauth/token,携带客户端信息和用户账号密码获取Token → 用Token请求资源服务器的http://localhost:8081/api/user接口,验证是否能正常访问
内容的提问来源于stack exchange,提问作者Magno C




