第20天-认证服务,短信验证码,MD5加密,用户注册细节

1.认证中心服务搭建



1.1.创建认证中心微服务模块

创建认证中心 gmall-auth 微服务模块

第20天-认证服务,短信验证码,MD5加密,用户注册细节_第1张图片

Nacos注册中心

spring:
  application:
	name: gmall-auth
  cloud:
	nacos:
	  discovery:
		server-addr: 192.168.139.10:8848
		namespace: 36854647-e68c-409b-9233-708a2d41702c

Nacos配置中心

spring.application.name=gmall-auth
spring.cloud.nacos.config.server-addr=192.168.139.10:8848
spring.cloud.nacos.config.namespace=2ae4a6b9-995f-4e5d-9da0-75743a83ae86
spring.cloud.nacos.config.group=dev


1.2.认证中心域名映射

192.168.139.10 auth.gmall.com



1.3.网关路由配置

- id: gmall_auth_route
  uri: lb://gmall-auth
  predicates:
  - Host=auth.gmall.com


1.4.搭建登录和注册页面


1.4.1.Nginx动静分离

  • 登录页面资源

    nginx/html/static/login/

  • 注册页面资源

    nginx/html/static/reg/

第20天-认证服务,短信验证码,MD5加密,用户注册细节_第2张图片


1.4.2.页面跳转实现

  • 登录页面: login.html
  • 注册页面: reg.html

对于页面跳转,没有业务数据渲染,不需要创建handler方法来处理,推荐使用
ViewControllerRegistry 统一进行页面跳转处理。

WebConfig 配置类

package com.atguigu.gmall.auth.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * Web配置 {@link WebConfig}
 *
 * @author zhangwen
 * @email: [email protected]
 */
@Configuration
public class WebConfig implements WebMvcConfigurer {

    /**
     * 视图映射
     * @param registry
     */
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
    	registry.addViewController("/login.html").setViewName("login");
        registry.addViewController("/reg.html").setViewName("reg");
    }
}

修改 index.html search.html item.html 三个页面的登录和注册链接地址

1.4.3.登录页面


1.4.4.注册页面

第20天-认证服务,短信验证码,MD5加密,用户注册细节_第3张图片



2.个人注册



2.1.短信验证码


2.1.1.短信接口购买

阿里云市场购买并开通短信服务

https://market.aliyun.com/products/57126001/cmapi00037415.html?spm=5176.730005.productlist.d_cmapi00037415.20e73524icZQR4&innerSource=search_%E7%9F%AD%E4%BF%A1#sku=yuncode3141500001


2.1.2.短信接口开发

gmall-third-party 第三方服务中创建发送短信组件

SmsComponent

package com.atguigu.gmall.thirdparty.component;

import com.atguigu.gmall.thirdparty.util.HttpUtils;
import lombok.Data;
import org.apache.http.HttpResponse;
import org.springframework.boot.context.properties.Confĕ gurationProperties;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;

/**
 * 短信发送 {@link SmsComponent}
 *
 * @author zhangwen
 * @email: [email protected]
 */
