• MyMusic 重点实现


    项目功能概述

    项目主要实现了:

    1. 用户登录之后可以进行音乐的播放。通过拦截器,如果用户未登录,就不可访问相应的资源。
    2. 用户注册的时候,通过 BCrypt 加密,防止密码被破解。
    3. 可以对音乐进行喜欢,对于喜欢的音乐也可以进行删除。
    4. 管理员可以对音乐进行单个删除,也可以进行多个删除。
    5. 当音乐被删除之后,喜欢列表的音乐也会随之被删除。
    6. 可以对音乐进行搜索,也可以自己上传音乐。

    普通用户

    注册

    普通用户在使用时,需要先进行注册,注册之后才可以登录使用。注册的时候通过 BCrypt 加密,保证用户密码的安全性:
    在这里插入图片描述

    登录之后的列表页面
    在这里插入图片描述
    因为是普通用户,所以不能删除音乐,只能喜欢、播放、搜索和添加音乐。

    喜欢列表
    在这里插入图片描述

    搜索功能
    在这里插入图片描述

    添加音乐
    在这里插入图片描述

    管理员

    注册
    管理员可以通过管理员注册页面进行注册:
    在这里插入图片描述

    音乐列表
    管理员可以对音乐进行单个删除和批量删除。
    在这里插入图片描述

    其它
    管理员在其他方面的功能和普通用户是一样的。

    注册密码加密

    主要就是对注册密码进行加密,防止在登陆的时候,别人一抓包,就抓到我们的账号和密码。加密的时候,有 MD5 和 BCrypt 两种加密方式。

    MD5 加密

    MD5是一个安全的散列算法。

    1. 输入两个不同的明文不会得到相同的输出值,根据输出值,不能得到原始的明文,就是它的过程不可逆。
    2. 虽然说是不可逆,但也不是说就是安全的。因为自从出现彩虹表后,这样的密码也"不安全"。
    3. 彩虹表,就是一个庞大的,针对各种可能的字母组合预先计算好的哈希值的集合,不一定是针对MD5算法的,各种算法的都有。
    4. 有了彩虹表可以快速的破解各类密码。越是复杂的密码,需要的彩虹表就越大,现在主流的彩虹表都是100G以上。
    5. 而 MD5加密出来的密码是固定,比如: 123 在加密之后,为 dhadhadhad。只要有一个 “密码库” 记录了这一个组合,就可以根据 加密之后的密码,获取到 原本密码。
    6. 也就是说:彩虹表 天克 MD5 加密。

    MD5 不安全的原因:

    1. 暴力攻击速度很快
    2. 字典表很大
    3. 容易碰撞

    让 MD5 更安全的方法: 主要就是通过加盐或者使用长密码等算法,让整个加密字符串变得更长,破解时间变慢。如果破解密码时间足够长,就说明密码是安全的。

    加盐的过程

    1. 添加 MD5 依赖:

      
      <dependency>
      	<groupId>commons-codecgroupId>
      	<artifactId>commons-codecartifactId>
      dependency>
      <dependency>
      	<groupId>org.apache.commonsgroupId>
      	<artifactId>commons-lang3artifactId>
      	<version>3.9version>
      dependency>
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
    2. 创建一个 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"));
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      • 40
      • 41
      • 42
      • 43
      • 44
      • 45

      进行两次加密是为了说明,MD5 的两次分开加密和一次加密两次的结果是一样的:
      在这里插入图片描述
      发现密码不一样,也就说明了加密的不安全性。

    3. 当每次都可以生成一个随机的盐值,就可以有效防止这样的问题的产生。所以也就用到了 BCrypt 加密。

    BCrypt 加密

    BCrypt 加密是一种比 MD5 刚安全的一种加密方式,也就是加的盐是随机的,这样的话,破解的时间成本就会大很多。

    BCrypt 生成的密文是 60 位的,而 MD5 是 32 位的,所以 BCrypt 的破解难度更大。

    1. 导入依赖:

      
      <dependency>
      	<groupId>org.springframework.securitygroupId>
      	<artifactId>spring-security-webartifactId>
      dependency>
      <dependency>
      	<groupId>org.springframework.securitygroupId>
      	<artifactId>spring-security-configartifactId>
      dependency>
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
    2. 在 SpringBoot 启动类当中,加上一个 exclude :exclude = {org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration.class} 如果不加的话,会导致项目报错。

    3. 创建一个类来测试:

      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);
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16

      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 的一种扩展,存在文件头部,长度不定。所以就很难判断文件是否是音频文件。

    1. ID3V2 到现在一共有 4 个版本,但流行的播放软件一般只支持第 3 版,既 ID3v2.3。由于 ID3V1 记录在 MP3 文件的末尾。ID3V2 就只好记录在 MP3 文件的首部了(如果有一天发布 ID3V3,真不知道该记录在哪里)。
    2. 也正是由于这个原因。对 ID3V2 的操作比 ID3V1 要慢。而且 ID3V2 结构比 ID3V1 的结构要复杂得多,但比前者全面且可以伸缩和扩展。
    3. 每个 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"));
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    测试 MP3 文件:
    在这里插入图片描述
    运行结果如下:
    在这里插入图片描述

    测试改后缀名的文件:
    在这里插入图片描述
    运行结果如下:
    在这里插入图片描述

  • 相关阅读:
    Java面试八股文整理
    【LOJ#6718】九个太阳「弱」化版(循环卷积,任意模数NTT)
    Spring源码:Bean生命周期(五)
    JAVA商城和PHP商城的区别
    Redis笔记 Redis主从同步
    sass内置函数汇总
    JavaWeb-EL表达式
    OCR技术狂潮:揭秘最新发展现状,引爆未来智能时代
    超图嵌入论文阅读2:超图神经网络
    k8s-服务网格实战-配置 Mesh(灰度发布)
  • 原文地址:https://blog.csdn.net/sjp151/article/details/127302538