现有表单登录应用接入OAuth2时如何设置自定义User为Principal
解决方案
1. 改造自定义User类实现OidcUser接口
Spring Security OIDC登录流程要求OAuth2UserService返回OidcUser类型的Principal,你原来的User类仅实现了UserDetails,不满足返回类型要求,需要新增接口实现和对应字段:
public class User implements UserDetails, OidcUser { // 原有字段保持不变 private Long id; private String email; private String password; private String googleAccessToken; // 新增OIDC所需字段 private List<GrantedAuthority> authorities; private Map<String, Object> attributes; private OidcIdToken idToken; private OidcUserInfo userInfo; // 原有getter、setter和UserDetails接口实现保持不变 // 实现OidcUser接口方法 @Override public Map<String, Object> getClaims() { return this.attributes; } @Override public OidcIdToken getIdToken() { return this.idToken; } @Override public OidcUserInfo getUserInfo() { return this.userInfo; } @Override public Map<String, Object> getAttributes() { return this.attributes; } @Override public Collection<? extends GrantedAuthority> getAuthorities() { return this.authorities; } @Override public String getName() { // 返回用户唯一标识,这里用邮箱即可 return this.email; } }
2. 修改OAuth2UserService返回自定义User实例
调整loadUser逻辑,优先匹配已有用户,新用户自动创建,组装完所有属性后直接返回自定义User对象,不需要再返回DefaultOidcUser:
@Service @RequiredArgsConstructor public class OAuth2UserDetailsService implements OAuth2UserService<OidcUserRequest, OidcUser> { private final UsersRepository usersRepository; @Override public OidcUser loadUser(OidcUserRequest userRequest) throws OAuth2AuthenticationException { final OidcUserService delegate = new OidcUserService(); OidcUser oidcUser = delegate.loadUser(userRequest); String email = oidcUser.getEmail(); String accessTokenValue = userRequest.getAccessToken().getTokenValue(); User user; switch (userRequest.getClientRegistration().getClientName()) { case "google": // 优先按邮箱匹配已有账号,兼容老用户绑定谷歌登录 user = usersRepository.findOneByEmail(email) .orElseGet(() -> { // 新用户自动创建账号 User newUser = new User(); newUser.setEmail(email); // 密码设置为随机值,禁止OAuth2用户用空密码走表单登录 newUser.setPassword(UUID.randomUUID().toString()); return newUser; }); // 更新用户的谷歌accessToken user.setGoogleAccessToken(accessTokenValue); // 填充OIDC相关属性 user.setAttributes(oidcUser.getAttributes()); user.setIdToken(oidcUser.getIdToken()); user.setUserInfo(oidcUser.getUserInfo()); // 合并权限:可按需拼接用户原有角色权限和OIDC返回的权限 List<GrantedAuthority> combinedAuthorities = Stream.concat( oidcUser.getAuthorities().stream(), // 替换为你自己的用户角色查询逻辑 AuthorityUtils.createAuthorityList("ROLE_USER").stream() ).distinct().collect(Collectors.toList()); user.setAuthorities(combinedAuthorities); // 保存用户数据到数据库 usersRepository.save(user); break; default: throw new OAuth2AuthenticationException(new OAuth2Error("unsupported_client")); } // 直接返回自定义User实例作为Principal return user; } }
3. 适配Spring Security配置
在Security配置类中注册自定义的OAuth2UserService,和原有表单登录逻辑兼容:
@Bean public SecurityFilterChain securityFilterChain(HttpSecurity http, OAuth2UserDetailsService oAuth2UserDetailsService) throws Exception { http // 原有表单登录配置保持不变 .formLogin(form -> form .loginPage("/login") .defaultSuccessUrl("/index") .permitAll() ) // 新增OAuth2登录配置,绑定自定义UserService .oauth2Login(oauth2 -> oauth2 .loginPage("/login") .userInfoEndpoint(userInfo -> userInfo .oidcUserService(oAuth2UserDetailsService) ) .defaultSuccessUrl("/index") .permitAll() ) // 其他权限校验规则保持不变 .authorizeHttpRequests(auth -> auth .anyRequest().authenticated() ); return http.build(); }
效果验证
改造完成后,无需修改原有控制器逻辑,不管用户走表单登录还是谷歌OAuth2登录,都可以通过@AuthenticationPrincipal User user直接拿到自定义的User实例。
内容的提问来源于stack exchange,提问作者Joaquín L. Robles
相关产品推荐
相关产品推荐

