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

Spring Security5 OAuth2资源服务器如何基于JWT加载数据库自定义用户实体

结论

可以实现将JWT转换为自定义域User实例,Spring Security 5 OAuth2 Resource Server场景下,官方推荐的扩展点为JwtAuthenticationConverter,而非常规用户名密码认证流程中使用的UserDetailsService——JWT自包含认证的默认流程不会主动触发UserDetailsService的调用,硬编码集成反而会增加冗余逻辑。

具体实现方案

1. 核心原理

OAuth2 Resource Server的默认流程会先完成JWT的签名、有效期校验,校验通过后调用JwtAuthenticationConverter将JWT转换为JwtAuthenticationToken实例,我们只需要重写该转换器的逻辑,替换默认的principal为自定义的User实例即可。

2. 自定义JWT转换器实现

@Component
public class CustomJwtAuthenticationConverter extends JwtAuthenticationConverter {

    private final UserRepository userRepository;

    public CustomJwtAuthenticationConverter(UserRepository userRepository) {
        this.userRepository = userRepository;
        // 保留默认的Scope转权限逻辑,会自动给scope添加SCOPE_前缀
        setJwtGrantedAuthoritiesConverter(new JwtGrantedAuthoritiesConverter());
    }

    @Override
    public Authentication convert(Jwt jwt) {
        // 1. 提取JWT中自带的权限(从scope转换而来)
        Collection<GrantedAuthority> jwtAuthorities = getJwtGrantedAuthoritiesConverter().convert(jwt);
        // 2. 用JWT的subject作为唯一标识查询数据库中的域用户
        String subject = jwt.getSubject();
        User domainUser = userRepository.findBySubject(subject)
                .orElseThrow(() -> new UsernameNotFoundException("对应域用户不存在,subject:" + subject));
        // 3. 合并JWT自带权限和域用户的业务角色权限
        List<GrantedAuthority> allAuthorities = Stream.concat(
                jwtAuthorities.stream(),
                Stream.of(new SimpleGrantedAuthority("ROLE_" + domainUser.getBusinessRole()))
        ).collect(Collectors.toList());
        // 4. 生成Authentication实例,principal替换为自定义的User对象
        return new JwtAuthenticationToken(jwt, allAuthorities, domainUser.getPreferredUsername()) {
            @Override
            public Object getPrincipal() {
                return domainUser;
            }
        };
    }
}

注:上述代码中的UserRepository为资源服务侧自定义的数据持久层接口,需自行实现findBySubject查询方法即可。

3. 注册转换器到安全配置

@Configuration
@EnableMethodSecurity
public class ResourceServerSecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http, CustomJwtAuthenticationConverter jwtConverter) throws Exception {
        http
                .authorizeHttpRequests(auth -> auth.anyRequest().authenticated())
                .oauth2ResourceServer(oauth2Config -> oauth2Config
                        .jwt(jwtConfig -> jwtConfig.jwtAuthenticationConverter(jwtConverter))
                );
        return http.build();
    }
}

4. 业务层使用自定义User实例

配置完成后,即可在Controller、Service层直接注入自定义User实例,同时支持在@PreAuthorize、@PostAuthorize注解中直接访问用户的业务属性做ABAC权限控制:

@DeleteMapping("/comments/{commentId}")
@PreAuthorize("authentication.principal.businessRole == 'ADMIN' or @commentService.getOwnerId(#commentId) == authentication.principal.id")
public void deleteComment(@AuthenticationPrincipal User user, @PathVariable Long commentId) {
    // 直接使用user的业务属性完成逻辑
}

优化建议

如果接口请求量较大,可在转换器中添加本地缓存或Redis缓存存储User信息,避免每次请求都查询数据库,降低数据库压力,缓存失效时机可和用户信息更新事件联动。


内容的提问来源于stack exchange,提问作者Constantin Müller

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.04 23:09:00