Springboot-04:Panic Buying Project MD5加密登录+JSR303校验

MD5加密登录


如果不做任何处理:那么明文密码就会在网络上进行传输,假如说恶意用户取得这个数据包,那么就可以得到这个密码,这不安全。

为什么做两次MD5?

  1. 用户端:PASS=MD5   (明文+固定Salt)
  2. 服务端:PASS=MD5(用户输入+随机Salt)

第一次 (在前端加密,客户端):密码加密是(明文密码+固定盐值)生成md5用于传输,目的由于http是明文传输,当输入密码若直接发送服务端验证,此时被截取将直接获取到明文密码,获取用户信息。加盐值是为了混淆密码,原则就是明文密码不能在网络上传输。
第二次:服务端接收到已经计算过依次MD5的密码后,我们并不是直接存至数据库里面,而是生成一个随机的salt,跟用户输入的密码一起拼装,再做一次MD5,然后再把最终密码存在数据库里面。
第二次的目的:
防止数据库被入侵,被人通过彩虹表反查出密码。所以服务端接受到后,也不是直接写入到数据库,而是生成一个随机盐(salt),再进行一次MD5后存入数据库。

在pom文件里面引入两个MD5的依赖

    <dependency>
    	<groupId>commons-codec</groupId>
    	<artifactId>commons-codec</artifactId>
    	<version>1.9</version>
    </dependency>
    <dependency>
    	<groupId>org.apache.commons</groupId>
    	<artifactId>commons-lang3</artifactId>
    	<version>3.6</version>
    </dependency>

编写MD5Util工具类用于生成MD5密码:

public class MD5Util {
	public static String md5(String src) {
		return DigestUtils.md5Hex(src);
	}
	//客户端固定的salt,跟用户的密码做一个拼装
	private static final String salt="1a2b3c4d";
	
	public static String inputPassToFormPass(String inputPass) {
		String str=""+salt.charAt(0)+salt.charAt(2)+inputPass+salt.charAt(5)+salt.charAt(4);
		System.out.println(md5(str));
		return md5(str); 			//char类型计算会自动转换为int类型
	}
	//二次MD5
	public static String formPassToDBPass(String formPass,String salt) {//随机的salt
		String str=""+salt.charAt(0)+salt.charAt(2)+formPass+salt.charAt(5)+salt.charAt(4);
		return md5(str); 			 
	}
	//数据库md5,使用数据库随机salt
	public static String inputPassToDbPass(String input,String saltDB) {
		String formPass=inputPassToFormPass(input);
		System.out.println(formPass);
		String dbPass=formPassToDBPass(formPass,saltDB);
		return dbPass;
	}
	
	public static void main(String[] args) {
		String pass = "123456";
        String salt = "1a2b3c4d";
        System.out.println(inputPassToDBPass(pass,salt));
	}
}

数据库里面存的是做了两次MD5的用户密码与其对应的salt值
在这里插入图片描述
现在我们登录的时候,要去取得数据库里面对应用户的密码和salt值,然后后台接收了前端做了一次MD5的密码formPass,然后将这个formPass去和数据库里面的salt一起再做一次MD5,然后检测是否与数据库里面存的那个密码一致。

问题引用:

第二次MD5所用的随机saltDB为什么要保存在数据库里?
当数据库被侵入,做MD5之后的密码和saltDB一起被盗的话,用户密码不就泄露了吗?
但是如果不保存这个随机的saltDB,下次用户登录的时候不就没法和数据库保存的密码进行校验了吗?

问题思考:
实际上做MD5也不是绝对安全的,但是我们可以使得破解的难度指数级增长md5是不可逆的,不能反向解密的,网上所谓的“解密”都是把“加密”结果存储到数据库再比对的只能暴力破解,即有一个字典,从字典中读取一条记录,将密码用加salt盐值做MD5来对比数据库里面的值是否相等。

因为好事者收集常用的密码,然后对他们执行 MD5,然后做成一个数据量非常庞大的数据字典,然后对泄露的数据库中的密码进行对比,如果你的原始密码很不幸的被包含在这个数据字典中,那么花不了多长时间就能把你的原始密码匹配出来,这个数据字典很容易收集,假设有600w个密码。坏人们可以利用他们数据字典中的密码,加上我们泄露数据库中的 Salt,然后散列,然后再匹配。

但是由于我们的 Salt 是随机产生的,每条数据都要加上 Salt 后再散列,假如我们的用户数据表中有 30w 条数据,数据字典中有 600w 条数据,坏人们如果想要完全覆盖的话,他们就必须加上 Salt 后再散列的数据字典数据量就应该是 300000* 6000000 = 1800000000000,所以说干坏事的成本太高了吧。但是如果只是想破解某个用户的密码的话,只需为这 600w 条数据加上 Salt,然后散列匹配。可见 Salt 虽然大大提高了安全系数,但也并非绝对安全

实际项目中,Salt 不一定要加在最前面或最后面,也可以插在中间,也可以分开插入,也可以倒序,程序设计时可以灵活调整,都可以使破解的难度指数级增长。

步骤:

1.创建秒杀User的domain类

@lombok
public class MiaoshaUser {
	private Long id;
	private String nickname;
	private String pwd;
	private String salt;
	private String head;
	private Date registerDate;
	private Date lastLoginDate;
	private Integer loginCount;
}

2.新建MiaoshaUserDao

