写点什么

Java 岗大厂面试百日冲刺 - 日积月累,每日三题【Day36】—

作者:Java高工P7
  • 2021 年 11 月 11 日
  • 本文字数:3198 字

    阅读完需:约 10 分钟

通过输出,可以看到密码是 32 位的 MD5:


"password": "325a2cc052914ceeb8c19016c091d2ac"


然后,再去破解网站试一下这个 MD5,就可以得到原始密码是 salt,也就知道了盐值是 salt



其实,知道盐是什么没什么关系,关键的是我们是在代码里写死了盐,并且盐很短、所有用户都是这个盐。这么做就会有三个问题:


  • 因为盐太短、太简单了,如果用户原始密码也很简单,那么整个拼起来的密码也很短,这样一般的 MD5 破解网站都可以直接解密这个 MD5除去盐就是原始密码了。

  • 相同的盐,意味着使用相同密码的用户 MD5 值是一样的,知道了一个用户的密码就可能知道了多个


【一线大厂Java面试题解析+后端开发学习笔记+最新架构讲解视频+实战项目源码讲义】
浏览器打开:qq.cn.hn/FTf 免费领取
复制代码



  • 黑客也可以使用这个盐来构建一张彩虹表,也就是字典表,虽然会花不少代价,但是一旦构建完成,所有人的密码都可以被破解。


所以,最好是每一个密码都有独立的盐,并且盐要长一点,比如超过 20 位。


第二,虽然说每个人的盐最好不同,但也不建议将一部分用户数据作为盐。比如,使用用户名作为盐:


userData.setPassword(DigestUtils.md5Hex(name + password));


如果世界上所有的系统都是按照这个方案来保存密码,那么 root、admin 这样的用户使用再复杂的密码也总有一天会被破解,因为黑客们完全可以针对这些常用用户名来做彩虹表。所以,盐最好是随机的值,并且是全球唯一的,意味着全球不可能有现成的彩虹表给你用。


正确的做法是,使用全球唯一的、和用户无关的、足够长的随机值作为盐。比如,可以使用 UUID 作为盐,把盐一起保存到数据库中:


userData.setSalt(UUID.randomUUID().toString());


userData.setPassword(DigestUtils.md5Hex(userData.getSalt() + password));


并且每次用户修改密码的时候都重新计算盐,重新保存新的密码。


需要注意的是,这么做虽然黑客已经很难通过彩虹表来破解密码了,但是仍然有可能暴力破解密码,也就是对于同一个用户名使用常见的密码逐一尝试登录。因此,除了做好密码哈希保存的工作外,我们还要建设一套完善的安全防御机制,在感知到暴力破解危害的时候,开启短信验证、图形验证码、账号暂时锁定等防御机制来抵御暴力破解


那么姓名和身份证又是怎么保存的?


我们把姓名和身份证,叫做二要素。


现在互联网非常发达,很多服务都可以在网上办理,很多网站仅仅依靠二要素来确认你是谁。所以,二要素是比较敏感的数据,如果在数据库中明文保存,那么数据库被攻破后,黑客就可能拿到大量的二要素信息。如果这些二要素被用来申请贷款等,后果不堪设想。


之前我们提到的单向散列算法(MD5),显然不适合用来加密保存二要素,因为数据无法解密。这个时候,我们需要选择真正的加密算法。可供选择的算法,包括对称加密非对称加密算法两类。


  • 对称加密算法:是使用相同的密钥进行加密和解密。使用对称加密算法来加密双方的通信的话,双方需要先约定一个密钥,加密方才能加密,接收方才能解密。如果密钥在发送的时候被窃取,那么加密就是白忙一场。因此,这种加密方式的特点是,加密速度比较快,但是密钥传输分发有泄露风险。



  • 非对称加密算法:或者叫公钥密码算法。公钥密码是由一对密钥对构成的,使用公钥或者说加密密钥来加密,使用私钥或者说解密密钥来解密,公钥可以任意公开,私钥不能公开。使用非对称加密的话,通信双方可以仅分享公钥用于加密,加密后的数据没有私钥无法解密。因此,这种加密方式的特点是,加密速度比较慢,但是解决了密钥的配送分发安全问题。



但是,对于保存敏感信息的场景来说,加密和解密都是我们的服务端程序,不太需要考虑密钥的分发安全性,也就是说使用非对称加密算法没有太大的意义。我们一般会使用对称加密算法来加密数据。常见的对称加密算法有:DES3DESAES






课间休息,欣赏一下来自咱们群里同学的搬砖工地附近,坐标:成都 太古里


作者:唐伯虎点香烟




面试题 2:怎么控制用户请求的幂等性的?


