当前位置:   article > 正文

22.认证服务-auth-server-注册功能

authserver

环境搭建

选择模块thymeleaf,web,openFeign,lombok,spring-boot-devTools

加入common模块(有注册中心之类的很多依赖)排除jdbc依赖

  1. <dependency>
  2. <groupId>com.wuyimin.gulimall</groupId>
  3. <artifactId>gulimall-common</artifactId>
  4. <version>0.0.1-SNAPSHOT</version>
  5. <exclusions>
  6. <exclusion>
  7. <groupId>com.baomidou</groupId>
  8. <artifactId>mybatis-plus-boot-starter</artifactId>
  9. </exclusion>
  10. </exclusions>
  11. </dependency>

配置yml文件

  1. spring: #配置nacos
  2. cloud:
  3. nacos:
  4. discovery:
  5. server-addr: 127.0.0.1:8848
  6. application:
  7. name: gulimall-auth-server
  8. server:
  9. port: 20000

 放入登录页面和注册页面,并且把静态资源转给nginx,login后改名为index

 网关配置

 添加域名

 登录准备

以前我们需要写一个空方法转发请求到登录注册页面

  1. @GetMapping("/login.html")
  2. public String loginPage(){
  3. return "login";
  4. }
  5. @GetMapping("/reg.html")
  6. public String regPage(){
  7. return "reg";
  8. }

现在使用映射配置类

  1. @Configuration
  2. public class MyWebConfig implements WebMvcConfigurer {
  3. //视图映射
  4. @Override
  5. public void addViewControllers(ViewControllerRegistry registry) {
  6. registry.addViewController("/login.html").setViewName("login");
  7. registry.addViewController("/reg.html").setViewName("reg");
  8. }
  9. }

整合短信验证码

https://market.aliyun.com/products/?keywords=短信验证码

去买一个免费的,复制一下测试代码,然后把appid换成自己的

正常来说是要用@ConfigurationProperties配置到配置文件里的,这里直接抽取不做配置

  1. @Component
  2. @Data
  3. public class SmsComponent {
  4. private String host;
  5. private String path;
  6. private String templateId="908e94ccf08b4476ba6c876d13f084ad";
  7. private String smsSignId="2e65b1bb3d054466b82f0c9d125465e2";
  8. private String appCode="78442b1006ae490da40cedda6826c7b5";
  9. public void sendSmsCode(String phone,String code){
  10. String host = "https://gyytz.market.alicloudapi.com";
  11. String path = "/sms/smsSend";
  12. String method = "POST";
  13. String appcode = appCode;
  14. Map<String, String> headers = new HashMap<String, String>();
  15. //最后在header中的格式(中间是英文空格)为Authorization:APPCODE 83359fd73fe94948385f570e3c139105
  16. headers.put("Authorization", "APPCODE " + appcode);
  17. Map<String, String> querys = new HashMap<String, String>();
  18. querys.put("mobile", phone);
  19. querys.put("param", "**code**:"+code+"**minute**:5");
  20. querys.put("smsSignId", smsSignId);
  21. querys.put("templateId", templateId);
  22. Map<String, String> bodys = new HashMap<String, String>();
  23. try {
  24. /**
  25. * HttpUtils请从
  26. * https://github.com/aliyun/api-gateway-demo-sign-java/blob/master/src/main/java/com/aliyun/api/gateway/demo/util/HttpUtils.java
  27. * 下载
  28. */
  29. HttpResponse response = HttpUtils.doPost(host, path, method, headers, querys, bodys);
  30. System.out.println(response.toString());
  31. //获取response的body
  32. //System.out.println(EntityUtils.toString(response.getEntity()));
  33. } catch (Exception e) {
  34. e.printStackTrace();
  35. }
  36. }
  37. }

第三方模块远程被调用的类

  1. @RestController
  2. @RequestMapping("/sms")
  3. public class SmsSendController {
  4. @Autowired
  5. SmsComponent smsComponent;
  6. //提供给别的服务进行调用
  7. @GetMapping("/sendcode")
  8. public R sendCode(@RequestParam("phone") String phone, @RequestParam("code")String code){
  9. smsComponent.sendSmsCode(phone,code);
  10. return R.ok();
  11. }
  12. }

auth模块远程调用接口:

  1. @FeignClient("gulimall-third-party")
  2. public interface ThirdPartyFeignService {
  3. @GetMapping("/sms/sendcode")
  4. public R sendCode(@RequestParam("phone") String phone, @RequestParam("code")String code);
  5. }

