MyMusic 重点实现
MyMusic 重点实现
- 项目功能概述
- 普通用户
- 管理员
- 注册密码加密
- MD5 加密
- BCrypt 加密
- 两种加密的区别
- 对 MP3 文件校验
- 文件格式
- ID3V1 和 ID3V2
- ID3V1
- ID3V2
项目功能概述
项目主要实现了:
- 用户登录之后可以进行音乐的播放。通过拦截器,如果用户未登录,就不可访问相应的资源。
- 用户注册的时候,通过 BCrypt 加密,防止密码被破解。
- 可以对音乐进行喜欢,对于喜欢的音乐也可以进行删除。
- 管理员可以对音乐进行单个删除,也可以进行多个删除。
- 当音乐被删除之后,喜欢列表的音乐也会随之被删除。
- 可以对音乐进行搜索,也可以自己上传音乐。
普通用户
注册:
普通用户在使用时,需要先进行注册,注册之后才可以登录使用。注册的时候通过 BCrypt 加密,保证用户密码的安全性:
登录之后的列表页面 :
因为是普通用户,所以不能删除音乐,只能喜欢、播放、搜索和添加音乐。
喜欢列表 :
搜索功能 :
添加音乐 :
管理员
注册 :
管理员可以通过管理员注册页面进行注册:
音乐列表:
管理员可以对音乐进行单个删除和批量删除。
其它 :
管理员在其他方面的功能和普通用户是一样的。
注册密码加密
主要就是对注册密码进行加密,防止在登陆的时候,别人一抓包,就抓到我们的账号和密码。加密的时候,有 MD5 和 BCrypt 两种加密方式。
MD5 加密
MD5是一个安全的散列算法。
- 输入两个不同的明文不会得到相同的输出值,根据输出值,不能得到原始的明文,就是它的过程不可逆。
- 虽然说是不可逆,但也不是说就是安全的。因为自从出现彩虹表后,这样的密码也"不安全"。
- 彩虹表,就是一个庞大的,针对各种可能的字母组合预先计算好的哈希值的集合,不一定是针对MD5算法的,各种算法的都有。
- 有了彩虹表可以快速的破解各类密码。越是复杂的密码,需要的彩虹表就越大,现在主流的彩虹表都是100G以上。
- 而 MD5加密出来的密码是固定,比如: 123 在加密之后,为 dhadhadhad。只要有一个 “密码库” 记录了这一个组合,就可以根据 加密之后的密码,获取到 原本密码。
- 也就是说:彩虹表 天克 MD5 加密。
MD5 不安全的原因:
- 暴力攻击速度很快
- 字典表很大
- 容易碰撞
让 MD5 更安全的方法: 主要就是通过加盐或者使用长密码等算法,让整个加密字符串变得更长,破解时间变慢。如果破解密码时间足够长,就说明密码是安全的。
加盐的过程 :
-
添加 MD5 依赖:
<!-- md5 依赖 --> <dependency> <groupId>commons-codec</groupId> <artifactId>commons-codec</artifactId> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.9</version> </dependency>
-
创建一个 MD5 类:
public class MD5Util { //定义一个固定的盐值 private static final String salt = "Lockey123"; public static String md5(String src) { return DigestUtils.md5Hex(src); } /** * 第一次加密 :模拟前端自己加密,然后传到后端 * @param inputPass * @return */ public static String inputPassToFormPass(String inputPass) { String str = ""+salt.charAt(1)+salt.charAt(3) + inputPass +salt.charAt(5) + salt.charAt(6); return md5(str); } /** * 第2次MD5加密 * @param formPass 前端加密过的密码,传给后端进行第2次加密 * @param salt 用户数据库当中的盐值 * @return */ public static String formPassToDBPass(String formPass, String salt) { String str = ""+salt.charAt(0)+salt.charAt(2) + formPass +salt.charAt(5) + salt.charAt(4); return md5(str); } /** * 上面两个函数合到一起进行调用 * @param inputPass * @param saltDB * @return */ public static String inputPassToDbPass(String inputPass, String saltDB) { String formPass = inputPassToFormPass(inputPass); String dbPass = formPassToDBPass(formPass, saltDB); return dbPass; } public static void main(String[] args) { System.out.println("对用户输入密码进行第1次加密:"+inputPassToFormPass("123456")); System.out.println("对用户输入密码进行第2次加密:"+formPassToDBPass(inputPassToFormPass("123456"), "Lockey123")); System.out.println("对用户输入密码进行第2次加密:"+inputPassToDbPass("123456", "Lockey123")); } }
进行两次加密是为了说明,MD5 的两次分开加密和一次加密两次的结果是一样的:
发现密码不一样,也就说明了加密的不安全性。 -
当每次都可以生成一个随机的盐值,就可以有效防止这样的问题的产生。所以也就用到了 BCrypt 加密。
BCrypt 加密
BCrypt 加密是一种比 MD5 刚安全的一种加密方式,也就是加的盐是随机的,这样的话,破解的时间成本就会大很多。
BCrypt 生成的密文是 60 位的,而 MD5 是 32 位的,所以 BCrypt 的破解难度更大。
-
导入依赖:
<!-- security依赖包 (加密)--> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-web</artifactId> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-config</artifactId> </dependency>
-
在 SpringBoot 启动类当中,加上一个 exclude :
exclude = {org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration.class}
如果不加的话,会导致项目报错。 -
创建一个类来测试:
public class BCryptUtil { public static void main(String[] args) { //模拟从前端获得的密码 String password = "123456"; BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder(); String newPassword = bCryptPasswordEncoder.encode(password); System.out.println("加密的密码为: "+newPassword); //使用matches方法进行密码的校验 boolean same_password_result = bCryptPasswordEncoder.matches(password,newPassword); //返回true System.out.println("加密的密码和正确密码对比结果: "+same_password_result); boolean other_password_result = bCryptPasswordEncoder.matches("987654",newPassword); //返回false System.out.println("加密的密码和错误的密码对比结果: " + other_password_result); } }
BCrypt 的使用比 MD5 简单很多,encode 方法就直接完成了加密,加密的运行结果如下:
再加密一次,结果如下:
虽然原始密码一样,但是每次加密的结果都不一样,所以 BCrypt 更安全。也就是每次加密的时候,都是随即加盐处理。密码安全的原因就是,破解的成本比收益高,就说明密码是安全的。
两种加密的区别
BCrypt 加密:
是一种加盐的单向 Hash,不可逆的加密算法,同一种明文(plaintext),每次加密后的密文都不一样,而且不可反向破解生成明文,破解难度很大。
MD5 加密:
是不加盐的单向 Hash,不可逆的加密算法,同一个密码经过 hash 的时候生成的是同一个 hash 值,在大多数的情况下,有些经过 md5 加密的方法将会被破解。
目前来说,MD5 和 BCrypt 都比较流行。相对来说,BCrypt 比 MD5 更安全,但加密更慢。虽然 BCrpyt 也是输入的字符串+盐,但是与 MD5+盐 的主要区别是:每次加的盐不同,导致每次生成的结果也不相同。无法比对!
对 MP3 文件校验
主要就是对 MP3 后缀名的文件进行校验,看是不是真的 MP3 文件。
文件格式
MP3 文件大体分为三部分:TAG_V2(ID3V2),Frame, TAG_V1(ID3V1) :
也就是说,每个Frame都由帧头和数据部分组成。那么每个帧头的数据格式如下,如图片所示:使用字符 A 到 M 表示不同的区域。在表格中可以看到每一区域的详细内容(只截取部分,详细参考点这里 :
ID3V1 和 ID3V2
提到 ID3V1 和 ID3V2 就不得不提 MPEG 音频标签 。ID3V1 和 ID3V2 是 MPEG标签 的两种。
ID3V1
这种是存在于 文件尾部,长度是 128字节。用来描述 MPEG 音频文件,包含艺术家,标题,唱片集,发布年代和流派,还有额外的注释空间,位于音频文件的最后。固定为 128 字节。可以读取该文件的最后这 128 字节,来获得标签,结构如下:
AAABBBBB BBBBBBBB BBBBBBBB BBBBBBBB
BCCCCCCC CCCCCCCC CCCCCCCC CCCCCCCD
DDDDDDDD DDDDDDDD DDDDDDDD DDDDDEEE
EFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFG
所以看了上面那张表,是不是就可以认为:我们只需要查看文件尾部的128个字节里面,有没有 TAG 字样就可以了。事实上就是这样的,如果三个字节中有 “TAG” 字样(标记)。就说明它是一个 音频文件。反之,如果不包含,则说明它不是一个 音频文件。存放“ TAG ”字符,表示 ID3 V1.0 标准 ,根据是否包含这个字符,判断是不是音频文件。
ID3V2
是对 ID3V1 的一种扩展,存在文件头部,长度不定。所以就很难判断文件是否是音频文件。
- ID3V2 到现在一共有 4 个版本,但流行的播放软件一般只支持第 3 版,既 ID3v2.3。由于 ID3V1 记录在 MP3 文件的末尾。ID3V2 就只好记录在 MP3 文件的首部了(如果有一天发布 ID3V3,真不知道该记录在哪里)。
- 也正是由于这个原因。对 ID3V2 的操作比 ID3V1 要慢。而且 ID3V2 结构比 ID3V1 的结构要复杂得多,但比前者全面且可以伸缩和扩展。
- 每个 ID3V2.3 的标签都一个标签头和若干个标签帧或一个扩展标签头组成。关于曲目的信息如标题、作者等都存放在不同的标签帧中,扩展标签头和标签帧并不是必要的,但每个标签至少要有一个标签帧。标签头和标签帧一起顺序存放在 MP3 文件的首部。
参考资料:
https://www.cnblogs.com/ranson7zop/p/7655474.html
https://blog.csdn.net/sunshine1314/article/details/2514322
代码如下:
import lombok.Data;
import java.io.*;
import java.util.HashMap;
@Data
public class GetFileType {
private static HashMap<String,String> fileHeadMap = new HashMap<>();
static {
//检测音频,分别是 两种的 ID3 类型
fileHeadMap.put("494433030000","mp3");
fileHeadMap.put("494433040000","MP3");
}
/**
* 获取文件类型
* @param filePath 文件路径
* @return 文件类型
*/
public static String getFileHead(String filePath)throws IOException {
//获取文件头
File file = new File(filePath);
FileReader reader = new FileReader(file);
BufferedReader br = new BufferedReader(reader);
StringBuilder fileHead = new StringBuilder();
for(int i = 0 ; i<=5;i++ ){
int flag = br.read();
if(Integer.toHexString(flag).length()==1){
fileHead.append(0);
}
fileHead.append(Integer.toHexString(flag));
}
br.close();
//查询文件类型
return fileHeadMap.get(fileHead.toString());
}
public static void main(String[] args) throws IOException {
System.out.println(getFileHead("D:/下载/Crush.mp3"));
}
}
测试 MP3 文件:
运行结果如下:
测试改后缀名的文件:
运行结果如下: