You need to enable JavaScript to run this app. Spring Boot 无用户名密码场景下如何使用JWT令牌?
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot 无用户名密码场景下如何使用JWT令牌?

解决方案

核心思路

loadUserByUsername的参数username本质是Spring Security约定的用户唯一标识占位符,不需要局限于「传统用户名」的语义,你完全可以将手机号作为该参数的传值使用,不需要对核心流程做大量改造。

具体实现步骤

  • 第一步:自定义适配手机号场景的UserDetails实现类
    不需要保留用户名、密码字段,直接把手机号作为唯一标识属性,getUsername()方法直接返回手机号,无密码场景下getPassword()返回空字符串即可。
    public class MobileUserDetails implements UserDetails {
        private final String mobile;
        private final List<GrantedAuthority> authorities;
    
        public MobileUserDetails(String mobile, List<GrantedAuthority> authorities) {
            this.mobile = mobile;
            this.authorities = authorities;
        }
    
        @Override
        public Collection<? extends GrantedAuthority> getAuthorities() {
            return authorities;
        }
    
        // 无密码场景直接返回空即可
        @Override
        public String getPassword() {
            return "";
        }
    
        // 这里把手机号当做用户名返回
        @Override
        public String getUsername() {
            return mobile;
        }
    
        // 下面四个状态方法根据你的业务需求返回即可
        @Override
        public boolean isAccountNonExpired() {
            return true;
        }
    
        @Override
        public boolean isAccountNonLocked() {
            return true;
        }
    
        @Override
        public boolean isCredentialsNonExpired() {
            return true;
        }
    
        @Override
        public boolean isEnabled() {
            return true;
        }
    }
    
  • 第二步:实现UserDetailsService接口,按手机号查询用户
    直接把方法入参当做手机号去数据库查询对应的用户记录,组装返回自定义的MobileUserDetails对象即可。
    @Service
    public class MobileUserDetailsService implements UserDetailsService {
        @Autowired
        private UserMapper userMapper;
    
        @Override
        public UserDetails loadUserByUsername(String mobile) throws UsernameNotFoundException {
            // 直接用传入的参数作为手机号查询用户
            User user = userMapper.selectByMobile(mobile);
            if (user == null) {
                throw new UsernameNotFoundException("手机号对应的用户不存在");
            }
            // 组装权限,无权限场景可传空列表
            List<GrantedAuthority> authorities = AuthorityUtils.createAuthorityList("ROLE_USER");
            return new MobileUserDetails(user.getMobile(), authorities);
        }
    }
    
  • 第三步:适配无密码的认证逻辑
    因为你的场景是验证码登录没有密码,需要自定义AuthenticationProvider跳过默认的密码校验逻辑,补充验证码校验的业务规则。
    @Component
    public class SmsCodeAuthenticationProvider implements AuthenticationProvider {
        @Autowired
        private MobileUserDetailsService userDetailsService;
    
        @Override
        public Authentication authenticate(Authentication authentication) throws AuthenticationException {
            String mobile = authentication.getName();
            UserDetails userDetails = userDetailsService.loadUserByUsername(mobile);
            // 此处补充验证码校验逻辑,比如从Redis取存储的验证码和请求传参比对
            return new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
        }
    
        @Override
        public boolean supports(Class<?> authentication) {
            return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
        }
    }
    
  • 第四步:适配JWT流程
    1. 登录阶段:用户验证码校验通过后,将手机号作为JWT的sub(主体)字段生成令牌返回给前端
    2. 鉴权阶段:解析请求携带的JWT拿到手机号,传给loadUserByUsername获取用户信息,存入Spring Security上下文完成鉴权

注意事项

  • JWT生成时必须使用强签名算法,解析时严格校验签名有效性,避免令牌伪造
  • 手机号查询、入库前统一格式化(比如去掉+86前缀、空格等),避免出现同一手机号不同格式匹配不到的问题
  • 如果后续需要扩展邮箱、账号等多登录方式,可以自定义Authentication实现类扩展认证逻辑,不需要改造现有UserDetailsService的代码

内容的提问来源于stack exchange,提问作者Blay-Z

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.14 14:54:04