细化验证码:

1.接口防刷(每次只要刷新后就可以重发验证码)

2.验证码校验--存入redis中

接口防刷

redis依赖导入

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-data-redis</artifactId>
  4. </dependency>

配置redis的地址以及具体实现逻辑

  1. spring:
  2. redis:
  3. host: 192.168.116.128
  1. @Slf4j
  2. @Controller
  3. public class LoginController {
  4. @Autowired
  5. ThirdPartyFeignService thirdPartyFeignService;
  6. @Autowired
  7. StringRedisTemplate redisTemplate;
  8. @ResponseBody
  9. @GetMapping("/sms/sendcode")
  10. public R sendCode(@RequestParam("phone") String phone){
  11. //TODO 接口防刷
  12. String redisCode = redisTemplate.opsForValue().get(AuthServerConstant.SMS_CODE_CAHE_PREFIX + phone);
  13. if(!StringUtils.isEmpty(redisCode)){
  14. long l=Long.parseLong(redisCode.split("_")[1]);//拿到时间
  15. if(System.currentTimeMillis()-l<60000){
  16. //60秒内不能再发
  17. return R.error(BizCodeEnum.SMS_CODE_EXCEPTION.getCode(),BizCodeEnum.SMS_CODE_EXCEPTION.getMsg());
  18. }
  19. }
  20. String code = UUID.randomUUID().toString().substring(0, 5)+"_"+System.currentTimeMillis();//加上系统时间
  21. //验证码的再次校验,存入redis key-手机号 value-code
  22. redisTemplate.opsForValue().set(AuthServerConstant.SMS_CODE_CAHE_PREFIX+phone,code,10, TimeUnit.MINUTES);
  23. try {
  24. thirdPartyFeignService.sendCode(phone,code);//第三方服务
  25. } catch (Exception e) {
  26. log.warn("远程调用不知名错误 [无需解决]");
  27. }
  28. return R.ok();
  29. }
  30. }

 注册页环境

踩坑

校验注解不生效是因为少了这个依赖

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-validation</artifactId>
  4. </dependency>

如果在controller类上添加了@Validated注解,错误会以500的响应模式出来

校验实体类

  1. @Data
  2. public class UserRegistVo {
  3. @NotEmpty(message = "用户名必须提交")
  4. @Length(min = 6,max = 18,message = "长度必须在6-18")
  5. private String userName;
  6. @NotEmpty(message = "密码必须提交")
  7. @Length(min = 6,max = 18,message = "长度必须在6-18")
  8. private String password;
  9. //第一个数组必须是1,第二个数字在3-9剩下9个数字在0-9,一共11位
  10. @NotEmpty(message = "手机号必须填写")
  11. @Pattern(regexp = "^[1]([3-9])[0-9]{9}$",message = "手机号格式不正确")
  12. private String phone;
  13. @NotEmpty(message = "验证码必须填写")
  14. private String code;
  15. }

  1. //TODO 重定向携带数据,利用session原理,将数据放在session中,只要跳到下一个页面,取出这个数据以后session里的数据就会删掉
  2. //TODO 分布式下的session问题
  3. //post请求不支持--我们配置的路径映射默认都是get方式才能访问,所以不能直接return“forward:/reg.html”,这样会直接把post请求发给页面
  4. @PostMapping("/regist")
  5. public String register(@Valid UserRegistVo vo, BindingResult result, RedirectAttributes attributes){//第三个参数是专门用来重定向携带数据的
  6. //注册成功会到登录页
  7. //1.判断校验是否通过
  8. Map<String, String> errors = new HashMap<>();
  9. if (result.hasErrors()){
  10. //1.1 如果校验不通过,则封装校验结果
  11. result.getFieldErrors().forEach(item->{
  12. // 获取错误的属性名和错误信息
  13. errors.put(item.getField(), item.getDefaultMessage());
  14. //1.2 将错误信息封装到session中
  15. attributes.addFlashAttribute("errors", errors);
  16. });
  17. //校验出错,重定向到注册页
  18. return "redirect:http://auth.gulimall.com/reg.html";//防止刷新的时候表单重复提交,采用重定向
  19. }else{
  20. return "redirect:http://auth.gulimall.com/login.html";
  21. }
  22. }
  23. }

异常机制