==================================================================================


幂等性:对于同一笔业务操作,不管调用多少次,得到的结果都是一样的。


当然,有些操作是天然幂等的,如:


  • 查询操作:查询一次和查询多次,在数据不变的情况下,查询结果是一样的。select 是天然的幂等操作;

  • 删除操作:删除操作也是幂等的,删除一次和多次删除都是把数据删除。


后台控制幂等性的几种途径:


  1. 设置唯一索引:防止新增脏数据


比如支付宝的资金账户,支付宝也有用户账户,每个用户只能有一个资金账户,怎么防止给用户创建资金账户多个,那么给资金账户表中的用户 ID 加唯一索引,所以一个用户新增成功一个资金账户记录。要点:唯一索引或唯一组合索引来防止新增数据存在脏数据(当表存在唯一索引,并发时新增报错时,再查询一次就可以了,数据应该已经存在了,返回结果即可);


  1. token机制:防止页面重复提交


原理上通过 session token 来实现的(也可以通过 redis 来实现)。当客户端请求页面时,服务器会生成一个随机数 Token,并且将Token放置到session当中,然后将 Token 发给客户端(一般通过构造 hidden 表单)。下次客户端提交请求时,Token会随着表单一起提交到服务器端


服务器端第一次验证相同过后,会将session中的Token值更新下,若用户重复提交,第二次的验证判断将失败,因为用户提交的表单中的 Token 没变,但服务器端 session 中 Token 已经改变了。


  1. 悲观锁


获取数据的时候加锁获取。


select * from table_xxx where id='xxx' for update;


注意:id 字段一定是主键或者唯一索引,不然是锁表,会死人的;悲观锁使用时一般伴随事务一起使用,数据锁定时间可能会很长,根据实际情况选用;


  1. 乐观锁


乐观锁只是在更新数据那一刻锁表,其他时间不锁表,所以相对于悲观锁,效率更高。乐观锁的实现方式多种多样,可以通过 version 或者其他状态条件判断:


  • 通过版本号实现


update table_xxx set name=#name#,version=version+1 where version=#version#;


  • 通过条件限制


update table_xxx set avai_amount=avai_amount where avai_amount >= 0


  1. 分布式锁


如果是分布式系统,构建全局唯一索引比较困难,例如唯一性的字段没法确定,这时候可以引入分布式锁,通过第三方的系统(redis 或 zookeeper),在业务系统插入数据或者更新数据,获取分布式锁,然后做操作,之后释放锁,这样其实是把多线程并发的锁的思路,引入多多个系统,也就是分布式系统中得解决思路。


要点:某个长流程处理过程要求不能并发执行,可以在流程执行之前根据某个标志(用户 ID+后缀等)获取分布式锁,其他流程执行时获取锁就会失败,也就是同一时间该流程只能有一个能执行成功,执行完成后,释放分布式锁(分布式锁要第三方系统提供);





北京 西二旗早高峰




面试题 3:你们是如何预防 SQL 注入问题的?


====================================================================================


SQL 注入攻击的总体思路


  1. 寻找到 SQL 注入的位置

  2. 判断服务器类型和后台数据库类型

  3. 针对不通的服务器和数据库特点进行 SQL 注入攻击


预防方式:


1、PreparedStatement(简单有效)


采用预编译语句集,它内置了处理 SQL 注入的能力,只要使用它的 setXXX 方法传值即可。


sql 注入只对 sql 语句的准备(编译)过程有破坏作用,而PreparedStatement在执行阶段只是把输入串作为数据处理,不再对sql语句进行解析,因此也就避免了 sql 注入问题。


2、使用正则表达式过滤传入的参数


要引入的包:


import java.util.regex.*;


正则表达式:


private String CHECKSQL = “^(.+)\sand\s(.+)|(.+)\sor(.+)\s$”;


判断是否匹配:


Pattern.matches(CHECKSQL,targerStr);


下面是常用来过滤参数是否存在SQL注入的正则表达式:


  • 检测 SQL meta-characters 的正则表达式 : /(\%27)|(\')|(\-\-)|(\%23)|(#)/ix

  • 修正检测 SQL meta-characters 的正则表达式 : /((\%3D)|(=))[^\n]*((\%27)|(\')|(\-\-)|(\%3B)|(:))/i

  • 典型的 SQL 注入攻击的正则表达式 : /\w*((\%27)|(\'))((\%6F)|o|(\%4F))((\%72)|r|(\%52))/ix

用户头像

Java高工P7

关注

还未添加个人签名 2021.11.08 加入

还未添加个人简介

评论

发布
暂无评论
Java岗大厂面试百日冲刺 - 日积月累,每日三题【Day36】—