1. 程式人生 > 其它 >Springboot-04:Panic Buying Project MD5加密登入+JSR303校驗

Springboot-04:Panic Buying Project MD5加密登入+JSR303校驗

技術標籤:Springbootspring bootjava

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);    //繫結
	
	//繫結在物件上面了[email protected]("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)等註解進行驗證,避免重複的校驗程式碼,只需在傳入的引數上打上註解就可以進行引數校驗,避免程式碼冗餘。