@ConfigurationProperties(prefix = "alibaba.cloud.sms")
@Data
@Component
public class SmsComponent {
	/**
	 * 短信接口API
	 */
	private String host;
	private String path;
	/**
	 * 签名id
	 */
	private String smsSignId;
	/**
	 * 模板id
	 */
	private String templateId;
	/**
	 * appcode
	 */
	private String appcode;
	/**
	 * 验证码有效时间
	 */
	private String minute;
	/**
	 * 发送短信验证码
	 * @param mobile
	 * @param code
	 */
	public void sendSmsCode (String mobile, String code) {
		String method = "POST";
		Map<String, String> headers = new HashMap<String, String>();
		headers.put("Authorization", "APPCODE " + appcode);
		Map<String, String> querys = new HashMap<String, String>();
		querys.put("mobile", mobile);
		querys.put("param", "**code**:"+ code + ", **minute**:" + minute);
		querys.put("smsSignId", smsSignId);
		querys.put("templateId", templateId);
		Map<String, String> bodys = new HashMap<String, String>();
		try {
			HttpResponse response = HttpUtils.doPost(host, path, method, headers,querys, bodys);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

2.1.3.发送短信验证码

LoginController

/**
 * 发送短信验证码
 * @param mobile
 * @return
 */
@ResponseBody
@GetMapping("/sms/sendcode")
public R sendCode (@RequestParam("mobile") String mobile) {
	try {
		smsService.sendCode(mobile);
	} catch (RRException e) {
		return R.error(e.getCode(), e.getMsg());
	}
	return R.ok();
}

SmsServcieImpl

 /**
  * 发送短信验证码
  * @param mobile 手机号码
  */
 @Override
 public void sendCode(String mobile) {
     // 从缓存中获取验证码
     String redisCode = redisTemplate.opsForValue().get(AuthConstant.SMS_CODE_CACHE_PREFIX + mobile);
     if (!StringUtils.isEmpty(redisCode)) {
         long codeTime = Long.parseLong(redisCode.split("_")[1]);
         long currTime = System.currentTimeMillis();
         if (currTime - codeTime < 60000) {
             // 60 秒内不能再次发送验证码
             throw new AuthBizException(BizCode.SMS_CODE_EXCEPTION.getMessage(), BizCode.SMS_CODE_EXCEPTION.getCode());
         }
     }

     String code = getCheckCode();
     // 验证码校验,将验证码放入到 redis 中
     redisTemplate.opsForValue().set(AuthConstant.SMS_CODE_CACHE_PREFIX + mobile,
             code + "_" + System.currentTimeMillis(), 10, TimeUnit.MINUTES);

     // 发送短信验证码
     R r = thirdPartyFeignService.sendCode(mobile, code);
     if (r.getCode() != 0) {
         log.error("调用远程服务 gmall-third-party 发送短信验证码失败");
     }
 }
 
 /**
  * 生成随机六位数字验证码
  * @return
  */
 private String getCheckCode () {
     char[] nonceChars = new char[6];
     for (int i = 0; i < nonceChars.length; ++i) {
         nonceChars[i] = SYMBOLS.charAt(RANDOM.nextInt(SYMBOLS.length()));
     }

     return new String(nonceChars);
 }

 private static final String SYMBOLS = "0123456789";
 
 private static final Random RANDOM = new SecureRandom();


2.2.注册实现


2.2.1.校验用户名是否存在

/**
 * 校验用户是否存在
 * @param username
 * @return
 */
@ResponseBody
@GetMapping("/check/user/{username}")
public R checkUser(@PathVariable("username") String username) {
	R r = memberFeignService.checkUser(username);
	return r;
}

2.2.2.校验手机号是否存在

/**
 * 检查手机号是否存在
 * @param mobile
 * @return
 */
@ResponseBody
@GetMapping("/check/mobile/{mobile}")
public R checkMobile(@PathVariable("mobile") String mobile) {
	R r = memberFeignService.checkMobile(mobile);
	return r;
}

2.2.3.验证码校验

/**
 * 校验验证码
 * @param mobile 手机号码
 * @param code 验证码
 * @return
 */
@Override
public boolean checkCode(String mobile, String code) {
	String redisCode = redisTemplate.opsForValue().get(AuthConstant.SMS_CODE_CACHE_PREFIX + mobile);
	if (StringUtils.isEmpty(redisCode)) {
		return false;
	} else {
		if (code.equals(redisCode.split("_")[0])) {
			//删除验证码, 令牌机制
			redisTemplate.delete(AuthConstant.SMS_CODE_CACHE_PREFIX + mobile);
			return true;
		} else {
			return false;
		}
	}
}

2.2.4.注册实现

LoginController

 /**
  * 用户注册
  * @param registerVO
  * @param result
  * @param redirectAttributes
  * @return
  */
 @PostMapping("/reg")
 public String register (@Valid UserRegisterVO registerVO, BindingResult result,RedirectAttributes redirectAttributes) {
     if (result.hasErrors()) {
         // 收集校验错误消息
         Map<String, String> errors = result.getFieldErrors().stream()
                 .collect(Collectors.toMap(FieldError::getField, FieldError::getDefaultMessage));
         redirectAttributes.addFlashAttribute("errors", errors);
         // 校验出错,重定向到注册页面
         // TODO 重定向携带数据, 利用Session原理. 到了目标页面从session中取出数据, 然后删除session中的数据.
         // TODO 分布式 Session 问题
         return "redirect:http://auth.gmall.com/reg.html";
     }

     // 校验验证码
     boolean b = smsService.checkCode(registerVO.getMobile(), registerVO.getCode());
     if (!b) {
         Map<String, String> errors = new HashMap<>();
         errors.put("code", "验证码错误");
         redirectAttributes.addFlashAttribute("errors", errors);
         return "redirect:http://auth.gmall.com/reg.html";
     }

     // 调用远程会员注册
     R r = memberFeignService.register(registerVO);
     if (r.getCode() != 0) {
         Map<String, String> errors = new HashMap<>();
         errors.put("msg", r.getData("msg", new TypeReference<String>() {}));
         redirectAttributes.addFlashAttribute("errors", errors);
         return "redirect:http://auth.gmall.com/reg.html";
     }

     // 注册成功,重定向到登录页
     return "redirect:http://auth.gmall.com/login.html";
 }

2.2.5.MD5&MD5盐值加密

MD5(Message Digest algorithm 5)信息摘要算法

  • 压缩性:任意长度的数据,计算出的MD5值长度都是固定的

  • 容易计算:从原数据计算出MD5值很容易

  • 抗修改性:对原数据进行任何改动,哪怕只修改1个字节,所得到的MD5值都有很大区别

    彩虹表:暴力破解,对各种字符串进行MD5计算,存入到表中,只要有相同的字符串就会立马匹配。所以不能用原始的MD5值,需要加盐处理

  • 强抗碰撞:想找到两个不同的数据,使它们具有相同的MD5值,是非常困难的

加盐:

  • 通过生成随机数与MD5生成字符串进行组合
  • 数据库同时存储MD5值与salt值,验证正确性时使用salt进行MD5即可

网盘秒传实现:上传的文件计算MD5值,然后去数据库进行匹配,看是否有与之匹配的MD5值的文件,如果有则不用上传,对应一个文件链接即可。

/**
 * 会员注册
 * @param registerVO
 */
@Override
public void register(MemberRegisterVO registerVO) {
	//检查用户名和手机号唯一
	checkUsernameUnique(registerVO.getUsername());
	checkMobileUnique(registerVO.getMobile());
	
	//设置用户名和手机号
	MemberEntity memberEntity = new MemberEntity();
	memberEntity.setUsername(registerVO.getUsername());
	memberEntity.setMobile(registerVO.getMobile());
	
	//设置会员默认等级
	MemberLevelEntity memberLevelEntity = memberLevelDao.getDefaultLevel();
	memberEntity.setLevelId(memberLevelEntity.getId());
	
	//设置密码, 进行盐值加密处理	
	//BCryptPasswordEncoder.encode() 加密:先生成盐值,根据盐值以及明文密码生成密文
	//BCryptPasswordEncoder.matches(登录密码,数据库存储密文) 密码匹配
	BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
	String encodePassword = passwordEncoder.encode(registerVO.getPassword());
	memberEntity.setPassword(encodePassword);
	
	//保存
	save(memberEntity);
}

你可能感兴趣的:(谷粒商城,java,spring,开发语言)