【若依RuoYi短信验证码登录】汇总 您所在的位置:网站首页 短信验证号码怎么弄 【若依RuoYi短信验证码登录】汇总

【若依RuoYi短信验证码登录】汇总

2024-07-10 22:38| 来源: 网络整理| 查看: 265

遇到一个场景,需要同时支持手机号或者邮箱和密码或者验证码进行登录的场景,故来记录一下。

说明:此流程主要是基于若依框架集成的多种方式登录,主要演示登录业务逻辑和前端登录密码和验证码切换组件和配置Security

一:后端登录业务逻辑代码:

因为有多个端,多个语言共享登录接口,所以,接口定义尽量简单,接口内的逻辑判断尽量全面,判断手机号还是邮箱登录,再判断密码还是验证码登录,验证完了之后,再去验证用户是否存在数据库中,如果是密码登录的,则需要对比密码,然后再创建一个登录的token,返回。

public AjaxResult login(LoginBody loginBody){ //验证手机号和邮箱是否符合格式或者是否为空 boolean isPhone = false; //先判断是手机号还是邮箱登录 if(StringUtils.isNotEmpty(loginBody.getTel()) && Pattern.compile("^[1][1,2,3,4,5,6,7,8,9][0-9]{9}$").matcher(loginBody.getTel()).matches()){ isPhone = true; }else if(StringUtils.isNotEmpty(loginBody.getEmail()) && loginBody.getEmail().matches("\\w{1,30}@[a-zA-Z0-9]{2,20}(\\.[a-zA-Z0-9]{2,20}){1,2}")){ isPhone = false; }else{ return AjaxResult.error("登录失败,邮箱和手机号不能同时为空!"); } //在判断是密码还是验证码登录 boolean isPassword = false; if(StringUtils.isNotEmpty(loginBody.getPassword())){ isPassword = true; }else if(StringUtils.isNotEmpty(loginBody.getCode())){ isPassword = false; }else{ return AjaxResult.error("登录失败,密码和验证码不能同时为空!"); } //验证码验证 if(!isPassword){ String codeKey = "0:" + isPhone? loginBody.getTel(): loginBody.getEmail()); String value = redisCache.getCacheObject(codeKey); if (StringUtils.isNotEmpty(value)) { if (!value.equals(loginBody.getCode())) { return AjaxResult.error("验证码错误!"); } }else{ return AjaxResult.error("验证码超时!"); } } // 用户验证 Authentication authentication = null; try { if(isPassword){ UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(isPhone? loginBody.getTel(): loginBody.getEmail(), loginBody.getPassword()); AuthenticationContextHolder.setContext(authenticationToken); // 该方法会去调用UserDetailsServiceImpl.loadUserByUsername authentication = authenticationManager.authenticate(authenticationToken); }else{ // 该方法会去调用UsernamePhoneUserDetailsServiceImpl.loadUserByUsername authentication = authenticationManager.authenticate(new UsernamePhoneAuthenticationToken(isPhone? loginBody.getTel(): loginBody.getEmail())); } } catch (Exception e) { if (e instanceof BadCredentialsException) { AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match"))); throw new UserPasswordNotMatchException(); } else { AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, e.getMessage())); throw new ServiceException(e.getMessage()); } } finally { AuthenticationContextHolder.clearContext(); } AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"))); LoginUser loginUser = (LoginUser) authentication.getPrincipal(); recordLoginInfo(loginUser.getUserId()); // 生成token return tokenService.createToken(loginUser); } 二:前端登录密码和验证码切换组件: 后台管理系统 {{computeTime>0 ? `(${computeTime}s)已发送` : '获取验证码'}} {{isSmsLogin ? '记住手机号/邮箱' : '记住密码'}} 账号密码登录 验证码登录 登 录 登 录 中... 立即注册 Copyright © 2018-2022 xiaoqiang All Rights Reserved. 三:配置Security:

按照Security的流程图可知,实现多种方式登录,只需要重写三个主要的组件,第一个用户认证处理过滤器,第二个用户认证token类,第三个,自定义短信登录身份认证。 在这里插入图片描述

1.参考UsernamePasswordAuthenticationToken类,继承AbstractAuthenticationToken,重写以下几个方法,自定义短信登录token验证。

/** * 自定义短信登录token验证 */ public class UsernamePhoneAuthenticationToken extends AbstractAuthenticationToken { /** * 手机号 */ private final Object principal; public UsernamePhoneAuthenticationToken(Object principals){ super(null); this.principal = principals; setAuthenticated(false); } public UsernamePhoneAuthenticationToken(Object principal, Collection authorities){ super(authorities); this.principal = principal; super.setAuthenticated(true); } @Override public Object getCredentials() { return null; } @Override public Object getPrincipal() { return this.principal; } @Override public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException{ if(isAuthenticated){ throw new IllegalArgumentException( "Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead"); } super.setAuthenticated(false); } @Override public void eraseCredentials(){ super.eraseCredentials(); } 重写UserDetailsService类的loadUserByUsername方法,实现用户验证处理。 /** * 用户验证处理 */ @Service("userDetailsByPhone") public class UsernamePhoneUserDetailsServiceImpl implements UserDetailsService { private static final Logger logger = LoggerFactory.getLogger(UsernamePhoneUserDetailsServiceImpl.class); @Autowired private ISysUserService userService; @Autowired private SysUserMapper sysUserMapper; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { SysUser sysUser; if(Pattern.compile("^[1][1,2,3,4,5,6,7,8,9][0-9]{9}$").matcher(username).matches()){ sysUser = sysUserMapper.selectUserByTel(username); }else if(username.matches("\\w{1,30}@[a-zA-Z0-9]{2,20}(\\.[a-zA-Z0-9]{2,20}){1,2}")){ sysUser = sysUserMapper.selectUserByEmail(username); }else{ throw new ServiceException("请使用手机号或者邮箱进行登录!"); } if(StringUtils.isNull(sysUser)){ logger.info("登录用户:{} 不存在.", username); throw new ServiceException("登录用户:" + username+ " 不存在"); } return createLoginUser(sysUser); } public UserDetails createLoginUser(SysUser user){ return new LoginUser(user.getUserId(), user.getDeptId(), user, permissionService.getMenuPermission(user)); }

3.注意,此时会有两个用户验证的处理类,一个是原来的UserDetailsServiceImpl,另一个是现在的UsernamePhoneUserDetailsServiceImpl,需要去SecurityConfig配置类去配置不同的用户认证业务类,通过@Qualifer指定注入的bean。

/** * spring security配置 * * @author victor_zhang */ @EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true) public class SecurityConfig extends WebSecurityConfigurerAdapter { /** * 自定义用户认证逻辑(账号密码) */ @Autowired @Qualifier("userDetailsByPass") private UserDetailsService userDetailsService; /** * 自定义用户认证逻辑(手机号验证码) */ @Autowired @Qualifier("userDetailsByPhone") private UserDetailsService userDetailsByPhone; //此处省略若干代码...... }

4.自定义一个短信登录的身份鉴权, UserDetailsService 只负责根据用户名返回用户信息,AuthenticationProvider负责将 UserDetails 组装成 Authentication 向调用者返回。

/** * 自定义短信登录身份认证 */ public class UsernamePhoneAuthenticationProvider implements AuthenticationProvider { private UserDetailsService userDetailsService; public UsernamePhoneAuthenticationProvider(UserDetailsService userDetailsService){ setUserDetailsService(userDetailsService); } /** * 重写authentication方法,实现身份验证逻辑 */ @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { UsernamePhoneAuthenticationToken authenticationToken = (UsernamePhoneAuthenticationToken) authentication; String phone = (String) authenticationToken.getPrincipal(); //委托 UserDetailsService 查找系统用户 UserDetails userDetails = userDetailsService.loadUserByUsername(phone); //鉴权成功,返回一个拥有鉴权的AbstractAuthenticationToken UsernamePhoneAuthenticationToken authenticationTokenRes = new UsernamePhoneAuthenticationToken(userDetails, userDetails.getAuthorities()); authenticationTokenRes.setDetails(authenticationToken.getDetails()); return authenticationTokenRes; } /** * 重写supports方法,指定此AuthenticationProvider 仅支持短信验证码身份验证 */ @Override public boolean supports(Class authentication){ return UsernamePhoneAuthenticationToken.class.isAssignableFrom(authentication); } public UserDetailsService getUserDetailsService() { return userDetailsService; } public void setUserDetailsService(UserDetailsService userDetailsService) { this.userDetailsService = userDetailsService; }

5.配置SecurityConfig 的configure方法

/** * spring security配置 * * @author victor_zhang */ @EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true) public class SecurityConfig extends WebSecurityConfigurerAdapter { /** * 自定义用户认证逻辑(账号密码) */ @Autowired @Qualifier("userDetailsByPass") private UserDetailsService userDetailsService; /** * 自定义用户认证逻辑(手机号验证码) */ @Autowired @Qualifier("userDetailsByPhone") private UserDetailsService userDetailsByPhone; /** * 认证失败处理类 */ @Autowired private AuthenticationEntryPointImpl unauthorizedHandler; /** * 退出处理类 */ @Autowired private LogoutSuccessHandlerImpl logoutSuccessHandler; /** * token认证过滤器 */ @Autowired private JwtAuthenticationTokenFilter authenticationTokenFilter; //此处省略n行代码...... /** * 身份认证接口 */ @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { //手机或邮箱的验证码的验证 auth.authenticationProvider(new UsernamePhoneAuthenticationProvider(userDetailsByPhone)); //账号密码的验证 auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder()); }

大概就这么多,如果有更好的方式,欢迎交流。



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有