@Mapper
	public interface MiaoshaUserDao {
	@Select("select * from miaosha_user where id=#{id}")  //这里#{id}通过后面参数来为其赋值
	public MiaoshaUser getById(@Param("id")long id);    //绑定
	
	//绑定在对象上面了----@Param("id")long id,@Param("pwd")long pwd 效果一致
	@Update("update miaosha_user set pwd=#{pwd} where id=#{id}")
	public void update(MiaoshaUser toupdateuser);	
	//public boolean update(@Param("id")long id);    //绑定	
}

3.新建MiaoshaUserService:

在service层,协调redis和mysql的存取

@Service
	public class MiaoshaUserService {
	public static final String COOKIE1_NAME_TOKEN="token";
	
	@Autowired
	MiaoshaUserDao miaoshaUserDao;
	@Autowired
	RedisService redisService;
	/**
	 * 根据id取得对象,先去缓存中取
	 * @param id
	 * @return
	 */
	public MiaoshaUser getById(long id) {
		//1.取缓存	---先根据id来取得缓存
		MiaoshaUser user=redisService.get(MiaoshaUserKey.getById, ""+id, MiaoshaUser.class);
		//能再缓存中拿到
		if(user!=null) {
			return user;
		}
		//2.缓存中拿不到,那么就去取数据库
		user=miaoshaUserDao.getById(id);
		//3.设置缓存
		if(user!=null) {
			redisService.set(MiaoshaUserKey.getById, ""+id, user);
		}
		return user;
	}
	}

4.新建LoginController

判断用户密码是否匹配

@RequestMapping("/login")
	@Controller
	public class LoginController{
	@Autowired
	UserService userService;
	@Autowired
	RedisService redisService;
	@Autowired
	MiaoshaUserService miaoshaUserService;	
	//slf4j
	private static Logger log=(Logger) LoggerFactory.getLogger(Logger.class);	
	@RequestMapping("/to_login")
	public String toLogin() {
		return "login";// 返回页面login
	}
	@RequestMapping("/do_login") // 作为异步操作
	@ResponseBody
	public CodeMsg doLogin(LoginVo loginVo) {// 0代表成功
		// log.info(loginVo.toString());
		if (loginVo == null) {
			return CodeMsg.SERVER_ERROR;
		}
		// 验证
		String formPass = loginVo.getPassword();
		String mobile = loginVo.getMobile();
		// 验证用户
		MiaoshaUser user = miaoshaUserService.getById(Long.parseLong(mobile));
		if (user == null) {
			return CodeMsg.MOBILE_NOTEXIST;
		}
		// 验证密码
		String dbPass = user.getPwd();
		String dbSalt = user.getSalt();
		System.out.println("dbPass:" + dbPass + "   dbSalt:" + dbSalt);
		// 验证密码,计算二次MD5出来的pass是否与数据库一致
		String tmppass = MD5Util.formPassToDBPass(formPass, dbSalt);
		System.out.println("formPass:" + formPass);
		System.out.println("tmppass:" + tmppass);
		if (!tmppass.equals(dbPass)) {
			return CodeMsg.PASSWORD_ERROR;
		}
		return CodeMsg.SUCCESS;
	}
}
5.前端login.html:引入bootstrap, 相关的js和css
		var pass=$("#password").val();
				//pass='111111';
				//固定salt
				var salt='1a2b3c4d';
				var str=""+salt.charAt(0)+salt.charAt(2)+pass+salt.charAt(5)+salt.charAt(4);
				var password=md5(str);
				//alert(salt);
				//alert(pass);
				//alert(password);
				//与后台Md5规则一致
				//var str=""+salt.charAt(0)+salt.charAt(2)+formPass+salt.charAt(5)+salt.charAt(4);
				$.ajax({
					url:"/login/do_login",
					type:"POST",
					data:{
						mobile:$("#phone").val(),
						password:password,
					},
					success:function(data){
						if(data.code==0){
							alert("success");
							//成功后跳转
							window.location.href="/goods/to_list";
						}else{
							alert(data.msg);
						}
					},
					error:function(data){
						alert("error");
						//alert(data.msg);
					}
				});

JSR303校验

系统在登录的时候做了一个参数校验,也就是说每一个方法的开头都要去做一个校验,那么有没有更简洁的方法呢?那就是使用JSR 303 校验。

JSR 303 用于对Java Bean 中的字段的值进行验证,使得验证逻辑从业务代码中脱离出来。是一个运行时的数据验证框架,在验证之后验证的错误信息会被马上返回。

1.引入依赖:

	 <dependency>
    	<groupId>org.springframework.boot</groupId>
    	<artifactId>spring-boot-starter-validation</artifactId>
    </dependency>

2.用法:

在需要验证的参数前面打上标签注解@Valid,那么此注解就会自动对该Bean 进行参数校验。具体校验规则在该Bean内部实现。本项目是对登陆时候,利用到了参数校验。

	public class LoginVo {
	private String mobile;
	private String password;	
	@NotNull
	@IsMobile
	public String getMobile() {
		return mobile;
	}	
	public void setMobile(String mobile) {
		this.mobile = mobile;
	}
	@NotNull
	@Length(min=32)//限定密码的长度
	public String getPassword() {
		return password;
	}
	public void setPassword(String password) {
		this.password = password;
	}
}
  • 好处:可以直接使用@NotNull、@Length(min=32)等注解进行验证,避免重复的校验代码,只需在传入的参数上打上注解就可以进行参数校验,避免代码冗余。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值