若依前后端分离版ruoyi-vue:增加新的登录接口(新用户表),用于小程序或者APP获取token,并使用若依的验证方法,结合腾讯云短信验证码实现手机号+验证码登陆

04-19 8683阅读 0评论

1.新建—SmsController类

package com.wanuw.user.controller.login;
import com.wanuw.common.constant.Constants;
import com.wanuw.common.core.domain.AjaxResult;
import com.wanuw.user.service.SmsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
/**
 * 手机端验证码
 *
 * @author dagang
 */
@RestController
@RequestMapping("/sms")
public class SmsController
{
    @Autowired
    private SmsService smsService;
    /**
     * 生成手机验证码
     */
    @PostMapping("/send")
    public AjaxResult sendSmsCode(@RequestParam(value = "phoneNumber") String phoneNumber) {
        String message = smsService.sendSmsCode(phoneNumber);
        return AjaxResult.success(message);
    }
    /**
     * 验证手机验证码
     */
    @PostMapping("/verify")
    public AjaxResult verifySmsCode(@RequestParam(value = "phoneNumber") String phoneNumber,
        @RequestParam(value = "smsCode") String smsCode) {
        AjaxResult ajax = AjaxResult.success();
        String token = smsService.verifySmsCode(phoneNumber, smsCode);
        ajax.put(Constants.TOKEN, token);
        return ajax;
    }
}

2.新建—SmsService类

package com.wanuw.user.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.wanuw.common.core.domain.user.member.DbUser;
/**
 * 手机端用户登录验证
 *
 * @author dagang
 */
public interface SmsService extends IService {
    /**
     * 生成手机验证码
     */
    String sendSmsCode(String phoneNumber);
    /**
     * 验证手机验证码
     */
    String verifySmsCode(String phoneNumber, String smsCode);
}

3.新建—SmsServiceImpl 实现层

此处注意注入的是:AppAuthenticationProvider

TokenService和RedisCache是若依框架自带

package com.wanuw.user.service.impl;
import cn.hutool.core.util.RandomUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.tencentcloudapi.sms.v20190711.models.SendStatus;
import com.wanuw.common.config.tencent.SmsConfig;
import com.wanuw.common.constant.CacheConstants;
import com.wanuw.common.constant.Constants;
import com.wanuw.common.core.domain.model.LoginUser;
import com.wanuw.common.core.redis.RedisCache;
import com.wanuw.common.exception.ServiceException;
import com.wanuw.common.exception.user.CaptchaException;
import com.wanuw.common.exception.user.CaptchaExpireException;
import com.wanuw.common.exception.user.UserNotExistsException;
import com.wanuw.common.exception.user.UserPasswordNotMatchException;
import com.wanuw.common.utils.ChineseNameUtils;
import com.wanuw.common.utils.MessageUtils;
import com.wanuw.common.utils.SecurityUtils;
import com.wanuw.common.utils.tencent.SmsUtil;
import com.wanuw.framework.manager.AsyncManager;
import com.wanuw.framework.manager.factory.AsyncFactory;
import com.wanuw.framework.web.service.TokenService;
import com.wanuw.common.core.domain.user.member.DbUser;
import com.wanuw.member.mapper.DbUserMapper;
import com.wanuw.member.service.IDbUserService;
import com.wanuw.user.controller.login.AppAuthenticationProvider;
import com.wanuw.user.service.SmsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;
/**
 * 手机端用户登录验证
 *
 * @author dagang
 */
