实现原理
首先需要了解Windows登录认证的流程,如下图所示,双方持有相同的口令,客户端请求登录,然后主域控制器产生8字节质询,并发送给客户端,双方都需要将口令散列对质询进行散列,然后客户端将最后的散列值发送给主域控制器,比对两个质询,相同则成功登录,否则失败。
前后端的简单实现(模拟流程)
前端请求
function loginPost() {
let request = new XMLHttpRequest();
request.open("POST","/user/authentication0")
let formData = new FormData();
let loginName = encrypt($(".l_user").val());
let password = encrypt($("#password").val();
let SHA1LoginName=sha1(loginName);
let SHA1Password=sha1(password);
formData.append("loginName", loginName);
formData.append("SHA1LoginName",SHA1LoginName);
request.send(formData);
request.onreadystatechange = function () {
if (request.readyState == 4 && request.status == 200) {
let data=request.responseText;
console.log(data);
if(data.includes("no")){
window.alert("用户名或密码错误!");
window.location.reload();
}else{
let temp=SHA1Password+data;
let digest=sha1(temp);
let request2=new XMLHttpRequest();
request2.open('POST','/user/authentication1');
let formData2=new FormData();
formData2.append('digest',digest);
request2.send(formData2);
request2.onreadystatechange=function(){
let data2=request2.responseText;
if(data2.includes("no")){
window.alert("登录失败,请重试");
window.location.reload();
}else{
window.location.replace("/productCategory/main");
}
}
}
}
}
}
后端产生8字节质询
public class GenUtil {
public static String generate(){
byte[] bytes=new byte[8];
new SecureRandom().nextBytes(bytes);
StringBuilder result0=new StringBuilder();
for(byte item:bytes){
result0.append(String.format("%02x", item));
}
return result0.toString();
}
}
后端处理请求
/**
* 用户登录认证
*/
@PostMapping("/authentication0")
@ResponseBody
public String Auth0( String loginName,String SHA1LoginName,HttpServletResponse response,HttpSession httpSession) throws NoSuchAlgorithmException {
UserLoginForm userLoginForm=new UserLoginForm();
userLoginForm.setLoginName(loginName);
userLoginForm.setSHA1LoginName(SHA1LoginName);
String userName=AESUtil.decrypt(userLoginForm.getLoginName(),"uUXsN6okXYqsh0BB");
String password="";
String ack;
//判断用户名是否存在
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("login_name", userName);
User user = this.userMapper.selectOne(queryWrapper);
if (user == null) {
ack="no";
log.info("【用户登录】用户名不存在");
throw new EcommerceException(ResponseEnum.USERNAME_NOT_EXISTS);
} else{
password=user.getPassword();
userLoginForm.setPassword(password);
userLoginForm.setSHA1Password(SHA1.sha1(password));
Cookie cookie0=new Cookie("username",userName);
Cookie cookie1=new Cookie("password",password);
response.addCookie(cookie0);
response.addCookie(cookie1);
ack= GenUtil.generate();
inquiry=ack;
}
User login = this.userService.login(userLoginForm);
httpSession.setAttribute("user" ,login);
return ack;
}
@PostMapping("/authentication1")
@ResponseBody
public String Auth1(HttpServletRequest request, String digest) throws NoSuchAlgorithmException {
System.out.println(digest);
Cookie[] cookies=request.getCookies();
String hashcode="";
for(Cookie cookie:cookies){
if (cookie.getName().equals("username")) {
//hashcode=cookie.getValue();
String name =cookie.getValue();
QueryWrapper<User> queryWrapper=new QueryWrapper<>();
queryWrapper.eq("login_name",name);
User user = this.userMapper.selectOne(queryWrapper);
String password=user.getPassword();
hashcode=SHA1.sha1(password);
System.out.println("6666666666666666666666666666");
}
}
String digest2= SHA1.sha1(hashcode+inquiry);
if(digest2.equals(digest)){
return "yes";
}else{
return "no";
}
}
这里面存在一些缺陷,在图中流程是用口令散列对质询进行散列,但是散列使用的手段是SHA-1,
(其中实现方式为http://t.csdn.cn/1C1LO)
无法实现此步,所以在其中只是简单的将口令散列和质询连接在一起,然后对整体进行SHA-1散列,得到digest。
其余流程和图中基本一致,或许有些地方有些不同,或者实现方式有误,感谢指正。