发表于: 2019-11-11 00:08:11
1 952
今天完成的事情:
用cookie登录
1.控制器保存/销毁cookie信息的登录/登出url
2.校验是否有cookie信息及匹配cookie信息的拦截器
3.在springMVC.xml中配置拦截器信息
在登录得时候增加cookie
登录成功后,客户端保存cookie,再通过拦截器拦截url,实现一个页面用户不登录就能访问,另一个页面用户登录才能访问
写一个类去实现HandlerInterceptor类
public class LoginInterceptor implements HandlerInterceptor {
@Autowired
UserService userService;
private static Logger logger = Logger.getLogger(LoginInterceptor.class);
//true表示继续流程,false表示中断流程,不会继续调用拦截器
@Override
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o)
throws Exception {
//获取request的cookie
Cookie[] cookies = httpServletRequest.getCookies();
//如果httpServletRequest里的cookies为空,就从httpServletResponse的header拿token.拿到后解析
//如果cookies的长度等于0,表示没有cookies,返回登录页面,让用户登录一下,发给用户一个cookie,下次光临的时候用户就有cookie了
if (cookies == null && cookies.length > 0){
logger.info("don't have cookie");
//从httpServletRequest.getHeader拿出token,然后解密
String token = httpServletRequest.getHeader("token");
DesUtil desUtil = new DesUtil();
//取出明文
String mw = DesUtil.decrypt(token);
//分割字符串
String[] fg = mw.split("\\|");
logger.info("时间戳:"+fg[0].toString());
//取出数字
String timeStamp = fg[0];
Long time = DesUtil.decryptToLong(timeStamp);
logger.info("时间:"+time);
//判断时间戳是否过期
if (System.currentTimeMillis()- time >= 60 * 60 * 1000){
logger.info("token过期");
httpServletResponse.sendRedirect("login");
return false;
}
else {
String ids = fg[2];
logger.info("用户id:"+ids);
Long idis = DesUtil.decryptToLong(ids);
logger.info("id是:" +idis);
//判断是否合法
User user =userService.selectById(idis);
logger.info("查到的数据:"+user);
//如果为空,就返回登录页面
if (user==null){
httpServletResponse.sendRedirect("login");
return false;
}
return true;
}
}else {
logger.info("you have cookie");
//遍历cookies,放入cookie看下是否对应
for (int i = 0; i < cookies.length; i++){
logger.info("cookieName:"+cookies[i].getName());
logger.info("cookieValue"+cookies[i].getValue());
//如果他的cookie与我给他发的token名字相等,那么可以返到页面里
if (cookies[i].getName().equals("token")){
logger.info("token:"+cookies[i].getValue());
String token = cookies[i].getValue();
//解token,取出id 判断id是否不为空, 不为空,去数据库里查这个id对应的用户是否存在,存在通过拦截器,不存在返回
DesUtil desUtil = new DesUtil();
//取出token明文
String mw = DesUtil.decrypt(token);
String[] str = mw.split("\\|");
String timeStamp = str[0];
Long time = Long.valueOf(timeStamp);
if (System.currentTimeMillis() - time>= 60*60 *1000) {
logger.info("token过期");
//返回登录页面
httpServletResponse.sendRedirect("login");
return false;
}
String idstr = str[2];
Long id = Long.valueOf(idstr);
User user = userService.selectById(id);
if (user==null){
httpServletResponse.sendRedirect("login");
return false;
}
}
return true;
}
}
//没有找到登录状态则重定向回登录页面,返回false,不执行controller的方法
httpServletResponse.sendRedirect("/login");
return false;
}
//在业务处理器处理请求完成之后,生成视图之前执行
@Override
public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView)
throws Exception {
}
//在DispatcherServlet完全处理请求之后被调用,可用于清理资源
@Override
public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e)
throws Exception {
}
}
使用DES对用户ID和登录时间加密
网上复制一个DesUtil
public class DesUtil {
private static String keyData = "ABCDEFGHIJKLMNOPQRSTWXYZabcdefghijklmnopqrstwxyz0123456789-_.";
//安全密钥
public DesUtil() {
}
//无参构造
public DesUtil(String key) {
keyData = key;
}
//有参构造
/**
* 将Long类型加密
* @param source
* @return
* @throws UnsupportedEncodingException
*/
public static String encryptFromLong(long source)throws UnsupportedEncodingException {
String source1=String.valueOf(source);
//先将long类型转化为String类型
return encrypt(source1, "UTF-8");
}
/**
* 将解密好的转化为long类型
* @param encryptedData
* @return
* @throws UnsupportedEncodingException
*/
public static long decryptToLong(String encryptedData) {
long decryptLong=0;
try {
decryptLong=Long.valueOf(decrypt(encryptedData, "UTF-8"));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return decryptLong;
}
/**
*加密UTF-8,调用底下的方法
* @param source 待加密数据
* @return 加密完成的数据
* @throws UnsupportedEncodingException
* 异常
*/
public static String encrypt(String source)throws UnsupportedEncodingException{
return encrypt(source, "UTF-8");
}
/**
*解密UTF-8,调用底下的方法
* @param encryptedData 待解密数据
* @return 解密完成数据
* @throws UnsupportedEncodingException
* 异常
*/
public static String decrypt(String encryptedData) {
String miwen=null;
try {
miwen= decrypt(encryptedData, "UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return miwen;
}
/**
*功能:加密
* @param source 待加密数据
* @param charSet 字符编码
* @return 加密完成数据
* @throws UnsupportedEncodingException
* 异常
*/
public static String encrypt(String source, String charSet)
throws UnsupportedEncodingException {
String encrypt = null;
byte[] ret = encrypt(source.getBytes(charSet));
encrypt = new String(Base64.encode(ret));
return encrypt;
}
/**
*功能:解密
* @param encryptedData 待解密数据
* @param charSet 字符编码
* @return 解密完成数据
* @throws UnsupportedEncodingException
* 异常
*/
public static String decrypt(String encryptedData, String charSet)
throws UnsupportedEncodingException {
String decryptedData = null;
byte[] ret = decrypt(Base64.decode(String.valueOf(encryptedData.toCharArray())));
decryptedData = new String(ret, charSet);
return decryptedData;
}
/**
*加密
* @param primaryData
* @return
*/
private static byte[] encrypt(byte[] primaryData) {
//取得安全密钥
byte[] rawKeyData = getKey();
//DES算法要求有一个可信任的随机数源
SecureRandom sr = new SecureRandom();
//使用原始密钥数据创建DESKeySpec对象
DESKeySpec dks = null;
try {
dks = new DESKeySpec(keyData.getBytes());
} catch (InvalidKeyException e) {
e.printStackTrace();
}
//创建一个密钥工厂
SecretKeyFactory keyFactory = null;
try {
keyFactory = SecretKeyFactory.getInstance("DES");
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
//用密钥工厂把DESKeySpec转换成一个SecretKey对象
SecretKey key = null;
try {
key = keyFactory.generateSecret(dks);
} catch (InvalidKeySpecException e) {
e.printStackTrace();
}
// Cipher对象实际完成加密操作
Cipher cipher = null;
try {
cipher = Cipher.getInstance("DES");
} catch (NoSuchAlgorithmException| NoSuchPaddingException e) {
e.printStackTrace();
}
// 用密钥初始化Cipher对象
try {
cipher.init(Cipher.ENCRYPT_MODE, key, sr);
} catch (InvalidKeyException e) {
e.printStackTrace();
}
// 正式执行加密操作
byte encryptedData[] = null;
try {
encryptedData = cipher.doFinal(primaryData);
} catch (IllegalStateException e) {
e.printStackTrace();
} catch (IllegalBlockSizeException|BadPaddingException e) {
e.printStackTrace();
}
//返回加密数据
return encryptedData;
}
/**
*加密
* @param encryptedData
* @return
*/
private static byte[] decrypt(byte[] encryptedData) {
/** DES算法要求有一个可信任的随机数源 */
SecureRandom sr = new SecureRandom();
/** 取得安全密钥 */
byte rawKeyData[] = getKey();
/** 使用原始密钥数据创建DESKeySpec对象 */
DESKeySpec dks = null;
try {
dks = new DESKeySpec(keyData.getBytes());
} catch (InvalidKeyException e) {
e.printStackTrace();
}
/** 创建一个密钥工厂 */
SecretKeyFactory keyFactory = null;
try {
keyFactory = SecretKeyFactory.getInstance("DES");
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
/** 用密钥工厂把DESKeySpec转换成一个SecretKey对象 */
SecretKey key = null;
try {
key = keyFactory.generateSecret(dks);
} catch (InvalidKeySpecException e) {
e.printStackTrace();
}
/** Cipher对象实际完成加密操作 */
Cipher cipher = null;
try {
cipher = Cipher.getInstance("DES");
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (NoSuchPaddingException e) {
e.printStackTrace();
}
/** 用密钥初始化Cipher对象 */
try {
cipher.init(Cipher.DECRYPT_MODE, key, sr);
} catch (InvalidKeyException e) {
e.printStackTrace();
}
/** 正式执行解密操作 */
byte decryptedData[] = null;
try {
decryptedData = cipher.doFinal(encryptedData);
} catch (IllegalStateException e) {
e.printStackTrace();
} catch (IllegalBlockSizeException e) {
e.printStackTrace();
} catch (BadPaddingException e) {
e.printStackTrace();
}
return decryptedData;
}
/**
*获得密钥
* @return
*/
private static byte[] getKey() {
/** DES算法要求有一个可信任的随机数源 */
SecureRandom sr = new SecureRandom();
/** 为我们选择的DES算法生成一个密钥生成器对象 */
KeyGenerator kg = null;
try {
kg = KeyGenerator.getInstance("DES");
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
kg.init(sr);
/** 生成密钥工具类 */
SecretKey key = kg.generateKey();
/** 生成密钥byte数组 */
byte rawKeyData[] = key.getEncoded();
return rawKeyData;
}
}
在登录接口里验证登录→登录成功→对id和登录时间进行加密放在token中→创建cookie→把token放入cookie中
拦截器拦截cookie→看cookie是否为空→遍历cookie→看是否是否有token
登录使用MD5加盐
MD5工具类
public class Md5Util {
//输入字符串,输出32进制字符串形式的MD5摘要
public static String MD5(String input) {
/*MessageDigest 类为应用程序提供信息摘要算法的功能,
如 MD5 或 SHA 算法。信息摘要是安全的单向哈希函数,
它接收任意大小的数据,并输出固定长度的哈希值。*/
MessageDigest md5 = null;
try {
//获取类实例
md5 = MessageDigest.getInstance("MD5");
} catch (NoSuchAlgorithmException e) {
return "check jdk";
} catch (Exception e) {
e.printStackTrace();
return "";
}
//input.toCharArray是将字符串对象中的字符转化为字符串数组
char[] charArray = input.toCharArray();
//创建一个和字符串长度相同的字节数组
byte[] byteArray = new byte[charArray.length];
//将字符串数组转化为字节数组
for (int i = 0; i < charArray.length; i++) {
byteArray[i] = (byte) charArray[i];
}
//获取摘要信息的字节数组
byte[] md5Bytes = md5.digest(byteArray);
/*StringBuffer类中的方法主要偏重于对于字符串的变化,
例如追加、插入和删除等,这个也是StringBuffer和String类的主要区别*/
StringBuffer hexValue = new StringBuffer();
//遍历摘要信息字节数组的每个元素
for (int i = 0; i < md5Bytes.length; i++) {
//byte类型向高24位(就是int的32位)转换,直接转换会将高24位也看做是有效位数,会导致错误。将高24位置0就可以避免
int val = ((int) md5Bytes[i]) & 0xff;
if (val < 16) {
hexValue.append("0");
}
hexValue.append(Integer.toHexString(val));
}
return hexValue.toString();
}
public static String generate(String password) {
//创建一个随机数生成器,没有随机种子时,以当前时间为随机种子;r.next()产生随机数并设置随机数范围
Random r = new Random();
//StringBuilder用字符串连接,可设置容量
StringBuilder sb = new StringBuilder(16);
//产生随机数
sb.append(r.nextInt(99999999)).append(r.nextInt(99999999));
int len = sb.length();
if (len < 16) {
for (int i = 0; i < 16 - len; i++) {
sb.append("0");
}
}
String salt = sb.toString();
//获取16进制的密码加盐的摘要
password = md5Hex(password + salt);
char[] cs = new char[48];
for (int i = 0; i < 48; i += 3) {
cs[i] = password.charAt(i / 3 * 2);
char c = salt.charAt(i / 3);
cs[i + 1] = c;
cs[i + 2] = password.charAt(i / 3 * 2 + 1);
}
return new String(cs);
}
//校验加盐后是否与原文一致,
public static boolean verify(String password, String md5) {
char[] cs1 = new char[32];
char[] cs2 = new char[16];
for (int i = 0; i < 48; i += 3) {
cs1[i / 3 * 2] = md5.charAt(i);
cs1[i / 3 * 2 + 1] = md5.charAt(i + 2);
cs2[i / 3] = md5.charAt(i + 1);
}
String salt = new String(cs2);
return md5Hex(password + salt).equals(new String(cs1));
}
/**
* 获取十六进制字符串形式的MD5摘要
*/
private static String md5Hex(String src) {
try {
MessageDigest md5 = MessageDigest.getInstance("MD5");
byte[] bs = md5.digest(src.getBytes());
return new String(new Hex().encode(bs));
} catch (Exception e) {
return null;
}
}
}
controller
新增了注销功能
@Controller
@RequestMapping("")
public class UserController {
private static Logger logger = Logger.getLogger(UserController.class);
@Autowired
UserService userService;
/**
* 跳转页面
* @return
*/
@RequestMapping(value = "/register", method = RequestMethod.GET)
public String register() {
return "register";
}
@RequestMapping(value = "/register", method = RequestMethod.POST)
public String insert(User user, String name) {
logger.info("新注册用户信息:" + user);
//通过用户名查找数据,看是否有这条数据
List<User> users = userService.selectByName(name);
logger.info("查看是否注册:" + users);
//能查出来这条数据就说明数据库里有这条数据,那么注册失败,否则注册成功,跳转到首页
if (!CollectionUtils.isEmpty(users)) {
logger.info("用户已存在");
return "register";
} else {
if (
user.getName() != null
&& user.getName().length() > 0
&& user.getPwd() != null
&& user.getPwd().length() > 0) {
//MD5给密码加密加盐
user.setPwd(Md5Util.MD5(user.getPwd() + user.getId()));
int row = userService.insert(user);
logger.info("注册成功:" + row);
return "home";
} else {
return "register";
}
}
}
@RequestMapping(value = "/login", method = RequestMethod.GET)
public String login(User user, HttpServletResponse response) throws UnsupportedEncodingException {
logger.info("user:" + user);
//给密码加密加盐
user.setPwd(Md5Util.MD5(user.getPwd() + user.getId()));
//加盐之后对比一下传进来的和数据库里的是否一致
List<User> user1 = userService.selectByNameAndPwd(user.getName(), user.getPwd());
//DES加密
String string1 = DesUtil.encryptFromLong(System.currentTimeMillis());
//取出数据库里的用户id
String string2 = DesUtil.encryptFromLong(user.getId());
//token由用户名,id,登录时间组成
String token = DesUtil.encrypt(string1 + "|" + user.getName() + "|" + string2);
logger.info("token:" + token);
if (user1 != null) {
Cookie tokenCookie = new Cookie("token", token);
//设置过期时间,单位为秒
tokenCookie.setMaxAge(60 * 60);
response.addCookie(tokenCookie);
return "home";
} else {
return "login";
}
}
@RequestMapping(value = "/logout", method = RequestMethod.GET)
public String logout(HttpServletResponse response, HttpServletRequest request) {
Cookie[] cookies = request.getCookies();
for (Cookie cookie : cookies) {
if ("token".equals(cookie.getName())) {
cookie.setMaxAge(0);
logger.info("被删除的token:" + cookie.getName());
response.addCookie(cookie);
return "home";
}
}
return "home";
}
}
明天计划的事情:学习JWT
遇到的问题:
空指针异常
问题出在之前使用tag标签转换时间的工具类里
不太清楚该怎么解决,从网上搬过来的代码
对于des加密解密也不太理解,先搬过来用着
收获:
cookie数据是存放在客户的浏览器上,session数据是放在服务器上。
由于HTTP是一种无状态的协议,服务器单从网络连接上无从知道用户身份。而且HTTP协议数据一旦交互完毕,客户端与服务器端的连接就会关闭,意味着服务器无法从连接上跟踪对话。怎么办呢?那就给客户端们颁发一个密匙吧,每人一个,无论谁访问都必须携带自己密匙。这样服务器就能从密匙上确认客户身份了。这就是Cookie的工作原理。
Session是另一种记录客户状态的机制,不同的是Cookie保存在客户端浏览器中,而Session保存在服务器上。客户端浏览器访问服务器的时候,服务器把客户端信息以某种形式记录在服务器上。这就是Session。客户端浏览器再次访问时只需要从该Session中查找该客户的状态就可以了。
登录要用MD5加盐
什么是MD5
它是一种信息摘要算法,它可以从一个字符串或一个文件中按照一定的规则生成一个特殊的字符串(这个特殊的字符串就被称之为摘要,我理解就是从文件中摘一些信息片段加工而来),并且一个文件所对应的MD5摘要是固定的,当文件内容变化后,其MD5值也会不一样(虽然理论上来说也有可能会一样,但概率极小极小),因此,在应用中经常使用MD5值来验证一段数据有没有被篡改。比如,在数据的发送方将原始数据生成出MD5值,然后把原始数据连同其MD5值一起传给接收方,接收该收到数据后,先将原始数据用MD5算法生成摘要信息,然后再将此摘要信息与发送方发过来的摘要信息进行比较,如果一致就认为原始数据没有被修改,否则原始数据就是被修改过了。
MD5的几个特点:
1、它是一段固定长度的数据,即128bit的由“0”和“1”组成的一段二进制数据。无论原始数据是多长或多短,其MD5值都是128bit。
2、通常(或者叫行业规定),这段128bit的数据,按4bit一组分成32组,每一组按16进制来计算其值,并以字符的形式输出每个值。比如一组数据按16进制计算出来的值是0~9,打印出来也是0~9,如果计算出来的值是a~f,如果直接打印的话将会是10~15,在java中通常使用Integer.toHexString(int)方法来将16进制的 a~f 打印成字母“a~f”,最终这个128bit的数据将会被打印成一个32位的字符串。所以我们通常所说的MD5值就是指这串32位的由“0~9,a~f”所组成的字符串。如果你看到某个MD5不是32位,或发现其中含有“0~f”之外的字符,那肯定是个错误的MD5值。
3、确定性,一个原始数据的MD5值是唯一的,同一个原始数据不可能会计算出多个不同的MD5值。
4、碰撞性,原始数据与其MD5值并不是一一对应的,有可能多个原始数据计算出来的MD5值是一样的,这就是碰撞。
5、不可逆。也就是说如果告诉你一个MD5值,你是无法通过它还原出它的原始数据的,这不是你的技术不够牛,这是由它的算法所决定的。因为根据第4点,一个给定的MD5值是可能对应多个原始数据的,并且理论上讲是可以对应无限多个原始数据,所有无法确定到底是由哪个原始数据产生的。
加盐
不同的用户可能会设置出一样的密码,那么通过相同的加密算法得到的是相同的结果。大部分常用密码都可以通过MD5摘要反向查询到密码明文。为了防止内部人员(能够接触到数据库或者数据库备份文件的人员)和外部入侵者通过MD5反查密码明文,更好地保护用户的密码和个人帐户安全(一个用户可能会在多个系统中使用同样的密码,因此涉及到用户在其他网站和系统中的数据安全),需要对MD5摘要结果掺入其他信息,称之为加盐。
简单的说就是为了减少破解了一个人的密码造成多人账户被盗的可能性。
评论