Spring授权服务器:联邦登录新用户先注册再跳转至客户端的实现
实现联邦登录首次登录跳转注册页方案
核心思路
拦截联邦登录成功后的流程,判断用户是否为首次登录(本地数据库无对应关联记录):如果是新用户,先保存当前授权请求上下文,跳转到注册页面;待用户完成注册并关联联邦身份后,恢复授权上下文,继续完成授权流程生成授权码并跳转至客户端的redirect uri。
具体实现步骤
1. 自定义AuthenticationSuccessHandler
该处理器负责判断用户是否为新联邦用户,保存授权请求并跳转注册页;老用户则走默认授权流程。
@Component public class FederatedAuthenticationSuccessHandler implements AuthenticationSuccessHandler { private final UserRepository userRepository; private final RequestCache requestCache = new HttpSessionRequestCache(); private final SavedRequestAwareAuthenticationSuccessHandler defaultHandler = new SavedRequestAwareAuthenticationSuccessHandler(); public FederatedAuthenticationSuccessHandler(UserRepository userRepository) { this.userRepository = userRepository; } @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { if (authentication instanceof OAuth2AuthenticationToken) { OAuth2AuthenticationToken oauthToken = (OAuth2AuthenticationToken) authentication; String providerId = oauthToken.getAuthorizedClientRegistrationId(); String externalUserId = oauthToken.getPrincipal().getName(); // 检查本地是否已关联该联邦用户 Optional<User> existingUser = userRepository.findByProviderAndExternalId(providerId, externalUserId); if (existingUser.isEmpty()) { // 保存当前授权请求到会话缓存 requestCache.saveRequest(request, response); // 跳转注册页,携带联邦身份标识用于后续关联 response.sendRedirect(String.format("/register?provider=%s&externalId=%s", providerId, externalUserId)); return; } } // 老用户或非联邦登录,执行默认成功流程 defaultHandler.onAuthenticationSuccess(request, response, authentication); } }
2. 配置Spring Security替换联邦登录的SuccessHandler
在授权服务器的SecurityFilterChain配置中,将OAuth2Login的成功处理器替换为自定义实现:
@Bean public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http, FederatedAuthenticationSuccessHandler successHandler) throws Exception { OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http); http.getConfigurer(OAuth2AuthorizationServerConfigurer.class) .oidc(Customizer.withDefaults()); http .oauth2Login(oauth2 -> oauth2 .successHandler(successHandler) ) .formLogin(Customizer.withDefaults()); return http.build(); }
3. 实现注册页面与注册完成后的流程
注册页面收集用户额外信息(如昵称、手机号),完成后恢复授权请求并继续流程:
注册控制器示例
@Controller public class RegisterController { private final UserRepository userRepository; private final RequestCache requestCache = new HttpSessionRequestCache(); private final SavedRequestAwareAuthenticationSuccessHandler successHandler = new SavedRequestAwareAuthenticationSuccessHandler(); public RegisterController(UserRepository userRepository) { this.userRepository = userRepository; } @GetMapping("/register") public String showRegisterForm(@RequestParam String provider, @RequestParam String externalId, Model model) { model.addAttribute("provider", provider); model.addAttribute("externalId", externalId); return "register"; } @PostMapping("/register") public void completeRegistration(HttpServletRequest request, HttpServletResponse response, @RequestParam String provider, @RequestParam String externalId, @RequestParam String username, @RequestParam String phone) throws IOException, ServletException { // 创建新用户并关联联邦身份信息 User newUser = new User(); newUser.setUsername(username); newUser.setPhone(phone); newUser.setProvider(provider); newUser.setExternalId(externalId); userRepository.save(newUser); // 恢复之前保存的授权请求 SavedRequest savedRequest = requestCache.getRequest(request, response); if (savedRequest != null) { requestCache.removeRequest(request, response); // 继续执行授权流程,生成授权码跳转客户端 successHandler.onAuthenticationSuccess(request, response, SecurityContextHolder.getContext().getAuthentication()); } else { response.sendRedirect("/"); } } }
4. 关键注意事项
- 授权请求缓存:必须通过
HttpSessionRequestCache保存登录前的授权请求(包含客户端redirect uri、scope等核心信息),否则注册完成后无法恢复原授权流程。 - 用户身份关联:注册时要确保联邦身份的provider和externalId与新用户唯一绑定,避免后续登录重复触发注册流程。
- Security上下文:注册完成后,当前用户的Authentication已存在于SecurityContext中,直接调用
SavedRequestAwareAuthenticationSuccessHandler即可自动完成授权码生成与跳转。
内容的提问来源于stack exchange,提问作者httPants
相关产品推荐
相关产品推荐

