Spring Boot 3.2.0替代废弃密码凭证模式生成用户AccessToken方案咨询
替代OAuth2密码凭证流的解决方案(Spring Boot 3.2.0)
由于OAuth2的密码凭证流(Password Credentials Grant)已被废弃,结合你的多前端应用认证需求,推荐使用**授权码流(Authorization Code Flow)+ PKCE(Proof Key for Code Exchange)**作为替代方案——这是OAuth2.1规范推荐的安全模式,无需依赖OpenID Connect即可实现前端应用的用户认证,同时天然支持多客户端隔离。
一、核心方案说明
- 安全优势:授权码流+PKCE避免了前端直接暴露用户密码,防止授权码劫持风险,适配单页应用、原生应用等各类前端场景
- 多客户端支持:为每个前端分配独立的
client-id(可配置client-secret,纯前端应用建议省略secret只使用PKCE),授权服务器通过客户端标识区分不同应用的权限范围与认证规则
二、具体实现步骤
1. 依赖配置(Maven)
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-oauth2-authorization-server</artifactId> </dependency>
2. 授权服务器核心配置
创建Spring Security配置类,完成客户端注册、用户认证、token规则定义:
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.oauth2.core.AuthorizationGrantType; import org.springframework.security.oauth2.core.ClientAuthenticationMethod; import org.springframework.security.oauth2.server.authorization.client.InMemoryRegisteredClientRepository; import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration; import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint; import java.util.UUID; @Configuration @EnableWebSecurity public class OAuth2AuthorizationServerConfig { // 授权服务器安全规则 @Bean public SecurityFilterChain authorizationServerFilterChain(HttpSecurity http) throws Exception { OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http); // 禁用OpenID Connect(无需依赖OIDC) http.getConfigurer(OAuth2AuthorizationServerConfigurer.class).oidc(oidc -> oidc.disable()); // 未认证时跳转登录页 http.exceptionHandling(exceptions -> exceptions .authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/login"))); return http.build(); } // 通用登录与资源访问规则 @Bean public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception { http.authorizeHttpRequests(authorize -> authorize .anyRequest().authenticated()) .formLogin(form -> form.loginPage("/login").permitAll()); return http.build(); } // 用户信息服务(实际项目建议从数据库读取) @Bean public UserDetailsService userDetailsService() { UserDetails userA = User.withUsername("user_a") .password(passwordEncoder().encode("pass_a123")) .roles("APP_USER") .build(); UserDetails userB = User.withUsername("user_b") .password(passwordEncoder().encode("pass_b123")) .roles("APP_USER") .build(); return username -> switch (username) { case "user_a" -> userA; case "user_b" -> userB; default -> throw new RuntimeException("用户不存在"); }; } // 密码加密器 @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } // 多客户端注册(每个前端对应一个客户端) @Bean public RegisteredClientRepository registeredClientRepository() { // 前端应用1配置 RegisteredClient shopFront = RegisteredClient.withId(UUID.randomUUID().toString()) .clientId("shop_front") .clientSecret(passwordEncoder().encode("shop_secret_001")) .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC) .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN) .redirectUri("http://localhost:3000/login/oauth2/code/shop-front") .scope("product:read") .scope("order:write") .clientSettings(settings -> settings.requireAuthorizationConsent(false)) .build(); // 前端应用2配置 RegisteredClient adminFront = RegisteredClient.withId(UUID.randomUUID().toString()) .clientId("admin_front") .clientSecret(passwordEncoder().encode("admin_secret_002")) .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC) .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN) .redirectUri("http://localhost:3001/login/oauth2/code/admin-front") .scope("user:manage") .scope("system:config") .clientSettings(settings -> settings.requireAuthorizationConsent(false)) .build(); return new InMemoryRegisteredClientRepository(shopFront, adminFront); } }
3. 前端对接流程(匹配指定OAuth认证流程)
前端发起授权请求:
GET /oauth2/authorize?response_type=code&client_id=shop_front&redirect_uri=http://localhost:3000/login/oauth2/code/shop-front&scope=product:read&code_challenge=xxx&code_challenge_method=S256其中
code_challenge由前端通过code_verifier生成并存储,用于后续token请求校验用户认证:用户在授权服务器的登录页输入用户名密码,完成身份验证
获取授权码:授权服务器重定向至前端配置的
redirect_uri,携带授权码code兑换Access Token:前端发起POST请求获取token:
POST /oauth2/token Content-Type: application/x-www-form-urlencoded grant_type=authorization_code &code=xxx(步骤3返回的授权码) &redirect_uri=http://localhost:3000/login/oauth2/code/shop-front &client_id=shop_front &client_secret=shop_secret_001 &code_verifier=xxx(前端存储的原始校验值)调用API:前端使用返回的
access_token在请求头中携带Authorization: Bearer {token},访问受保护的后端API
4. 资源服务器配置(可选,用于保护API)
如果需要单独配置资源服务器验证token,可添加如下类:
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.web.SecurityFilterChain; @Configuration @EnableWebSecurity public class ResourceServerConfig { @Bean public SecurityFilterChain resourceServerFilterChain(HttpSecurity http) throws Exception { http.authorizeHttpRequests(authorize -> authorize .requestMatchers("/api/products/**").hasAuthority("SCOPE_product:read") .requestMatchers("/api/orders/**").hasAuthority("SCOPE_order:write") .anyRequest().authenticated()) .oauth2ResourceServer(oauth2 -> oauth2.jwt(jwt -> jwt.jwtAuthenticationConverter(new CustomJwtConverter()))); return http.build(); } }
三、关键注意事项
- 多客户端隔离:每个前端应用的
client-id、redirect_uri、权限范围需独立配置,避免跨应用权限泄露 - PKCE强制使用:对于无后端的纯前端应用(如Vue/React单页应用),建议省略
client-secret,仅通过PKCE保障安全 - 生产环境优化:客户端信息、用户信息建议从数据库读取,而非内存存储;同时配置HTTPS保障传输安全
内容的提问来源于stack exchange,提问作者SonaliKoripally
相关产品推荐
相关产品推荐