远程的Member服务

  1. @PostMapping("/regist")
  2. public R regist(@RequestBody MemberRegisterVo vo){//远程服务必须要获得json对象
  3. try{
  4. memberService.regist(vo);
  5. }catch (Exception e){
  6. //对于不同的异常有不同的处理方式
  7. }
  8. return R.ok();
  9. }

查出默认等级的方法

  1. <select id="getDefaultLevel" resultType="com.wuyimin.gulimall.member.entity.MemberLevelEntity">
  2. select * from ums_member_level where default_status=1
  3. </select>

自定义异常,用于判断手机号与用户名是否已经存在

  1. public class PhoneExistException extends RuntimeException {
  2. public PhoneExistException() {
  3. super("手机号已经存在");
  4. }
  5. }

检查手机和用户名的函数

  1. @Override
  2. public void checkPhone(String phone) throws PhoneExistException {
  3. Integer count = baseMapper.selectCount(new QueryWrapper<MemberEntity>().eq("mobile", phone));
  4. if (count > 0) {
  5. throw new PhoneExistException();
  6. }
  7. }
  8. @Override
  9. public void checkUserName(String userName) throws UsernameExistException {
  10. Integer count = baseMapper.selectCount(new QueryWrapper<MemberEntity>().eq("username", userName));
  11. if (count > 0) {
  12. throw new UsernameExistException();
  13. }
  14. }

现阶段的regist方法

  1. @Override
  2. public void regist(MemberRegisterVo vo) {
  3. MemberEntity memberEntity = new MemberEntity();
  4. MemberLevelEntity memberLevelEntity = memberLevelDao.getDefaultLevel();
  5. memberEntity.setLevelId(memberLevelEntity.getId());//默认等级为1普通会员
  6. //设置其他的默认信息
  7. //检查用户名和手机号的唯一性 为了让controller能感知异常,我们使用异常机制
  8. checkPhone(vo.getPhone());
  9. checkUserName(vo.getUserName());
  10. //设置用户名和手机
  11. memberEntity.setMobile(vo.getPhone());
  12. memberEntity.setUsername(vo.getUserName());
  13. //设置密码(密码需要进行加密存储)
  14. baseMapper.insert(memberEntity);
  15. }

MD5,盐值和BCrypt

MD5:信息摘要算法,是不可逆的算法--》但是利用其抗修改性,使用彩虹表可以暴力破解,所以不能直接存储

加盐:通过生产随机数和MD5字符串进行组合,数据库同时存储MD5值和盐值,验证正确的时候使用salt进行MD5即可

  1. @Override
  2. public void regist(MemberRegisterVo vo) {
  3. MemberEntity memberEntity = new MemberEntity();
  4. MemberLevelEntity memberLevelEntity = memberLevelDao.getDefaultLevel();
  5. memberEntity.setLevelId(memberLevelEntity.getId());
  6. checkPhone(vo.getPhone());
  7. checkUserName(vo.getUserName());
  8. memberEntity.setMobile(vo.getPhone());
  9. memberEntity.setUsername(vo.getUserName());
  10. //设置密码(密码需要进行加密存储)
  11. BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
  12. String encode = passwordEncoder.encode(vo.getPassword());
  13. memberEntity.setPassword(encode);
  14. //其他的默认信息。。
  15. //保存
  16. baseMapper.insert(memberEntity);
  17. }

远程注册功能完善

  1. @PostMapping("/regist")
  2. public R regist(@RequestBody MemberRegisterVo vo){//远程服务必须要获得json对象
  3. try{
  4. memberService.regist(vo);
  5. }catch (PhoneExistException e){
  6. return R.error(BizCodeEnum.PHONE_EXIST_EXCEPTION.getCode(),BizCodeEnum.PHONE_EXIST_EXCEPTION.getMsg());
  7. }catch (UsernameExistException e){
  8. return R.error(BizCodeEnum.USER_EXIST_EXCEPTION.getCode(),BizCodeEnum.USER_EXIST_EXCEPTION.getMsg());
  9. }
  10. return R.ok();
  11. }

