返回顶部
首页 > 资讯 > 服务器 >【若依RuoYi短信验证码登录】汇总
  • 703
分享到

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

数据库服务器java 2023-09-15 10:09:08 703人浏览 八月长安
摘要

遇到一个场景,需要同时支持手机号或者邮箱和密码或者验证码进行登录的场景,故来记录一下。 说明:此流程主要是基于若依框架集成的多种方式登录,主要演示登录业务逻辑和前端登录密码和验证码切换组件和配置Sec

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

说明:此流程主要是基于若依框架集成的多种方式登录,主要演示登录业务逻辑和前端登录密码和验证码切换组件和配置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);}

二:前端登录密码和验证码切换组件:

<template>  <div class="login">    <el-fORM ref="loginForm" :model="loginForm" :rules="loginRules" class="login-form">      <h3 class="title">后台管理系统</h3>      <el-form-item prop="loginP1" v-if="!iSSMsLogin">        <el-input v-model="loginForm.loginP1" type="text" auto-complete="off" placeholder="请输入手机号/邮箱" >          <svg-icon slot="prefix" icon-class="user" class="el-input__icon input-icon" />        </el-input>      </el-form-item>      <el-form-item prop="password" v-if="!isSmsLogin">        <el-input          v-model="loginForm.password"          type="password"          auto-complete="off"          placeholder="请输入密码"          @keyup.enter.native="handleLogin"        >          <svg-icon slot="prefix" icon-class="password" class="el-input__icon input-icon" />        </el-input>      </el-form-item>      <el-form-item prop="loginP2" v-if="isSmsLogin">        <el-input v-model="loginForm.loginP2" type="text" auto-complete="off" placeholder="请输入手机号/邮箱">          <svg-icon slot="prefix" icon-class="user" class="el-input__icon input-icon" />        </el-input>      </el-form-item>      <el-form-item prop="code" v-if="isSmsLogin">        <el-input          v-model="loginForm.code"          auto-complete="off"          placeholder="验证码"          style="width: 63%"          @keyup.enter.native="handleLogin"        >          <svg-icon slot="prefix" icon-class="validCode" class="el-input__icon input-icon" />        </el-input>        <div class="login-code">          <el-button round @click.native.prevent="getSmsCode">{{computeTime>0 ? `(${computeTime}s)已发送` : '获取验证码'}}</el-button>        </div>      </el-form-item>      <el-row>        <el-checkbox v-model="loginForm.rememberMe" style="margin:0px 0px 25px 0px;">{{isSmsLogin ? '记住手机号/邮箱' : '记住密码'}}</el-checkbox>        <div class="sms-login">          <el-button            size="mini"            type="text"            @click.native.prevent="loginMethod"          >            <span v-if="isSmsLogin">账号密码登录</span>            <span v-else>验证码登录</span>          </el-button>        </div>      </el-row>      <el-form-item style="width:100%;">        <el-button          :loading="loading"          size="medium"          type="primary"          style="width:100%;"          @click.native.prevent="handleLogin"        >          <span v-if="!loading">登 录</span>          <span v-else>登 录 中...</span>        </el-button>        <div style="float: right;" v-if="reGISter">          <router-link class="link-type" :to="'/register'">立即注册</router-link>        </div>      </el-form-item>    </el-form>    <!--  底部  -->    <div class="el-login-footer">      <span>Copyright © 2018-2022 xiaoqiang All Rights Reserved.</span>    </div>  </div></template>

三:配置Security:

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

参考UsernamePasswordAuthenticationToken类,继承AbstractAuthenticationToken,重写以下几个方法,自定义短信登录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<? extends GrantedAuthority> 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();    }
  1. 重写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));    }

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

@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)public class SecurityConfig extends WEBSecurityConfigurerAdapter{        @Autowired    @Qualifier("userDetailsByPass")    private UserDetailsService userDetailsService;        @Autowired    @Qualifier("userDetailsByPhone")    private UserDetailsService userDetailsByPhone;//此处省略若干代码......}

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

public class UsernamePhoneAuthenticationProvider implements AuthenticationProvider {    private UserDetailsService userDetailsService;    public UsernamePhoneAuthenticationProvider(UserDetailsService userDetailsService){        setUserDetailsService(userDetailsService);    }        @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;    }        @Override    public boolean supports(Class<?> authentication){        return UsernamePhoneAuthenticationToken.class.isAssignableFrom(authentication);    }    public UserDetailsService getUserDetailsService() {        return userDetailsService;    }    public void setUserDetailsService(UserDetailsService userDetailsService) {        this.userDetailsService = userDetailsService;    }

配置SecurityConfig 的configure方法

@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;        @Autowired    private JwtAuthenticationTokenFilter authenticationTokenFilter;        //此处省略n行代码......        @Override    protected void configure(AuthenticationManagerBuilder auth) throws Exception    {        //手机或邮箱的验证码的验证        auth.authenticationProvider(new UsernamePhoneAuthenticationProvider(userDetailsByPhone));        //账号密码的验证               auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder());    }

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

来源地址:https://blog.csdn.net/z_xiao_qiang/article/details/130170360

--结束END--

本文标题: 【若依RuoYi短信验证码登录】汇总

本文链接: https://lsjlt.com/news/408399.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作