筆者認爲登錄功能不難做,但是要做出一個安全可靠的登錄界面卻非常的難,很多人會說,登錄功能是最簡單的業務代碼,但是筆者認爲許多網站出現用戶登錄密碼泄露事件都和這個登錄有一定的關係,所以登錄功能必須好好研究一下,現在就利用MD5實現相對安全的登錄模塊。注意是相對安全。
思考
md5真的無法解密嗎?
- MD5加密無法解密,不可逆轉,是很安全的。
- 從技術的角度講,MD5真的很安全,因爲無法解密,破解MD5的方式只有一個:撞庫(我是不知道別的方式),然而,就在大數據的今天。我今天隨意的生成了幾個自己常用的密碼的MD5密文,很恐怖,隨意百度到的工具都給破解出來了。大家可以試試,大數據是真的恐怕,我記得好像誰說過大數據是未來的生產力,也理解爲什麼。反正非常恐怕。
所以,我相信,不久的將來,隨着各種加密原文的收集,我們的MD5已不再安全,就像一個post請求一樣,只是你認爲他不是明文。他只是阻隔了一部分什麼都不知道的人羣,對於黑客(甚至入門級別的黑客)來說,只要抓到了你的請求,數據暴露無疑。
數據庫存放的是用戶的真實密碼嗎?
大學老師一般都是把真實密碼存放到數據庫中,但那只是演示,並非企業開發是這樣做的。
數據庫裏面存的是做了兩次MD5的用戶密碼 與其對應的salt值
如何判斷密碼輸入正確?
現在我們登錄的時候,要去取得數據庫裏面對應用戶的密碼和salt值,然後後臺接收了前端做了一次MD5的密碼formPass,然後將這個formPass去和數據庫裏面的salt一起再做一次MD5,然後檢測是否與數據庫裏面存的那個密碼一致。
如何靈活使用MD5加強安全性?
MD5也不是絕對安全的,但是我們可以使得破解的難度指數級增長。md5是不可逆的,不能反向解密的,網上所謂的“解密”都是把“加密”結果存儲到數據庫再比對的只能暴力破解,即有一個字典,從字典中讀取一條記錄,將密碼用加salt鹽值做MD5來對比數據庫裏面的值是否相
- 加鹽
- 鹽的位置靈活多變,不固定,也可倒序。
思路:
先弄清楚原理再來實現代碼
兩次MD5:
- 用戶端:PASS=MD5(明文+固定Salt)
- 服務端:PASS=MD5(用戶輸入處理+隨機Salt)
代碼實現步驟:
創建秒殺User的domain類
@Getter
@Setter
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;
新建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); //綁定
}
新建MiaoshaUserService
@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;
}
}
新建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);
//現在我們登錄的時候,要去取得數據庫裏面對應用戶的密碼和salt值,然後後臺接收了前端做了一次MD5的密碼formPass,然後將這個formPass去和數據庫裏面的salt一起再做一次MD5,然後檢測是否與數據庫裏面存的那個密碼一致。
// 驗證密碼,計算二次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;
}
}
前端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);
}
});
完整的前端login.html代碼:
<!DOCTYPE html>
<!-- 使用thymeleaf,配置相應的 -->
<html xmlns:th="http://www.thymeleaf.org"> <!-- th!!! 命名空間使用 -->
<head>
<meta charset="UTF-8"/><!--<meta charset="UTF-8" /> thymeleaf模板引擎默認是Template modes:HTML5解析的,所以解析比較嚴格。 -->
<title>登錄</title>
<!-- thymeleaf引入靜態資源的方式,@加大括弧 "/" 代表static路徑-->
<!-- jquery -->
<!-- <script type="text/javascript" th:src="@{/js/jequery.min.js}"></script> -->
<script type="text/javascript" th:src="@{/jquery-validation/lib/jquery-1.11.1.js}"></script>
<!-- bootstrap -->
<!-- <link type="text/css" rel="stylesheet" th:href="@{/bootstrap/css/bootstrap.min.css}"/> -->
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css" integrity="sha384-9gVQ4dYFwwWSjIDZnLEWnxCjeSWFphJiwGPXr1jddIhOegiu1FwO5qRGvFXOdJZ4" crossorigin="anonymous"/>
<script type="text/javascript" th:src="@{/bootstrap/js/bootstrap.min.js}"></script>
<!--jquery-validator -->
<script type="text/javascript" th:src="@{/jquery-validation/jquery.validate.min.js}"></script>
<!-- <script type="text/javascript" th:src="@{/jquery-validation/jquery.validate.js}"></script> -->
<script type="text/javascript" th:src="@{/jquery-validation/localization/messages_zh.min.js}"></script>
<!-- layer -->
<script type="text/javascript" th:src="@{/layer/layer.js}"></script>
<!-- md5.js -->
<script type="text/javascript" th:src="@{/js/md5.min.js}"></script>
</head>
<body>
<div class="container">
<div class="row">
<div class="col-sm-8 offset-sm-2">
<div class="border-bottom mb-4 mt-4 pb-2">
</div>
<div class="card">
<div class="card-header">
<h6 class="card-text">Simple Form</h6><!--</h3> -->
</div>
<div class="card-body">
<form id="signupForm" method="post" class="form-horizontal" ><!-- action="" -->
<div class="form-group row">
<label class="col-sm-4 col-form-label" for="phone">Phone</label>
<div class="col-sm-6">
<input type="text" class="form-control" id="phone" name="phone" placeholder="phone" />
</div>
</div>
<div class="form-group row">
<label class="col-sm-4 col-form-label" for="username">Username</label>
<div class="col-sm-6">
<input type="text" class="form-control" id="username" name="username" placeholder="Username" />
</div>
</div>
<div class="form-group row">
<label class="col-sm-4 col-form-label" for="email">Email</label>
<div class="col-sm-6">
<input type="text" class="form-control" id="email" name="email" placeholder="Email" />
</div>
</div>
<div class="form-group row">
<label class="col-sm-4 col-form-label" for="password">Password</label>
<div class="col-sm-6">
<input type="password" class="form-control" id="password" name="password" placeholder="Password" />
</div>
</div>
<div class="form-group row">
<label class="col-sm-4 col-form-label" for="confirm_password">Confirm password</label>
<div class="col-sm-6">
<input type="password" class="form-control" id="confirm_password" name="confirm_password" placeholder="Confirm password" />
</div>
</div>
<div class="form-group row">
<div class="col-sm-6 offset-sm-4">
<div class="form-check">
<input type="checkbox" id="agree" name="agree" value="agree" class="form-check-input"/>
<label class="form-check-label">Please agree to our policy</label>
</div>
</div>
</div>
<div class="form-group row">
<div class="col-sm-9 offset-sm-4">
<button type="submit" class="btn btn-primary" name="signup" value="Sign up">Sign up</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
<script type="text/javascript">
$.validator.setDefaults( {
submitHandler: function () {
var pass=$("#password").val();
//pass='111111';
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);
}
});
//alert( "submitted!" );
}
} );
$( document ).ready( function () {
$( "#signupForm" ).validate( {
rules: {
firstname: "required",
lastname: "required",
username: {
required: true,
minlength: 2
},
password: {
required: true,
minlength: 5
},
confirm_password: {
required: true,
minlength: 5,
equalTo: "#password"
},
email: {
required: true,
email: true
},
agree: "required"
},
messages: {
firstname: "Please enter your firstname",
lastname: "Please enter your lastname",
username: {
required: "Please enter a username",
minlength: "Your username must consist of at least 2 characters"
},
password: {
required: "Please provide a password",
minlength: "Your password must be at least 5 characters long"
},
confirm_password: {
required: "Please provide a password",
minlength: "Your password must be at least 5 characters long",
equalTo: "Please enter the same password as above"
},
email: "Please enter a valid email address",
agree: "Please accept our policy"
},
errorElement: "em",
errorPlacement: function ( error, element ) {
// Add the `invalid-feedback` class to the error element
error.addClass( "invalid-feedback" );
if ( element.prop( "type" ) === "checkbox" ) {
error.insertAfter( element.next( "label" ) );
} else {
error.insertAfter( element );
}
},
highlight: function ( element, errorClass, validClass ) {
$( element ).addClass( "is-invalid" ).removeClass( "is-valid" );
},
unhighlight: function (element, errorClass, validClass) {
$( element ).addClass( "is-valid" ).removeClass( "is-invalid" );
}
} );
} );
</script>
</body>
</html>