我们正在将 Spring Security 应用程序从用户名/密码身份验证转换为 OAuth2。我们已成功将 OAuth2 登录添加到安全链,但我们在安全上下文中面临用户对象的问题。
这是我们当前的设置:
用户对象:
public class CustomUser implements UserDetails {
private Long id;
private String username;
private Set<CustomRole> roles;
// Many more fields, getters, setters, etc.
}
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 {
// Load User from database
}
}
然后在很多地方我们使用以下片段来检索登录的用户:
@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 之后添加一个过滤器,以用我们的自定义对象替换 SecurityContext 中的 User 对象。然而,这种方法感觉有点肮脏。
我想在 UsernamePasswordAuthenticationFilter 之后添加一个过滤器,用新的过滤器替换 SecurityContext 中的 Userobject。但这似乎有点肮脏。我觉得必须有更好的方法。但到目前为止我尝试过的所有方法(例如使用 OAuthUserService 或 OidcUserService)仍然会产生某种 OAuth 对象。
我的问题是:如何仅使用 OAuth 进行身份验证,然后仅将我的 CustomUser 对象加载到安全上下文中,同时忽略 OAuth 的任何授权和权限?
如果您阅读了 Spring Security 文档,这正是
GrantedAuthoritiesMapper
的用途。它记录在文档中here。
这是文档中的代码示例:
@Configuration
@EnableWebSecurity
public class OAuth2LoginSecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.oauth2Login(oauth2 -> oauth2
.userInfoEndpoint(userInfo -> userInfo
.userAuthoritiesMapper(this.userAuthoritiesMapper())
...
)
);
return http.build();
}
private GrantedAuthoritiesMapper userAuthoritiesMapper() {
return (authorities) -> {
Set<GrantedAuthority> mappedAuthorities = new HashSet<>();
authorities.forEach(authority -> {
if (OidcUserAuthority.class.isInstance(authority)) {
OidcUserAuthority oidcUserAuthority = (OidcUserAuthority)authority;
OidcIdToken idToken = oidcUserAuthority.getIdToken();
OidcUserInfo userInfo = oidcUserAuthority.getUserInfo();
// Map the claims found in idToken and/or userInfo
// to one or more GrantedAuthority's and add it to mappedAuthorities
} else if (OAuth2UserAuthority.class.isInstance(authority)) {
OAuth2UserAuthority oauth2UserAuthority = (OAuth2UserAuthority)authority;
Map<String, Object> userAttributes = oauth2UserAuthority.getAttributes();
// Map the attributes found in userAttributes
// to one or more GrantedAuthority's and add it to mappedAuthorities
}
});
return mappedAuthorities;
};
}
}
在文档中提供的代码示例中,他们手动从 JWT 等中提取权限,并返回带有新的 GrantedAuthorities 的地图,但在您的情况下,我相信您会调用数据库并获取所需的权限(角色)和与您需要的权威机构一起构建地图并将其返回。