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
相关产品推荐
相关产品推荐

