认证服务之验证码注册

  • 说明
    • 需求梳理
    • 技术点
  • 发送验证码
    • 准备
    • 集成第三方短信服务
      • gulimall-api(第三方服务模块)
        • com.xfwang.gulimall.api.component.SmsComponent
        • com.xfwang.gulimall.api.controller.SmsSendController
        • application.yaml
      • 认证服务
          • ThirdPartFeignService
        • LoginController
  • 注册会员
    • 认证服务
    • 会员服务
      • MemberController
      • MemberServiceImpl

说明

需求梳理

这块功能主要是,在商城的注册页面,需要后台提供一个发送验证码校验的注册功能,主要涉及发送验证码设置时限性,验证通过会员模块注册用户

技术点

  • 验证码第三方服务
  • 接口防刷
  • MD5加密方式
  • jsr 303参数校验
  • 自定义异常类

发送验证码

准备

我们需要拥有一个阿里云账号,并去阿里云市场中搜索 三合一短信,找到其中免费试用的服务并购买。

购买之后会有一个appCode作为短信api中的秘钥

集成第三方短信服务

gulimall-api(第三方服务模块)

com.xfwang.gulimall.api.component.SmsComponent

@ConfigurationProperties(prefix = "spring.cloud.alicloud.sms")
@Component
@Data
public class SmsComponent {private String host;private String path;private String skin;private String sign;private String appcode;public void sendCode(String phone,String code) {String method = "GET";Map<String, String> headers = new HashMap<String, String>();//最后在header中的格式(中间是英文空格)为Authorization:APPCODE 83359fd73fe94948385f570e3c139105headers.put("Authorization", "APPCODE " + appcode);Map<String, String> querys = new HashMap<String, String>();querys.put("code", code);querys.put("phone", phone);querys.put("skin", skin);querys.put("sign", sign);//JDK 1.8示例代码请在这里下载:  http://code.fegine.com/Tools.ziptry {HttpResponse response = HttpUtils.doGet(host, path, method, headers, querys);//System.out.println(response.toString());如不输出json, 请打开这行代码,打印调试头部状态码。//状态码: 200 正常;400 URL无效;401 appCode错误; 403 次数用完; 500 API网管错误//获取response的bodySystem.out.println(EntityUtils.toString(response.getEntity()));} catch (Exception e) {e.printStackTrace();}}}

com.xfwang.gulimall.api.controller.SmsSendController

@RestController
@RequestMapping(value = "/sms")
public class SmsSendController {@Resourceprivate SmsComponent smsComponent;/*** 提供给别的服务进行调用* @param phone* @param code* @return*/@GetMapping(value = "/sendCode")public R sendCode(@RequestParam("phone") String phone, @RequestParam("code") String code) {//发送验证码smsComponent.sendCode(phone,code);return R.ok();}}

application.yaml

spring:cloud:nacos:discovery:server-addr: xxxxxxxxx:8848alicloud:access-key: LTAI5t9twxU2F1CjifPTDmXo2xsecret-key: LsWy1PPMSBl511KijHLndKuG0GyvHS8oss:endpoint: oss-cn-shanghai.aliyuncs.combucket: gulimall-xfwangaccess-id:sms:host: https://fesms.market.alicloudapi.compath: /sms/sign: 1skin: 1appcode: 20c3ac75d675426a8e9cb4d0ea8fd15bf

认证服务

我们需要创建一个认证服务 gulimall-auth-server
具体细节不多赘述
需要注册到nacos,去远程调用其他服务

ThirdPartFeignService
@FeignClient("gulimall-api")
public interface ThirdPartFeignService {@GetMapping(value = "/sms/sendCode")R sendCode(@RequestParam("phone") String phone, @RequestParam("code") String code);}

LoginController

/*** 登录注册接口*/
@Controller
public class LoginController {private static Logger log = LoggerFactory.getLogger(LoginController.class);@Autowiredprivate ThirdPartFeignService thirdPartFeignService;@Autowiredprivate StringRedisTemplate stringRedisTemplate;@ResponseBody@GetMapping(value = "/sms/sendCode")public R sendCode(@RequestParam("phone") String phone) {//1、接口防刷String redisCode = stringRedisTemplate.opsForValue().get(AuthServerConstant.SMS_CODE_CACHE_PREFIX + phone);if (!StringUtils.isEmpty(redisCode)) {//活动存入redis的时间,用当前时间减去存入redis的时间,判断用户手机号是否在60s内发送验证码long currentTime = Long.parseLong(redisCode.split("_")[1]);if (System.currentTimeMillis() - currentTime < 60000) {//60s内不能再发return R.error(BizCodeEnume.SMS_CODE_EXCEPTION.getCode(), BizCodeEnume.SMS_CODE_EXCEPTION.getMsg());}}//2、验证码的再次效验 redis.存key-phone,value-codeint code = (int) ((Math.random() * 9 + 1) * 100000);String codeNum = String.valueOf(code);String redisStorage = codeNum + "_" + System.currentTimeMillis();//存入redis,防止同一个手机号在60秒内再次发送验证码stringRedisTemplate.opsForValue().set(AuthServerConstant.SMS_CODE_CACHE_PREFIX + phone,redisStorage, 1, TimeUnit.MINUTES);try {thirdPartFeignService.sendCode(phone, codeNum);} catch (Exception e) {log.error("调用发送验证码报错!");}return R.ok();}
}

接口防刷
主要是通过是在redis存发送成功的验证码,并加上指定时间的过期时间。
在没达到过期时间之前不允许在调用第三方的发送验证码的服务,并返回报错信息

注册会员

在验证码发送成功之后,需要对注册信息进行保存

认证服务

/*** 登录注册接口*/
@Controller
public class LoginController {private static Logger log = LoggerFactory.getLogger(LoginController.class);@Autowiredprivate ThirdPartFeignService thirdPartFeignService;@Autowiredprivate MemberFeignService memberFeignService;@Autowiredprivate StringRedisTemplate stringRedisTemplate;
/*** TODO: 重定向携带数据:利用session原理,将数据放在session中。* TODO:只要跳转到下一个页面取出这个数据以后,session里面的数据就会删掉* TODO:分布下session问题* RedirectAttributes:重定向也可以保留数据,不会丢失* 用户注册** @return*/@ResponseBody@PostMapping(value = "/register")public R register(@Valid UserRegisterVo vos, BindingResult result) {//如果有错误回到注册页面if (result.hasErrors()) {Map<String, String> errors = result.getFieldErrors().stream().collect(Collectors.toMap(FieldError::getField, FieldError::getDefaultMessage));//效验出错回到注册页面return R.error().put("errors", errors).put("url", "http://auth.gulimall.com/reg.html");}//1、效验验证码String code = vos.getCode();//获取存入Redis里的验证码String redisCode = stringRedisTemplate.opsForValue().get(AuthServerConstant.SMS_CODE_CACHE_PREFIX + vos.getPhone());if (!StringUtils.isEmpty(redisCode)) {//截取字符串if (code.equals(redisCode.split("_")[0])) {//删除验证码;令牌机制stringRedisTemplate.delete(AuthServerConstant.SMS_CODE_CACHE_PREFIX + vos.getPhone());//验证码通过,真正注册,调用远程服务进行注册R register = memberFeignService.register(vos);if (register.getCode() == 0) {//成功return R.ok().put("url", "http://auth.gulimall.com/login.html");} else {//失败Map<String, String> errors = new HashMap<>();errors.put("msg", register.getData("msg", new TypeReference<String>() {}));return R.error().put("errors", errors).put("url", "http://auth.gulimall.com/reg.html");}} else {//效验出错回到注册页面Map<String, String> errors = new HashMap<>();errors.put("code", "验证码错误");return R.error().put("errors", errors).put("url", "http://auth.gulimall.com/reg.html");}} else {//效验出错回到注册页面Map<String, String> errors = new HashMap<>();errors.put("code", "验证码错误");return R.error().put("errors", errors).put("url", "http://auth.gulimall.com/reg.html");}}}

会员服务

MemberController

@RestController
@RequestMapping("member/member")
public class MemberController {@Autowiredprivate MemberService memberService;/*** 注册用户* @param vo* @return*/@PostMapping(value = "/register")public R register(@RequestBody MemberUserRegisterVo vo) {try {memberService.register(vo);} catch (PhoneException e) {return R.error(BizCodeEnume.PHONE_EXIST_EXCEPTION.getCode(),BizCodeEnume.PHONE_EXIST_EXCEPTION.getMsg());} catch (UsernameException e) {return R.error(BizCodeEnume.USER_EXIST_EXCEPTION.getCode(),BizCodeEnume.USER_EXIST_EXCEPTION.getMsg());}return R.ok();}}

MemberServiceImpl

@Service("memberService")
public class MemberServiceImpl extends ServiceImpl<MemberDao, MemberEntity> implements MemberService {@Resourceprivate MemberLevelDao memberLevelDao;@Overridepublic void register(MemberUserRegisterVo vo) {MemberEntity memberEntity = new MemberEntity();//设置默认等级MemberLevelEntity levelEntity = memberLevelDao.getDefaultLevel();memberEntity.setLevelId(levelEntity.getId());//设置其它的默认信息//检查用户名和手机号是否唯一。感知异常,异常机制checkPhoneUnique(vo.getPhone());checkUserNameUnique(vo.getUserName());memberEntity.setNickname(vo.getUserName());memberEntity.setUsername(vo.getUserName());//密码进行MD5加密BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();String encode = bCryptPasswordEncoder.encode(vo.getPassword());memberEntity.setPassword(encode);memberEntity.setMobile(vo.getPhone());memberEntity.setGender(0);memberEntity.setCreateTime(new Date());//保存数据this.baseMapper.insert(memberEntity);}@Overridepublic void checkPhoneUnique(String phone) throws PhoneException {Integer phoneCount = this.baseMapper.selectCount(new QueryWrapper<MemberEntity>().eq("mobile", phone));if (phoneCount > 0) {throw new PhoneException();}}@Overridepublic void checkUserNameUnique(String userName) throws UsernameException {Integer usernameCount = this.baseMapper.selectCount(new QueryWrapper<MemberEntity>().eq("username", userName));if (usernameCount > 0) {throw new UsernameException();}}}

jsr 303
在创建vo对象属性使用校验注解
在接口对象接受时使用@Valid 可以触发校验,并通过BindingResult返回校验信息

自定义异常

public class PhoneException extends RuntimeException {public PhoneException() {super("存在相同的手机号");}
}

就是集成了runtimeException

MD5加密方式
常见的加密方式,但目前MD5的加密方式容易被暴力破解,因此目前spring最近的技术中对MD5加密采取加盐值的方式来避免通过彩虹表的暴力破解方式

  BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();String encode = bCryptPasswordEncoder.encode(vo.getPassword());

MD5本身的加密方式是不可逆的加密方式,但是有唯一性,既针对某一密码的加密生成的字符串都是唯一的,因此常常有人对目前常见的密码或者不常见的密码进行加密后保存至数据库,在通过比对的方式推断出MD5加密的字符串的原始密码。

解决方案
加MD5加密时对原始密码进行处理后(算法),再去加密,这样得到原始密码就是随机变化的,再去MD5加密就会安全很多,这种方式我们称为加盐操作。

谷粒商城高级篇(39)——认证服务之验证码注册相关推荐

  1. 谷粒商城高级篇上(未完待续)

    谷粒商城高级篇(上)保姆级整理 之前整理了基础篇,Typora提示将近20000词,谷粒商城基础篇保姆级整理 在学高级篇的时候,不知不觉又整理了两万多词,做了一阶段,先发出来,剩余部分整理好了再发.自 ...

  2. 谷粒商城高级篇笔记1

    这里写自定义目录标题 0.ElasticSearch 1.Nginx配置域名问题 01.Nginx(反向代理) 配置 02.Nginx(负载均衡)+ 网关 配置 03.Nginx动静分离 2.JMet ...

  3. 【谷粒商城高级篇】商品服务 商品上架

    谷粒商城笔记合集 分布式基础篇 分布式高级篇 高可用集群篇 ===简介&环境搭建=== ===Elasticsearch=== 项目简介与分布式概念(第一.二章) Elasticsearch: ...

  4. 谷粒商城-高级篇-aiueo

    105 初步检索 105.1 _cat GET /_cat/nodes : 查看所有节点 GET /_cat/health : 查看es健康状况 GET /_cat/master : 查看主节点 GE ...

  5. 【谷粒商城高级篇】商城业务:商品检索

    谷粒商城笔记合集 分布式基础篇 分布式高级篇 高可用集群篇 ===简介&环境搭建=== ===Elasticsearch=== 项目简介与分布式概念(第一.二章) Elasticsearch: ...

  6. 【谷粒商城高级篇】Elasticsearch:全文检索

    谷粒商城笔记合集 分布式基础篇 分布式高级篇 高可用集群篇 ===简介&环境搭建=== ===Elasticsearch=== 项目简介与分布式概念(第一.二章) Elasticsearch: ...

  7. 谷粒商城高级篇资料_一文搞定剑指offer面试题【分文别类篇】

    点击上方"蓝字",关注了解更多 数组: 面试题3:数组中重复的数字 面试题4:二维数组中的查找 面试题21:调整数组顺序使奇数位于偶数前面 面试题39:数组中出现次数超过一半的数字 ...

  8. 谷粒商城高级篇爬坑笔记--错误异常信息乱码问题

    由于高级篇开发相对较多,配置的内容较少,本人编写过程中没有遇到特别大的问题,唯一的问题就是消息乱码: 项目定义了如下的异常类: UNKNOW_EXCEPTION(10000,"系统未知异常& ...

  9. 谷粒商城高级篇(38)——异步编排之商品详情查询

    异步编排之商品详情查询 异步编排 CompletableFuture介绍 创建异步对象 计算完成时回调方法 handle 方法 线程串行化方法 两任务组合 全部完成 一个完成即可 多任务组合 业务描述 ...

  10. 谷粒商城-高级篇-Day11-商城业务

    文章目录 整合thymeleaf渲染页面 页面修改不重启服务器实时更新 渲染二三级数据 nginx-搭建域名访问环境一 nginx-搭建域名访问环境二 整合thymeleaf渲染页面 将index放到 ...

最新文章

  1. java striptrailingzeros_java – 为什么不BigDecimal.stripTrailingZeros()总是删除所有尾随零?...
  2. Rk3288运行linux,查看“Firefly-rk3288 build linux”的源代码
  3. Asp.net mvc中使用配置Unity
  4. Taro+react开发(67):数组中push返回的是长度
  5. SQL语句关于数据库安全性
  6. 浅谈《原神》中的图形渲染技术
  7. 计算机实战项目、课程设计、毕业设计之[含论文+源码等]微信小程序校园论坛|商城|电商系统+后台管理系统|前后分离VUE[包运行
  8. 智能优化算法:灰狼优化算法-附代码
  9. Windows Server 2019系统Windows defender误删文件的解决办法
  10. 2010年北京大学软件与微电子学院毕业生就业去向(官方不完全统计)
  11. [测试开发面试]zyb面试题总结
  12. python正则匹配中文/英文/数字/其它字符
  13. 看脸的世界:牙齿整齐找工作更容易
  14. RDS MySQL和Mongodb 物理备份文件.xb恢复到自建数据库
  15. 视频教程-软件测试入门视频教程-软件测试
  16. 单片机入门教程:第七章 1602LCD液晶显示模块
  17. 做vr需要什么技术? 常用的vr技术板块
  18. LDA-线性判别分析(一)预备知识
  19. 微信统一支付详解,坑太多,不得不写
  20. 阿里云服务器上安装Mysql 服务

热门文章

  1. 【RSLogix5000】—(1.1)—厂房ControlLogix系统介绍(硬件介绍)——原理
  2. java实现单点登录
  3. 卡尔曼滤波与扩展卡尔曼滤波(EKF)
  4. html实现tab 左右滑动
  5. 计算机输入什么指令关机,电脑关机命令是什么 电脑关机命令详解
  6. 使用Clustal进行多序列比对
  7. js如何获取一个object的第一个数据
  8. png图片怎么转换成jpg?png转jpg批量
  9. 数据库——T-SQL方式创建数据库
  10. pilz pnoz s4说明书_如何使用Pilz的安全继电器PNOZ S4?