简单来说就是一组安全随机数。它在特定的时候,加入到密码中(一般来说是加密后的密码)。从而使密码变得更有味道(从单一简单化到复杂化),更安全。
盐值是一个随机数。当用户注册一个简单密码时,系统会同时生成这样一个salt,与该用户对应,保存到数据库中。
加密操作步骤:
(1)注册、修改密码时,前台将 888888加密后的pwd1,传入后台
(2)后台拿到pwd1以后,生成一个相应的随机数 salt。将pwd1与salt拼接并再次加密,生成pwd2
(3)后台将pwd2和salt 一并存储到数据库中。
登录验证步骤:
(1)当用户每次输入用户名密码后,将密码加密生成pwd1'后,传入后台。
(2)后台拿到pwd1'后,根据用户名id拿到对应的盐值。与盐值拼接加密后,生成pwd2'。
(3)然后判断pwd2'与数据库中的pwd2是否一致即可。
这里有两点需要注意:
(1)密码在前后台的加密方式可以采用不同的形式
(2)盐值的拼接不一定非要拼接到最后,也可以放在前边、插在中间、甚至拆开或者倒序拼接。
重放攻击 (Replay Attacks) 又称重播攻击、回放攻击,是指攻击者发送一个目的主机已接收过的包,来达到欺骗系统的目的,主要用于身份认证过程,破坏认证的正确性。重放攻击可以由发起者,也可以由拦截并重发该数据的敌方进行。攻击者利用网络监听或者其他方式盗取认证凭据,之后再把它重新发给认证服务器。重放攻击在任何网络通过程中都可能发生,是计算机世界黑客常用的攻击方式之一。
解决思路之一(加随机数):
(1)每次登陆时,可以生成一个随机数(一个动态生成的salt),这个salt在前后台各自保存一份。
(2)当用户名输入完密码pwd后。前台会进行 f1(pwd)加密,然后用动态生成的salt加密的密钥,然后再次加密。即: pwd1=f2(f1(pwd)+salt)。之后前台就把这个pwd1发送到后台。(注意由于动态salt每次都会改变,所以pwd1每次也会改变)
(3)后台拿到数据后,在用动态salt(即f2()使用可逆的机密算法)解密,然后再拼接固化salt接着再次加密,最后与数据库对比即可
实现密码加密使用: HashedCredentialsMatcher
1、访问项目路径到登录页面
@GetMapping(value= {"/","/login"})
public String login(Model model, HttpSession session) {
//生成一组16位随机数
int hashCodeValue = UUID.randomUUID().hashCode();
if(hashCodeValue < 0) hashCodeValue = -hashCodeValue;
String uuidSalt = String.format("%016d",hashCodeValue);//左边补0,16位,进制(d,x)
//把uuid盐值,同时保存在前后端
model.addAttribute("uuidSalt", uuidSalt);
session.setAttribute("uuidSalt", uuidSalt);
return "login";
}
登录页面
2、输入账号和密码进行登录
用户密码通过 MD5 加密,然后在进行 JQSalt 盐值加盐后保存到数据库 。所以 ShiroRealm 验证也是用相同盐值。
1)action 登录方法
@PostMapping("/login")
public String login(User user, HttpSession session) {
//使用 shiro 登录验证
//1 认证的核心组件:获取 Subject 对象
Subject subject = SecurityUtils.getSubject();
//将密码进行 aes 解密
String key = (String) session.getAttribute("uuidSalt");
String iv = (String) session.getAttribute("uuidSalt");
try {
user.setPazzword(AesEncryptUtil.desEncrypt(user.getPazzword(), key, iv));
//密码解码成功后盐值失效
session.removeAttribute("uuidSalt");
} catch (Exception e1) {
e1.printStackTrace();
return "loginError";
}
//2 将登陆表单封装成 token 对象
UsernamePasswordToken token = new UsernamePasswordToken(user.getUsername(), user.getPazzword());
try {
//3 让 shiro 框架进行登录验证:
subject.login(token);
} catch (Exception e) {
e.printStackTrace();
return "loginError";
}
return "redirect:/admin/index";
}
2)自定义 Realm 的实现验证
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.realm.AuthenticatingRealm;
import org.apache.shiro.util.ByteSource;
import org.springframework.beans.factory.annotation.Autowired;
import cn.jq.ssm.dao.UserMapper;
import cn.jq.ssm.model.User;
public class ShiroRealm extends AuthenticatingRealm{
@Autowired
private UserMapper userMapper;
/**
* 登录的验证实现方法
* @param token
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
UsernamePasswordToken token2 = (UsernamePasswordToken) token;
String username = token2.getUsername();
User user = userMapper.getUserByUsername(username);
if(user == null) {
throw new UnknownAccountException("用户名或密码有误!");
}
if(user.getStatus() == 0) {
throw new UnknownAccountException("用户名已被禁用,请联系系统管理员!");
}
/**
* principals: 可以使用户名,或d登录用户的对象
* hashedCredentials: 从数据库中获取的密码
* credentialsSalt:密码加密的盐值
* RealmName: 类名(ShiroRealm)
*/
ByteSource credentialsSalt = ByteSource.Util.bytes("JQSalt");
AuthenticationInfo info = new SimpleAuthenticationInfo(user, user.getPazzword(), credentialsSalt, getName());
return info;
}
}
简单 重放攻击实现 end ~
密码加密加盐
密码从明文变成密文,可以通过 SimpleHash 这个类来实现
@PostMapping("addUser")
public String addUser(User user) {
//获取加密工具
ByteSource pzwd1 = ByteSource.Util.bytes(user.getPazzword());
String saltValue = "JQSalt";
/**
* algorithmName: 加密方式
* source: 要加密的内容
* salt:盐值
* hashIterations: 加密次数
*/
Object pzwd2 = new SimpleHash("MD5", pzwd1, saltValue, 1024);
user.setPazzword(pzwd2.toString());
userService.add(user);
return "userlist";
}