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流程
- 登录阶段:用户验证码校验通过后,将手机号作为JWT的
sub(主体)字段生成令牌返回给前端 - 鉴权阶段:解析请求携带的JWT拿到手机号,传给
loadUserByUsername获取用户信息,存入Spring Security上下文完成鉴权
- 登录阶段:用户验证码校验通过后,将手机号作为JWT的
注意事项
- JWT生成时必须使用强签名算法,解析时严格校验签名有效性,避免令牌伪造
- 手机号查询、入库前统一格式化(比如去掉+86前缀、空格等),避免出现同一手机号不同格式匹配不到的问题
- 如果后续需要扩展邮箱、账号等多登录方式,可以自定义
Authentication实现类扩展认证逻辑,不需要改造现有UserDetailsService的代码
内容的提问来源于stack exchange,提问作者Blay-Z
相关产品推荐
相关产品推荐

