如何在JWT认证过程中或认证完成后设置自定义Principal对象?
我来帮你解决这个问题——要把Spring Security里的Principal从Jwt换成你的AppUserEntity,同时自动创建Firebase用户对应的后端记录,其实有几种优雅的方案,完全不用改动你现有的业务代码:
核心思路
Spring Security的JWT认证流程中,我们可以在JWT验证通过后,替换Authentication对象里的Principal为自定义的用户实体,同时完成“查询/创建用户”的逻辑。下面是具体实现方案:
方案一:自定义AuthenticationProvider(最符合Spring Security设计)
这个方案直接介入认证核心流程,委托默认的JWT验证逻辑,之后替换Principal,是最推荐的方式。
1. 实现自定义AuthenticationProvider
@Component public class FirebaseJwtAuthenticationProvider implements AuthenticationProvider { private final JwtAuthenticationProvider delegateProvider; private final UserService userService; // 注入Firebase的JwtDecoder和你的用户服务 public FirebaseJwtAuthenticationProvider(JwtDecoder jwtDecoder, UserService userService) { this.delegateProvider = new JwtAuthenticationProvider(jwtDecoder); this.userService = userService; } @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { // 先让默认的JWT验证器校验令牌有效性 Authentication authenticated = delegateProvider.authenticate(authentication); if (authenticated.isAuthenticated()) { Jwt jwt = (Jwt) authenticated.getPrincipal(); // 从Firebase JWT中提取用户标识(比如user_id、email) String firebaseUid = jwt.getClaim("user_id"); String email = jwt.getClaim("email"); // 查询或自动创建后端用户 AppUserEntity user = userService.findOrCreateByFirebaseUid(firebaseUid, email); // 生成新的Authentication对象,替换Principal为AppUserEntity return new UsernamePasswordAuthenticationToken( user, authenticated.getCredentials(), // 保留原JWT凭证 authenticated.getAuthorities() // 保留JWT中的权限信息 ); } return authenticated; } @Override public boolean supports(Class<?> authentication) { // 只处理JWT类型的认证请求 return delegateProvider.supports(authentication); } }
2. 在Spring Security配置中注册自定义Provider
@Configuration @EnableWebSecurity public class SecurityConfig { private final FirebaseJwtAuthenticationProvider firebaseJwtAuthProvider; public SecurityConfig(FirebaseJwtAuthenticationProvider firebaseJwtAuthProvider) { this.firebaseJwtAuthProvider = firebaseJwtAuthProvider; } @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests(auth -> auth.anyRequest().authenticated()) .oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults())) .authenticationProvider(firebaseJwtAuthProvider); // 添加自定义Provider return http.build(); } }
方案二:自定义JwtAuthenticationConverter(更简洁)
如果不想替换整个AuthenticationProvider,可以利用Spring Security提供的JwtAuthenticationConverter,在转换JWT到Authentication的环节替换Principal。
1. 实现自定义转换器
@Component public class CustomJwtAuthenticationConverter implements JwtAuthenticationConverter { private final JwtAuthenticationConverter delegateConverter = new JwtAuthenticationConverter(); private final UserService userService; public CustomJwtAuthenticationConverter(UserService userService) { this.userService = userService; // 可选:配置JWT权限转换逻辑(比如从claim中提取角色) delegateConverter.setJwtGrantedAuthoritiesConverter(new JwtGrantedAuthoritiesConverter()); } @Override public Authentication convert(Jwt jwt) { // 先调用默认转换器得到JwtAuthenticationToken JwtAuthenticationToken authToken = (JwtAuthenticationToken) delegateConverter.convert(jwt); if (authToken != null) { String firebaseUid = jwt.getClaim("user_id"); String email = jwt.getClaim("email"); AppUserEntity user = userService.findOrCreateByFirebaseUid(firebaseUid, email); // 替换Principal为AppUserEntity return new UsernamePasswordAuthenticationToken( user, authToken.getCredentials(), authToken.getAuthorities() ); } return authToken; } }
2. 配置转换器到Security链
@Configuration @EnableWebSecurity public class SecurityConfig { private final JwtDecoder jwtDecoder; private final CustomJwtAuthenticationConverter customJwtAuthConverter; public SecurityConfig(JwtDecoder jwtDecoder, CustomJwtAuthenticationConverter customJwtAuthConverter) { this.jwtDecoder = jwtDecoder; this.customJwtAuthConverter = customJwtAuthConverter; } @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests(auth -> auth.anyRequest().authenticated()) .oauth2ResourceServer(oauth2 -> oauth2 .jwt(jwt -> jwt .decoder(jwtDecoder) .jwtAuthenticationConverter(customJwtAuthConverter) // 设置自定义转换器 ) ); return http.build(); } }
方案三:后置Filter(灵活性最高)
如果不想改动认证核心逻辑,还可以在BearerTokenAuthenticationFilter之后加一个自定义Filter,直接修改SecurityContext中的Authentication对象。
1. 实现后置Filter
@Component public class ReplacePrincipalFilter extends OncePerRequestFilter { private final UserService userService; public ReplacePrincipalFilter(UserService userService) { this.userService = userService; } @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); // 只处理已认证的JwtAuthenticationToken if (authentication != null && authentication.isAuthenticated() && authentication instanceof JwtAuthenticationToken) { Jwt jwt = (Jwt) authentication.getPrincipal(); String firebaseUid = jwt.getClaim("user_id"); String email = jwt.getClaim("email"); AppUserEntity user = userService.findOrCreateByFirebaseUid(firebaseUid, email); // 替换Authentication对象的Principal Authentication newAuth = new UsernamePasswordAuthenticationToken( user, authentication.getCredentials(), authentication.getAuthorities() ); SecurityContextHolder.getContext().setAuthentication(newAuth); } filterChain.doFilter(request, response); } }
2. 注册Filter到Security链
@Configuration @EnableWebSecurity public class SecurityConfig { private final JwtDecoder jwtDecoder; private final ReplacePrincipalFilter replacePrincipalFilter; public SecurityConfig(JwtDecoder jwtDecoder, ReplacePrincipalFilter replacePrincipalFilter) { this.jwtDecoder = jwtDecoder; this.replacePrincipalFilter = replacePrincipalFilter; } @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests(auth -> auth.anyRequest().authenticated()) .oauth2ResourceServer(oauth2 -> oauth2.jwt(jwt -> jwt.decoder(jwtDecoder))) .addFilterAfter(replacePrincipalFilter, BearerTokenAuthenticationFilter.class); // 放在JWT认证Filter之后 return http.build(); } }
补充:实现用户查询/创建逻辑
不管用哪个方案,都需要一个UserService来处理用户的查询和自动创建:
@Service public class UserService { private final AppUserRepository userRepository; public UserService(AppUserRepository userRepository) { this.userRepository = userRepository; } public AppUserEntity findOrCreateByFirebaseUid(String firebaseUid, String email) { // 查询现有用户,不存在则创建新用户 return userRepository.findByFirebaseUid(firebaseUid) .orElseGet(() -> { AppUserEntity newUser = new AppUserEntity(); newUser.setFirebaseUid(firebaseUid); newUser.setEmail(email); newUser.setCreatedAt(LocalDateTime.now()); // 可添加其他默认字段 return userRepository.save(newUser); }); } }
最终效果
完成配置后,你在业务代码中就能直接获取AppUserEntity类型的Principal了:
Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); AppUserEntity currentUser = (AppUserEntity) authentication.getPrincipal();
完全不用修改你现有的依赖AppUserEntity的业务逻辑,同时实现了自动创建Firebase用户对应的后端记录。
内容的提问来源于stack exchange,提问作者Stefan Falk
相关产品推荐
相关产品推荐

