You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于Spring Security 6实现Google社交登录后生成自定义Access Token

问题

我正在用Spring Security 6实现Spring授权服务器,目前已完成本地数据库注册用户的简单认证,可正常生成Access Token。

需要集成Google作为认证提供商实现社交登录,但要求Access Token仍由我的授权服务器生成:仅让Google负责登录验证,授权服务器收到Google的成功响应后,用用户名查询本地数据库获取权限等用户数据,进而生成Token返回给客户端。

未找到可参考的实现示例,恳请提供帮助。

注:已完成Google账号配置,当前可与授权服务器正常通信。

我的安全过滤器链配置如下:

@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
    OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);

    http.getConfigurer(OAuth2AuthorizationServerConfigurer.class)
            .oidc(oidc -> oidc
                    .userInfoEndpoint(userInfo -> userInfo.userInfoMapper(userInfoMapper()))
                    .logoutEndpoint(withDefaults()));

    http.exceptionHandling(exceptions -> exceptions
                    .authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint(FORM_LOGIN_URL)))
            .oauth2ResourceServer(resourceServer -> resourceServer.jwt(withDefaults()));

    return http.build();
}

@Bean
@Order(2)
public SecurityFilterChain oauth2SecurityFilterChain(HttpSecurity http) throws Exception {
    http
            .oauth2ResourceServer(oauth2 -> oauth2.jwt(withDefaults()))
            .formLogin(formLogin -> formLogin.loginPage(FORM_LOGIN_URL).permitAll())
            .oauth2Login(oauth2 -> oauth2.loginPage(FORM_LOGIN_URL).permitAll())
            .logout(logout -> logout
                    .logoutUrl(LOGOUT_ENDPOINT)
                    .logoutSuccessUrl(FORM_LOGIN_URL)
                    .deleteCookies("JSESSIONID", "remember-me")
                    .invalidateHttpSession(true)
                    .clearAuthentication(true));

    return http.build();
}
解决方案

1. 自定义OAuth2登录成功处理器

拦截Google登录成功后的请求,替换默认跳转逻辑,改为查询本地用户并触发授权服务器生成Token:

@Component
public class OAuth2LoginSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {

    private final OAuth2AuthorizationService authorizationService;
    private final OAuth2TokenGenerator<? extends OAuth2Token> tokenGenerator;
    private final RegisteredClientRepository registeredClientRepository;
    private final UserDetailsService userDetailsService;

    public OAuth2LoginSuccessHandler(OAuth2AuthorizationService authorizationService,
                                     OAuth2TokenGenerator<? extends OAuth2Token> tokenGenerator,
                                     RegisteredClientRepository registeredClientRepository,
                                     UserDetailsService userDetailsService) {
        this.authorizationService = authorizationService;
        this.tokenGenerator = tokenGenerator;
        this.registeredClientRepository = registeredClientRepository;
        this.userDetailsService = userDetailsService;
    }

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        // 从Google认证信息中提取用户标识(示例用email,也可使用sub字段)
        OAuth2AuthenticationToken oauth2Token = (OAuth2AuthenticationToken) authentication;
        String userIdentifier = oauth2Token.getPrincipal().getAttribute("email");

        // 查询本地数据库获取用户权限等信息
        UserDetails localUser = userDetailsService.loadUserByUsername(userIdentifier);
        // 构建包含本地权限的认证对象
        UsernamePasswordAuthenticationToken localAuth = new UsernamePasswordAuthenticationToken(
                localUser, null, localUser.getAuthorities());

        // 解析请求中的客户端ID(实际场景需根据授权流程调整)
        String clientId = request.getParameter("client_id");
        RegisteredClient registeredClient = registeredClientRepository.findByClientId(clientId);

        // 生成Access Token
        OAuth2TokenContext tokenContext = OAuth2TokenContext.builder()
                .registeredClient(registeredClient)
                .principal(localAuth)
                .tokenType(OAuth2TokenType.ACCESS_TOKEN)
                .build();
        OAuth2Token accessToken = tokenGenerator.generate(tokenContext);

        // 将Token以JSON格式返回给客户端
        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
        new ObjectMapper().writeValue(response.getWriter(), 
                Collections.singletonMap("access_token", accessToken.getTokenValue()));
    }
}

2. 绑定自定义处理器到OAuth2Login流程

修改oauth2SecurityFilterChain配置,指定自定义的登录成功处理器:

@Bean
@Order(2)
public SecurityFilterChain oauth2SecurityFilterChain(HttpSecurity http, OAuth2LoginSuccessHandler successHandler) throws Exception {
    http
            .oauth2ResourceServer(oauth2 -> oauth2.jwt(withDefaults()))
            .formLogin(formLogin -> formLogin.loginPage(FORM_LOGIN_URL).permitAll())
            .oauth2Login(oauth2 -> oauth2
                    .loginPage(FORM_LOGIN_URL)
                    .permitAll()
                    .successHandler(successHandler)) // 绑定自定义处理器
            .logout(logout -> logout
                    .logoutUrl(LOGOUT_ENDPOINT)
                    .logoutSuccessUrl(FORM_LOGIN_URL)
                    .deleteCookies("JSESSIONID", "remember-me")
                    .invalidateHttpSession(true)
                    .clearAuthentication(true));

    return http.build();
}

3. 关联本地用户与Google认证标识

在UserDetailsService实现中处理用户关联逻辑:

  • 若通过Google标识查询到本地用户,直接返回包含本地权限的UserDetails对象
  • 若未查询到用户,可根据Google返回的信息自动创建本地用户(初始化默认权限),确保后续能正常生成包含权限的Token

4. 确保必要Bean存在

确认上下文已配置RegisteredClientRepository、OAuth2AuthorizationService、OAuth2TokenGenerator等核心Bean,Spring授权服务器默认配置已包含这些组件,如有自定义需求可自行实现扩展。

内容的提问来源于stack exchange,提问作者Andeson Morais

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 16:04:53