如何在Spring OAuth2校验JWT时检查账号是否关联用户档案
可行的实现方案
方案1:自定义JwtAuthenticationProvider(推荐)
把用户档案检查逻辑整合到认证流程中,在JWT认证通过后、生成Authentication对象前执行检查,这是符合Spring Security认证流程的标准做法。
实现步骤:
- 实现自定义的JwtAuthenticationProvider,继承默认实现并重写认证方法:
@Component public class CustomJwtAuthenticationProvider extends JwtAuthenticationProvider { private final UserProfileRepository userProfileRepository; public CustomJwtAuthenticationProvider(JwtDecoder jwtDecoder, UserProfileRepository userProfileRepository) { super(jwtDecoder); this.userProfileRepository = userProfileRepository; } @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { // 先执行默认JWT认证逻辑 Authentication authenticated = super.authenticate(authentication); Jwt jwt = (Jwt) authenticated.getPrincipal(); String userId = jwt.getSubject(); // 跳过注册接口的检查 HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); if (!"/public/register".equals(request.getRequestURI())) { if (!userProfileRepository.existsByUserId(userId)) { throw new InsufficientAuthenticationException("用户档案未创建,请先完成注册"); } } return authenticated; } }
- 在SecurityConfig中替换默认的认证提供者:
@Configuration @EnableWebSecurity public class SecurityConfig { private final CustomJwtAuthenticationProvider customJwtAuthenticationProvider; public SecurityConfig(CustomJwtAuthenticationProvider customJwtAuthenticationProvider) { this.customJwtAuthenticationProvider = customJwtAuthenticationProvider; } @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests(authorizeRequests -> authorizeRequests .requestMatchers("/public/**").permitAll() .anyRequest().authenticated() ) .oauth2ResourceServer(oauth2ResourceServer -> oauth2ResourceServer .jwt(jwt -> jwt.jwtAuthenticationConverter(jwtAuthenticationConverter())) ) .authenticationProvider(customJwtAuthenticationProvider); // 注入自定义认证提供者 return http.build(); } private JwtAuthenticationConverter jwtAuthenticationConverter() { JwtAuthenticationConverter converter = new JwtAuthenticationConverter(); converter.setJwtGrantedAuthoritiesConverter(new CustomAuthoritiesConverter()); return converter; } }
方案2:自定义Filter插入Security链
在OAuth2认证Filter之后添加自定义Filter,对已认证的请求检查用户档案,自动跳过公开接口和注册接口。
实现步骤:
- 编写自定义Filter:
@Component public class UserProfileCheckFilter extends OncePerRequestFilter { private final UserProfileRepository userProfileRepository; public UserProfileCheckFilter(UserProfileRepository userProfileRepository) { this.userProfileRepository = userProfileRepository; } @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { // 跳过所有公开接口 if (request.getRequestURI().startsWith("/public/")) { filterChain.doFilter(request, response); return; } // 获取已认证的用户信息 Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); if (authentication != null && authentication.isAuthenticated() && !(authentication instanceof AnonymousAuthenticationToken)) { Jwt jwt = (Jwt) authentication.getPrincipal(); String userId = jwt.getSubject(); if (!userProfileRepository.existsByUserId(userId)) { response.setStatus(HttpServletResponse.SC_FORBIDDEN); response.getWriter().write("用户档案未创建,请先完成注册"); return; } } filterChain.doFilter(request, response); } // 指定Filter执行顺序,确保在OAuth2认证完成后执行 @Override protected void initFilterBean() throws ServletException { super.initFilterBean(); setOrder(OAuth2ResourceServerAuthenticationFilter.DEFAULT_ORDER + 1); } }
- 在SecurityConfig中注册该Filter:
@Configuration @EnableWebSecurity public class SecurityConfig { private final UserProfileCheckFilter userProfileCheckFilter; public SecurityConfig(UserProfileCheckFilter userProfileCheckFilter) { this.userProfileCheckFilter = userProfileCheckFilter; } @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .addFilterAfter(userProfileCheckFilter, OAuth2ResourceServerAuthenticationFilter.class) .authorizeHttpRequests(authorizeRequests -> authorizeRequests .requestMatchers("/public/**").permitAll() .anyRequest().authenticated() ) .oauth2ResourceServer(oauth2ResourceServer -> oauth2ResourceServer .jwt(jwt -> jwt.jwtAuthenticationConverter(jwtAuthenticationConverter())) ); return http.build(); } private JwtAuthenticationConverter jwtAuthenticationConverter() { JwtAuthenticationConverter converter = new JwtAuthenticationConverter(); converter.setJwtGrantedAuthoritiesConverter(new CustomAuthoritiesConverter()); return converter; } }
方案3:全局方法安全+自定义检查逻辑
通过方法层面的注解控制,开启全局方法安全后,在需要保护的接口上添加检查逻辑,注册接口因属于/public/**范围自动放行。
实现步骤:
- 开启全局方法安全:
@Configuration @EnableGlobalMethodSecurity(prePostEnabled = true) public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration { }
- 编写检查用户档案的服务类:
@Service public class UserProfileCheckService { private final UserProfileRepository userProfileRepository; public UserProfileCheckService(UserProfileRepository userProfileRepository) { this.userProfileRepository = userProfileRepository; } public boolean isProfileExists() { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); if (authentication == null || !(authentication.getPrincipal() instanceof Jwt)) { return false; } Jwt jwt = (Jwt) authentication.getPrincipal(); return userProfileRepository.existsByUserId(jwt.getSubject()); } }
- 在需要保护的控制器方法上添加注解:
@RestController @RequestMapping("/api") public class ApiController { @PreAuthorize("@userProfileCheckService.isProfileExists()") @GetMapping("/resource") public String getResource() { return "protected resource"; } }
方案对比
- 方案1最贴合Spring Security认证流程,检查逻辑整合到认证环节,异常抛出更统一,适合全局统一检查场景。
- 方案2适合需要灵活控制检查路径的场景,可快速调整需要检查的接口范围。
- 方案3适合细粒度的方法级控制,无需修改Security链配置,仅针对特定接口生效。
内容的提问来源于stack exchange,提问作者user2741831
相关产品推荐
相关产品推荐

