Springboot-04:Panic Buying Project MD5加密登入+JSR303校驗
技術標籤:Springbootspring bootjava
MD5加密登入
如果不做任何處理:那麼明文密碼就會在網路上進行傳輸,假如說惡意使用者取得這個資料包,那麼就可以得到這個密碼,這不安全。
為什麼做兩次MD5?
- 使用者端:PASS=MD5 (明文+固定Salt)
- 服務端: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)等註解進行驗證,避免重複的校驗程式碼,只需在傳入的引數上打上註解就可以進行引數校驗,避免程式碼冗餘。