Spring Security 如何提取OIDC的role claims转换为Spring Authorities?
根本原因
你配置的JwtAuthenticationConverter系列组件属于Spring Security OAuth2资源服务器模块,仅会在处理携带Authorization: Bearer {JWT}的接口请求时触发。你走oauth2Login的OIDC授权码登录流程时,用户身份会被封装为DefaultOidcUser,这套登录流程和资源服务器的JWT解析逻辑完全独立,所以你写的转换器永远不会被调用。
解决方案
你需要自定义OidcUserService,在加载OIDC用户信息的阶段,从Azure AD返回的claims中提取自定义角色,拼接到Spring Security的权限列表中,具体实现如下:
- 自定义OidcUserService实现类
import org.springframework.security.oauth2.core.oidc.OidcUser; import org.springframework.security.oauth2.core.oidc.user.OidcUserService; import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserRequest; import org.springframework.security.oauth2.core.OAuth2AuthenticationException; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser; import java.util.Collections; import java.util.List; import java.util.stream.Collectors; import java.util.stream.Stream; public class CustomOidcUserService extends OidcUserService { @Override public OidcUser loadUser(OidcUserRequest userRequest) throws OAuth2AuthenticationException { OidcUser originalOidcUser = super.loadUser(userRequest); // 从Azure AD返回的claims中提取自定义角色 List<String> roles = originalOidcUser.getClaimAsStringList("roles"); roles = roles == null ? Collections.emptyList() : roles; // 拼接原有权限和自定义角色,可根据需求调整角色前缀规则 List<GrantedAuthority> mergedAuthorities = Stream.concat( originalOidcUser.getAuthorities().stream(), roles.stream().map(role -> new SimpleGrantedAuthority("ROLE_" + role)) ).collect(Collectors.toList()); // 返回携带完整权限的OidcUser return new DefaultOidcUser(mergedAuthorities, originalOidcUser.getIdToken(), originalOidcUser.getUserInfo()); } }
- 修改安全配置,将自定义实现注册到oauth2Login流程中
@EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true) @Import(SecurityProblemSupport.class) public class SecurityConfiguration extends WebSecurityConfigurerAdapter { @Override public void configure(HttpSecurity http) throws Exception { // @formatter:off http .csrf() // 你原有和oauth/oidc无关的配置保留 .and() .oauth2Login() // 新增自定义OidcUserService配置 .userInfoEndpoint() .oidcUserService(customOidcUserService()) .and() .and() .oauth2ResourceServer() .jwt() .jwtAuthenticationConverter(jwtAuthenticationConverter()) .and() .and() .oauth2Client(); // @formatter:on } private CustomOidcUserService customOidcUserService() { return new CustomOidcUserService(); } // 你原有jwtAuthenticationConverter方法保留,用于资源服务器场景 private JwtAuthenticationConverter jwtAuthenticationConverter() { JwtGrantedAuthoritiesConverter jwtGrantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter(); jwtGrantedAuthoritiesConverter.setAuthoritiesClaimName("roles"); JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter(); jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(jwtGrantedAuthoritiesConverter); return jwtAuthenticationConverter; } }
补充说明
如果你同时需要支持页面登录、第三方客户端持JWT调用接口两种场景,保留原有资源服务器JWT转换器配置即可,注意两边的角色前缀、映射规则保持一致。
内容的提问来源于stack exchange,提问作者Bat
相关产品推荐
相关产品推荐

