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

如何在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.28 12:54:10