auth模块远程调用member模块的接口

  1. @FeignClient("gulimall-member")
  2. public interface MemberFeignService {
  3. @PostMapping("/member/member/regist")
  4. R regist(@RequestBody MemberRegisterVo vo);
  5. }

  1. //TODO 重定向携带数据,利用session原理,将数据放在session中,只要跳到下一个页面,取出这个数据以后session里的数据就会删掉
  2. //TODO 分布式下的session问题
  3. //post请求不支持--我们配置的路径映射默认都是get方式才能访问,所以不能直接return“forward:/reg.html”,这样会直接把post请求发给页面
  4. @PostMapping("/regist")
  5. public String register(@Valid UserRegistVo vo, BindingResult result, RedirectAttributes attributes){//第三个参数是专门用来重定向携带数据的
  6. //注册成功会到登录页
  7. //1.判断校验是否通过
  8. Map<String, String> errors = new HashMap<>();
  9. if (result.hasErrors()){
  10. //1.1 如果校验不通过,则封装校验结果
  11. result.getFieldErrors().forEach(item->{
  12. // 获取错误的属性名和错误信息
  13. errors.put(item.getField(), item.getDefaultMessage());
  14. //1.2 将错误信息封装到session中
  15. attributes.addFlashAttribute("errors", errors);
  16. });
  17. //校验出错,重定向到注册页
  18. return "redirect:http://auth.gulimall.com/reg.html";//防止刷新的时候表单重复提交,采用重定向
  19. }else{
  20. //真正的注册
  21. //1.校验验证码
  22. String code=vo.getCode();
  23. String redisCode = redisTemplate.opsForValue().get(AuthServerConstant.SMS_CODE_CAHE_PREFIX + vo.getPhone());
  24. if(!StringUtils.isEmpty(redisCode)){
  25. if(code.equals(redisCode.split("_")[0])){
  26. //删除验证码
  27. redisTemplate.delete(AuthServerConstant.SMS_CODE_CAHE_PREFIX + vo.getPhone());
  28. //验证码通过,调用远程接口进行服务注册
  29. R r = memberFeignService.regist(vo);
  30. if(r.getCode()==0){
  31. //成功
  32. return "redirect:http://auth.gulimall.com/login.html";
  33. }else{
  34. //调用失败,返回注册页并显示错误信息
  35. String msg = (String) r.get("msg");
  36. errors.put("msg", msg);
  37. attributes.addFlashAttribute("errors", errors);
  38. log.error("远程调用会员服务失败");
  39. return "redirect:http://auth.gulimall.com/reg.html";
  40. }
  41. }else{
  42. //验证码没有匹配
  43. errors.put("code","验证码错误");
  44. attributes.addFlashAttribute("errors",errors);
  45. return "redirect:http://auth.gulimall.com/reg.html";
  46. }
  47. }else{
  48. //没有验证码
  49. errors.put("code","验证码错误");
  50. attributes.addFlashAttribute("errors",errors);
  51. return "redirect:http://auth.gulimall.com/reg.html";
  52. }
  53. }
  54. }

修改验证码的bug,存进redis的是带uuid的但是传递进service服务的参数不该带uuid

  1. @ResponseBody
  2. @GetMapping("/sms/sendcode")
  3. public R sendCode(@RequestParam("phone") String phone){
  4. //TODO 接口防刷
  5. String redisCode = redisTemplate.opsForValue().get(AuthServerConstant.SMS_CODE_CAHE_PREFIX + phone);
  6. if(!StringUtils.isEmpty(redisCode)){
  7. long l=Long.parseLong(redisCode.split("_")[1]);//拿到时间
  8. if(System.currentTimeMillis()-l<60000){
  9. //60秒内不能再发
  10. return R.error(BizCodeEnum.SMS_CODE_EXCEPTION.getCode(),BizCodeEnum.SMS_CODE_EXCEPTION.getMsg());
  11. }
  12. }
  13. String code = UUID.randomUUID().toString().substring(0, 5);
  14. String saveInRedis = code + "_" + System.currentTimeMillis();//加上系统时间
  15. //验证码的再次校验,存入redis key-手机号 value-code
  16. redisTemplate.opsForValue().set(AuthServerConstant.SMS_CODE_CAHE_PREFIX+phone,saveInRedis,10, TimeUnit.MINUTES);
  17. try {
  18. thirdPartyFeignService.sendCode(phone,code);//第三方服务
  19. } catch (Exception e) {
  20. log.warn("远程调用不知名错误 [无需解决]");
  21. }
  22. return R.ok();
  23. }

至此所有注册功能已经完成

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/weixin_40725706/article/detail/374999
推荐阅读
相关标签
  

闽ICP备14008679号