在Spring Boot OAuth2资源服务器通过Keycloak的JWT校验后添加内部用户校验的实现方案
在Spring Boot OAuth2资源服务器通过Keycloak的JWT校验后添加内部用户校验的实现方案
很高兴能帮你搞定这个需求!其实在Spring Boot OAuth2资源服务器完成Keycloak的JWT校验后,再加一层内部用户校验的思路很清晰——核心就是在认证流程的后置环节插入咱们的自定义校验逻辑。下面给你几个实用的实现方案,你可以根据自己的场景选:
方案一:通过自定义UserDetailsService集成本地校验
这种方案和Spring Security的认证流程深度绑定,当Keycloak的JWT校验通过后,会自动触发本地数据库的用户校验,校验失败直接返回认证错误,非常适合全局校验所有受保护接口的场景。
步骤1:实现自定义UserDetailsService
从本地数据库加载用户信息,同时加入你的内部校验规则(比如用户是否存在、是否启用等):
@Service public class LocalUserDetailsService implements UserDetailsService { @Autowired private LocalUserRepository userRepository; // 你的本地用户数据仓库 @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { // 从本地数据库查询用户,不存在则抛出认证异常 LocalUser localUser = userRepository.findByUsername(username) .orElseThrow(() -> new UsernameNotFoundException("该用户未在本地系统注册: " + username)); // 额外校验:比如检查用户状态是否正常 if (!localUser.isEnabled()) { throw new DisabledException("用户账户已被禁用"); } // 转换为Spring Security标准的UserDetails对象(密码可留空,因为Keycloak已经校验过) return User.withUsername(localUser.getUsername()) .password("") .authorities(Collections.emptyList()) // 可从本地数据库获取权限,或沿用JWT中的权限 .build(); } }
步骤2:配置SecurityFilterChain
在安全配置中指定咱们自定义的UserDetailsService,让Spring Security在JWT校验后自动调用它:
@Configuration @EnableWebSecurity public class SecurityConfig { @Autowired private LocalUserDetailsService localUserDetailsService; @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http.oauth2ResourceServer(oauth2 -> oauth2 .jwt(jwt -> jwt .userDetailsService(localUserDetailsService) // 绑定自定义服务 ) ); // 这里可以添加你的权限规则、跨域配置等 return http.build(); } }
方案二:自定义Filter插入认证流程
如果需要更灵活的控制(比如只针对部分路径校验、添加日志或其他自定义操作),可以写一个自定义Filter,放在OAuth2认证Filter之后执行。
步骤1:实现自定义校验Filter
@Component public class LocalUserValidationFilter extends OncePerRequestFilter { @Autowired private LocalUserRepository userRepository; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { // 获取已经通过JWT校验的认证信息 Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); if (authentication != null && authentication.isAuthenticated()) { String username = authentication.getName(); // 查询本地数据库 LocalUser localUser = userRepository.findByUsername(username).orElse(null); if (localUser == null) { // 本地无此用户,清空认证上下文并返回401 SecurityContextHolder.clearContext(); response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "用户未在本地系统注册"); return; } // 额外校验:比如检查用户是否处于活跃状态 if (!localUser.isActive()) { SecurityContextHolder.clearContext(); response.sendError(HttpServletResponse.SC_FORBIDDEN, "用户账户已冻结"); return; } } // 校验通过,继续执行后续请求链 filterChain.doFilter(request, response); } // 可选:排除不需要校验的公开接口 @Override protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException { return request.getRequestURI().startsWith("/public/"); } }
步骤2:将Filter加入安全链
在SecurityConfig中把自定义Filter放在OAuth2认证Filter之后:
@Configuration @EnableWebSecurity public class SecurityConfig { @Autowired private LocalUserValidationFilter localUserValidationFilter; @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http.oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults())) // 指定Filter的执行顺序,要在OAuth2认证之后 .addFilterAfter(localUserValidationFilter, OAuth2AuthorizationRequestRedirectFilter.class); // 其他配置 return http.build(); } }
方案三:方法级安全(针对特定接口)
如果不需要全局校验,只需要在部分接口上做内部校验,可以用Spring的方法级安全结合自定义校验逻辑。
步骤1:实现自定义校验器
@Component public class LocalUserValidator { @Autowired private LocalUserRepository userRepository; public boolean validateUser(String username) { LocalUser localUser = userRepository.findByUsername(username).orElse(null); if (localUser == null) { return false; } // 这里可以添加任意内部校验规则 return localUser.isActive(); } }
步骤2:启用方法级安全并在接口上使用
首先在配置类上开启方法级安全:
@Configuration @EnableWebSecurity @EnableMethodSecurity // 开启方法级安全 public class SecurityConfig { @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http.oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults())); return http.build(); } }
然后在Controller的目标方法上添加@PreAuthorize注解,调用自定义校验器:
@RestController @RequestMapping("/api") public class MyController { @Autowired private LocalUserValidator localUserValidator; @GetMapping("/sensitive") @PreAuthorize("@localUserValidator.validateUser(authentication.name)") public String sensitiveEndpoint() { return "这是需要本地用户校验的敏感接口"; } }
方案选择建议
- 全局校验所有受保护接口:优先选方案一(集成度高,符合Spring Security规范)或方案二(灵活性更强);
- 仅部分接口需要校验:选方案三(更轻量化,针对性强)。
备注:内容来源于stack exchange,提问作者dimmxx
相关产品推荐
相关产品推荐

