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

Spring Security如何配置仅传JWT中username给loadUserByUsername

问题背景

在JWT令牌中额外编码用户名、用户权限等业务数据时,出现Spring Security调用UserDetailsService.loadUserByUsername()方法时,传入参数为完整解码后的JWT载荷JSON字符串,而非单独username字段的问题,实际传入参数示例:
{"username":"123456788","language":null,"deactivationReason":null,"terminationYear":null,"authorities":[{"authority":"ROLE_ADMIN_ROK"}],"active":false,"terminated":false}
待解决的两个核心问题:

  • 如何修改配置,实现仅提取JWT中的username字段传递给loadUserByUsername方法
  • AuthenticationProvider和UserDetailsService的调用顺序、执行优先级是怎样的

问题解答

问题1:实现仅提取JWT中username字段传给loadUserByUsername方法

这个问题的根因在自定义的JwtRequestFilter中:Spring Security本身不会自动解析JWT内容,是过滤器在构造待认证的Authentication对象时,把完整解码后的JWT载荷JSON字符串设置为了principal身份信息,后续流程就会把这个完整字符串作为参数传给loadUserByUsername。
修复方案分两种,优先选第一种:

  • 方案一(推荐):修改JwtRequestFilter的解析逻辑
    解析完JWT拿到载荷Claims后,不要传入整个载荷的JSON串,单独提取username字段值构造认证对象:
    // 解析JWT得到载荷后,单独提取username
    String username = claims.get("username", String.class);
    // 构造认证令牌时,principal参数传入单独提取的username,不要传整个JSON
    UsernamePasswordAuthenticationToken authReq = new UsernamePasswordAuthenticationToken(username, null, userAuthorities);
    SecurityContextHolder.getContext().setAuthentication(authReq);
    
  • 方案二(临时兜底):在MyUserDetailsService中兼容解析入参
    如果暂时不方便修改过滤器,可以在loadUserByUsername方法开头判断入参格式,遇到JSON串先解析出username再执行后续查库逻辑:
    @Override
    public UserDetails loadUserByUsername(String inputParam) throws UsernameNotFoundException {
        String actualUsername = inputParam;
        // 识别传入完整JWT载荷JSON的场景
        if (inputParam != null && inputParam.trim().startsWith("{")) {
            try {
                ObjectMapper objectMapper = new ObjectMapper();
                JsonNode payloadNode = objectMapper.readTree(inputParam);
                actualUsername = payloadNode.get("username").asText();
            } catch (Exception e) {
                throw new UsernameNotFoundException("Invalid token content");
            }
        }
        // 原有查库逻辑替换为使用actualUsername查询
        Optional<Aektab> aekAztab = aektabRepository.findByGeNr(actualUsername);
        // 后续原有判断逻辑保持不变即可
    }
    

问题2:AuthenticationProvider和UserDetailsService的调用顺序

二者执行顺序为AuthenticationProvider先触发,UserDetailsService由AuthenticationProvider在认证流程内部按需调用,完整执行链路如下:

  1. 请求经过安全过滤器链时,自定义过滤器解析请求携带的凭证,构造未认证的Authentication对象,传入AuthenticationManager
  2. AuthenticationManager遍历所有已注册的AuthenticationProvider,找到第一个支持当前Authentication类型的Provider,调用其authenticate()方法执行认证逻辑
  3. 如果使用Spring Security默认的DaoAuthenticationProvider,该Provider会在自身认证逻辑中调用注入的UserDetailsService的loadUserByUsername方法,查询用户信息、完成密码校验、权限填充
  4. 如果是自定义AuthenticationProvider(比如你当前写的CustomAuthenticationProvider),只有你主动在authenticate()方法里调用UserDetailsService,它才会执行;你当前贴的Provider代码里没有任何调用UserDetailsService的逻辑,正常情况下你写的MyUserDetailsService根本不会被触发。

注意:你当前自定义的CustomAuthenticationProvider没有做任何凭证校验,只要传入UsernamePasswordAuthenticationToken就直接返回认证通过状态,存在严重安全风险,需要补充JWT签名合法性校验、用户状态校验、权限匹配逻辑后再上线使用。


内容的提问来源于stack exchange,提问作者Arjun

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.02 23:15:58