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