@Service
public class SmsServiceImpl extends ServiceImpl implements SmsService {
    @Autowired
    private TokenService tokenService;
    @Resource
    private AppAuthenticationProvider authenticationManager;
    @Resource
    private SmsConfig smsConfig;
    @Autowired
    private RedisCache redisCache;
    @Autowired
    private IDbUserService dbUserService;
    /**
     * 创建手机验证码
     */
    @Override
    public String sendSmsCode(String phoneNumber) {
        // 下发手机号码,采用e.164标准,+[国家或地区码][手机号]
        String[] phoneNumbers = {"+86" + phoneNumber};
        // 生成6位随机数字字符串
        String smsCode = RandomUtil.randomNumbers(6);
        // 模板参数:若无模板参数,则设置为空(参数1为随机验证码,参数2为有效时间)
        String[] templateParams = {smsCode, smsConfig.getExpireTime().toString()};
        // 发送短信验证码
        SendStatus[] sendStatuses = SmsUtil.sendSms(smsConfig, templateParams, phoneNumbers);
        if ("Ok".equals(sendStatuses[0].getCode())) {
            // 创建短信验证码缓存的key并设置过期时间
            String key = CacheConstants.CAPTCHA_TELPHONE_CODE_KEY + phoneNumber;
            redisCache.setCacheObject(key, smsCode, smsConfig.getExpireTime(), TimeUnit.MINUTES);
            return "验证码发送成功";
        } else {
            return "验证码发送失败:" + sendStatuses[0].getMessage();
            //return "验证码发送失败:";
        }
    }
    /**
     * 验证手机验证码
     */
    @Override
    public String verifySmsCode(String phoneNumber, String smsCode) {
        //1.验证码校验
        checkCode(phoneNumber, smsCode);
        //2.检查用户手机号是否已经注册,若未注册,直接注册成用户
        LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper();
        queryWrapper.eq(DbUser::getUserTel, phoneNumber);
        DbUser dbUser = dbUserService.getOne(queryWrapper);
        if (dbUser == null) {
            //说明未注册过账户,开始注册账户
            DbUser dbUserNew = new DbUser();
            dbUserNew.setUserTel(phoneNumber);
            //添加默认密码123456
            dbUserNew.setPassword(SecurityUtils.encryptPassword("123456"));
            //这是个生成随机汉字名字的工具
            dbUserNew.setUserNickname(ChineseNameUtils.randomChineseName());
            dbUserNew.setUserName(phoneNumber);
            dbUserService.save(dbUserNew);
        }
        String username = phoneNumber;
        String password = "123456";
        //自定义用户名和密码
        // 用户验证
        Authentication authentication;
        try {
            // 原来其实就这么一句话:该方法会去调用UserDetailsServiceImpl.loadUserByUsername。指的是原来若依自定义的UserDetailsServiceImpl
            //此处会让人很迷惑,特别是对新手来说。其实就是调用了AppUserDetailsServiceImpl中的loadUserByUsername方法
            //而这个方法的是通过AppAuthenticationProvider中去发起的。所以这个authenticationManager  其实就是注入的AppAuthenticationProvider
            //这个地方一定要注意!!!!!
            authentication = authenticationManager
                    .authenticate(new UsernamePasswordAuthenticationToken(username, password));
        } 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());
            }
        }
        AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success")));
        LoginUser loginUser = (LoginUser) authentication.getPrincipal();
        //recordLoginInfo(dbUser.getId());
        // 生成token
        return tokenService.createToken(loginUser);
    }
    /**
     *
     * @param phoneNumber 电话号码
     * @param smsCode  验证码
     */
    public void checkCode(String phoneNumber, String smsCode) {
        // 创建key
        String key = CacheConstants.CAPTCHA_TELPHONE_CODE_KEY + phoneNumber;
        String captcha = redisCache.getCacheObject(key);
        //校验传入数据是否为空
        if (phoneNumber == null || smsCode == null) {
            AsyncManager.me().execute(AsyncFactory.recordLogininfor(phoneNumber, Constants.LOGIN_FAIL, MessageUtils.message("not.null")));
            throw new UserNotExistsException();
        }
        //校验验证码位数
        if (smsCode.length() != 6) {
            AsyncManager.me().execute(AsyncFactory.recordLogininfor(phoneNumber, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
            throw new UserPasswordNotMatchException();
        }
        // 判断指定key是否存在并且未过期
        if (captcha == null)
        {
            AsyncManager.me().execute(AsyncFactory.recordLogininfor(phoneNumber, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire")));
            throw new CaptchaExpireException();
        }
        // 验证输入的验证码是否正确
        if (!smsCode.equalsIgnoreCase(captcha))
        {
            AsyncManager.me().execute(AsyncFactory.recordLogininfor(phoneNumber, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error")));
            throw new CaptchaException();
        }
        // 验证成功后删除验证码缓存
        redisCache.deleteObject(key);
    }
}

4.新建—AppUserDetailsServiceImpl类

package com.wanuw.user.controller.login;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.wanuw.common.core.domain.model.LoginUser;
import com.wanuw.common.exception.ServiceException;
import com.wanuw.common.utils.StringUtils;
import com.wanuw.common.core.domain.user.member.DbUser;
import com.wanuw.member.mapper.DbUserMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
/**
 * 手机端验证码
 *
 * @author dagang
 */
@Slf4j
@Service
public class AppUserDetailsServiceImpl implements UserDetailsService {
    @Autowired
    private DbUserMapper dbUserMapper;
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        //验证登录用户,使用的是mybatis-plus语法查询的用户数据
        //因为是手机号登录,上面形参其实是手机号码,可以改成phone,本人偷懒,未改
        DbUser dbUser = dbUserMapper.selectOne(new LambdaQueryWrapper()
                .eq(DbUser::getUserTel, username));
        if (StringUtils.isNull(dbUser)) {
            log.info("登录用户:{} 不存在.", username);
            throw new ServiceException("登录用户:" + username + " 不存在");
        } else if (dbUser.getDeleted() == 1) {
            log.info("登录用户:{} 已被删除.", username);
            throw new ServiceException("对不起,您的账号:" + username + " 已被删除");
        } else if (dbUser.getUserStatus() == 1) {
            log.info("登录用户:{} 已被停用.", username);
            throw new ServiceException("对不起,您的账号:" + username + " 已停用");
        }
        //返回UserDetails用户对象
        return createLoginUser(dbUser);
    }
    public UserDetails createLoginUser(DbUser dbUser) {
        /**
         * 参数一:第一个是用户的ID,用户后期使用SecurityUtils.getUserId()时获取上下文中存储的用户ID数据,若未设置,SecurityUtils.getUserId()获取数据位null
         * 参数二:整个用户数据传入,后面会保存在Redis中,登陆校验时会用到
         */
        return new LoginUser(dbUser.getId(),dbUser);
    }
}

5.新建—AppAuthenticationProvider

package com.wanuw.user.controller.login;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import java.util.Collection;
/**
 * 手机端验证码
 *
 * @author dagang
 */
@Slf4j
@Component
public class AppAuthenticationProvider implements AuthenticationProvider {
    @Autowired
    private AppUserDetailsServiceImpl userDetailsService;
    @SneakyThrows
    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        // 这个获取表单输入中返回的用户名,此处获取到的是手机号码
        String userName = authentication.getName();
        // 这里构建来判断用户是否存在和密码是否正确
        // 这里调用我们的自己写的AppUserDetailsServiceImpl获取用户的方法;
        UserDetails userInfo = userDetailsService.loadUserByUsername(userName);
        Collection

免责声明
1、本网站属于个人的非赢利性网站,转载的文章遵循原作者的版权声明。
2、本网站转载文章仅为传播更多信息之目的,凡在本网站出现的信息,均仅供参考。本网站将尽力确保所
提供信息的准确性及可靠性,但不保证信息的正确性和完整性,且不对因信息的不正确或遗漏导致的任何
损失或损害承担责任。
3、任何透过本网站网页而链接及得到的资讯、产品及服务,本网站概不负责,亦不负任何法律责任。
4、本网站所刊发、转载的文章,其版权均归原作者所有,如其他媒体、网站或个人从本网下载使用,请在
转载有关文章时务必尊重该文章的著作权,保留本网注明的“稿件来源”,并白负版权等法律责任。

手机扫描二维码访问

文章版权声明:除非注明,否则均为主机测评原创文章,转载或复制请以超链接形式并注明出处。

发表评论

快捷回复: 表情:
评论列表 (暂无评论,8683人围观)

还没有评论,来说两句吧...

目录[+]