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

在Spring Security中整合OAuth2并保留自定义CustomUser对象

问题

我们正将Spring Security应用从用户名/密码认证迁移至OAuth2,已成功在安全链中添加OAuth2登录,但安全上下文的用户对象出现问题。

当前配置如下:

用户对象

public class CustomUser implements UserDetails {
    private Long id;
    private String username;
    private Set<CustomRole> roles;
    // 更多字段、getter、setter等
}
public class UserService implements UserDetailsService {
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        try {
            return getUserByLoginname(username);
        } catch (NotFoundException e) {
            throw new UsernameNotFoundException("");
        }
    }

    public CustomUser getUserByLoginname(String identifier) throws NotFoundException {
        // 从数据库加载用户
    }
}

我们在多处通过以下代码获取登录用户:

@GetMapping(value = "/XXX")
public void XXX() {
    CustomUser loggedInUser = (CustomUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
// ... 
}

现在我们希望整合OAuth2的同时保留CustomUser模型,仅用OAuth2完成认证,不从OAuth流程获取权限或角色,而是在自有数据库中维护用户角色。已在安全配置中加入oauth2Login:

@Bean
protected SecurityFilterChain configure(HttpSecurity http, ClientRegistrationRepository clientRegistrationRepository)
        throws Exception {
    http
        .cors(c -> corsConfigurationSource())
            .authorizeHttpRequests(requests -> requests
                .requestMatchers(AntPathRequestMatcher.antMatcher("/XXX"),
                        AntPathRequestMatcher.antMatcher("/XXXXX"))
                    .permitAll()

                .anyRequest().authenticated())

            .oauth2Login(o -> o.authorizationEndpoint(end -> end
                    .authorizationRequestResolver(new CustomAuthorizationRequestResolver(clientRegistrationRepository))))

            .logout(l -> l.logoutSuccessUrl("/logoutsite").permitAll())

            .sessionManagement(management -> management.maximumSessions(-1).sessionRegistry(sessionRegistry()))
            .headers(headers -> headers.referrerPolicy(policy -> policy.policy(ReferrerPolicy.NO_REFERRER)))
            .csrf(csrf -> csrf.disable());
    return http.build();
}

认证已成功,但安全上下文中的对象是DefaultOidcUser而非CustomUser。曾考虑在UsernamePasswordAuthenticationFilter后添加过滤器替换用户对象,但觉得不够优雅。尝试过OAuthUserService或OidcUserService等方法,仍得到OAuth相关对象。

核心需求:如何仅用OAuth2完成认证,将自定义CustomUser对象载入安全上下文,同时忽略OAuth的所有权限和授权信息?

解决方案

通过自定义OidcUserService(如果使用非OIDC类型的OAuth2提供商,可替换为OAuth2UserService),在OAuth2认证完成后替换安全上下文的用户对象为CustomUser,具体实现如下:

1. 自定义OidcUserService实现

创建自定义服务,在OAuth2用户信息获取完成后,调用自有UserService加载数据库中的CustomUser,并构建新的认证对象覆盖安全上下文:

@Service
public class CustomOidcUserService extends OidcUserService {

    private final UserService userService;

    public CustomOidcUserService(UserService userService) {
        this.userService = userService;
    }

    @Override
    public OidcUser loadUser(OidcUserRequest userRequest) throws OAuth2AuthenticationException {
        // 先获取OAuth2返回的用户信息
        OidcUser oidcUser = super.loadUser(userRequest);
        
        // 从OAuth2用户信息中提取唯一标识(比如email或sub字段,根据提供商配置调整)
        String userIdentifier = oidcUser.getEmail(); // 或oidcUser.getSubject()
        
        // 加载自有数据库中的CustomUser
        CustomUser customUser;
        try {
            customUser = userService.getUserByLoginname(userIdentifier);
        } catch (NotFoundException e) {
            // 可在此处理首次登录自动注册逻辑,根据业务需求调整
            throw new OAuth2AuthenticationException("用户不存在");
        }
        
        // 用CustomUser的角色生成权限,忽略OAuth返回的权限
        Collection<GrantedAuthority> authorities = customUser.getRoles()
                .stream()
                .map(role -> new SimpleGrantedAuthority("ROLE_" + role.getName()))
                .collect(Collectors.toList());
        
        // 构建新的认证对象并替换安全上下文
        Authentication authentication = new UsernamePasswordAuthenticationToken(
                customUser,
                null, // OAuth2认证无需凭证,设为null
                authorities
        );
        SecurityContextHolder.getContext().setAuthentication(authentication);
        
        return oidcUser;
    }
}

2. 在安全配置中绑定自定义服务

修改SecurityFilterChain配置,将自定义的OidcUserService绑定到oauth2Login的用户信息端点:

@Bean
protected SecurityFilterChain configure(HttpSecurity http, ClientRegistrationRepository clientRegistrationRepository, CustomOidcUserService customOidcUserService)
        throws Exception {
    http
        .cors(c -> corsConfigurationSource())
            .authorizeHttpRequests(requests -> requests
                .requestMatchers(AntPathRequestMatcher.antMatcher("/XXX"),
                        AntPathRequestMatcher.antMatcher("/XXXXX"))
                    .permitAll()
                .anyRequest().authenticated())
            .oauth2Login(o -> o
                    .authorizationEndpoint(end -> end
                            .authorizationRequestResolver(new CustomAuthorizationRequestResolver(clientRegistrationRepository)))
                    .userInfoEndpoint(userInfo -> userInfo
                            .oidcUserService(customOidcUserService)) // 绑定自定义服务
            )
            .logout(l -> l.logoutSuccessUrl("/logoutsite").permitAll())
            .sessionManagement(management -> management.maximumSessions(-1).sessionRegistry(sessionRegistry()))
            .headers(headers -> headers.referrerPolicy(policy -> policy.policy(ReferrerPolicy.NO_REFERRER)))
            .csrf(csrf -> csrf.disable());
    return http.build();
}

关键说明

  • 标识映射:确保从OAuth2返回的用户标识与自有数据库的用户登录名匹配,这是关联OAuth认证和自有用户的核心。
  • 权限控制:完全使用CustomUser中的角色生成权限,彻底忽略OAuth流程返回的任何权限信息。
  • 兼容性:原有业务中获取CustomUser的代码无需修改,安全上下文已替换为目标对象。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 20:54:53