Java面试题冲刺第二十六天--实战编程

目录
  • 面试题1:你们是怎样保存用户密码等敏感数据的?
  • 面试题2:怎么控制用户请求的幂等性的?
    • 1.设置唯一索引:防止新增脏数据
    • 2.token机制:防止页面重复提交
    • 3.悲观锁
    • 4.乐观锁
    • 5.分布式锁
  • 面试题3:你们是如何预防SQL注入问题的?
    • 预防方式:
      • 1、PreparedStatement(简单有效)
      • 2、使用正则表达式过滤传入的参数
      • 3.使用正则表达式过滤传入的URL
  • 总结

面试题1:你们是怎样保存用户密码等敏感数据的?

本题回答参考朱晔的《Java业务开发常见错误100例》

我们知道,用户名、密码、身份证等都属于用户敏感信息,其中最敏感的数据恐怕就是用户的密码了。黑客一旦窃取了用户密码,就可以登录进用户的账号,消耗其资产、发布不良信息等;更可怕的是,有些用户至始至终都是使用一套密码,密码一旦泄露,就可以被黑客通过撞库来登录全网各大平台,嘿嘿嘿。

为了防止密码泄露,最重要的原则是不要在数据库保存用户原始密码。

大家经常说,不要明文保存用户密码,应该把密码通过 MD5 加密后保存。这的确是一个正确的方向,但这个说法并不准确。

首先,MD5 其实不是真正的加密算法。所谓加密算法,是可以使用密钥把明文加密为密文,随后还可以使用密钥解密出明文,是双向的。而 MD5 是散列、哈希算法或者摘要算法。不管多长的数据,使用 MD5 运算后得到的都是固定长度的摘要信息或指纹信息,无法再解密为原始数据。所以,MD5 是单向的。最重要的是,仅仅使用 MD5 对密码进行摘要,并不安全。

比如,使用如下代码在保持用户信息时,对密码进行了 MD5 计算:

UserData userData = new UserData();
userData.setId(1L);
userData.setName(name);
//密码字段使用MD5哈希后保存
userData.setPassword(DigestUtils.md5Hex(password));
return userRepository.save(userData);

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

"password": "325a2cc052914ceeb8c19016c091d2ac"

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

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

  • 因为盐太短、太简单了,如果用户原始密码也很简单,那么整个拼起来的密码也很短,这样一般的 MD5 破解网站都可以直接解密这个 MD5,除去盐就是原始密码了。
  • 相同的盐,意味着使用相同密码的用户 MD5 值是一样的,知道了一个用户的密码就可能知道了多个。
  • 黑客也可以使用这个盐来构建一张彩虹表,也就是字典表,虽然会花不少代价,但是一旦构建完成,所有人的密码都可以被破解。

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

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

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

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

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

userData.setSalt(UUID.randomUUID().toString());
userData.setPassword(DigestUtils.md5Hex(userData.getSalt() + password));

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

3.悲观锁

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

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

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

4.乐观锁

乐观锁只是在更新数据那一刻锁表,其他时间不锁表,所以相对于悲观锁,效率更高。乐观锁的实现方式多种多样,可以通过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

5.分布式锁

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

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

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

SQL注入攻击的总体思路

  • 寻找到SQL注入的位置
  • 判断服务器类型和后台数据库类型
  • 针对不通的服务器和数据库特点进行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
  • 检测SQL注入,UNION查询关键字的正则表达式 : /((\%27)|(\'))union/ix(\%27)|(\')

3.使用正则表达式过滤传入的URL

比较通用的一个方法,jsp中调用该函数检查是否包函非法字符,防止SQL从URL注入:

(||之间的参数可以根据自己程序的需要添加)

public static boolean sql_inj(String str){
    String inj_str = "'|and|exec|insert|select|delete|update|
    count|*|%|chr|mid|master|truncate|char|declare|;|or|-|+|,";
    String inj_stra[] = split(inj_str,"|");
    for (int i=0 ; i < inj_stra.length ; i++ ){
        if (str.indexOf(inj_stra[i])>=0){
        return true;
        }
    }
    return false;
}

总结

本篇文章就到这里了,希望能给你带来帮助,也希望您能够多多关注我们的更多内容!

(0)

相关推荐

  • java实战小技巧之字符串与容器互转详解

    目录 前言 I. 字符串转列表 1. jdk支持方式 2. guava方式 3. apache-commons II. 列表转字符串 1. StringBuilder 2. String.join 3. gauva III. 小结 总结 前言 每天一个实战小技巧:字符串与Collection的互转 将字符串转换为List,这种业务场景可以说非常非常常见了,实现方式也比较简单 public List<String> str2list(String str, String split) { Str

  • 利用Java对PDF文件进行电子签章的实战过程

    目录 一. 概述 二. 技术选型 三. 生成一个图片签章 1. 生成一个如下图的签章图片 2. 相关代码 四. 如何按模板生成PDF文件 1. 制作PDF模板 2. 制作一个如下图的PDF模板,该模板是带有PDF的表单域的 五. 如何生成PKCS12证书 1. PKCS的简单介绍 2. 使用JAVA生成一个PKCS12证书并进行存贮,相关分析见代码注解 六. 如何生成一个高清晰的签章 1. 由PDF模板生成一个PDF文件,见代码注解 2. 对PDF文件进行签章 3. 高清签章 七. 如何进行多次

  • java实战小技巧之优雅的实现字符串拼接

    目录 前言 String底层原理 1. 普通写法 2. StringJoiner 3. guava joiner 4. 小结 总结 前言 字符串拼接不管是在业务上,还是写算法时都会频繁使用到. 相信没有小伙伴没有写过这样的代码,比如说现在让我们来实现一个字符串拼接的场景,怎样的实现才算是优雅的呢? 以将int数组转为英文逗号分隔的字符串为例进行演示 String底层原理 在讨论字符串拼接时,首先需要知道String的底层原理. 看下结构 private final byte[] value; 这

  • Java协程编程之Loom项目实战记录

    目录 前提 Loom项目简单介绍 Virtual Thread使用 小结 前提 之前很长一段时间关注JDK协程库的开发进度,但是前一段时间比较忙很少去查看OpenJDK官网的内容.Java协程项目Loom(因为项目还在开发阶段,OpenJDK给出的官网https://openjdk.java.net/projects/loom中只有少量Loom项目相关的信息)已经在2018年之前立项,目前已经发布过基于JDK17编译和JDK18编译等早期版本,笔者在下载Loom早期版本的时候只找到JDK18编译

  • Java实战小技巧之数组与list互转

    目录 前言 I. 数组转 List 1. Array.asList 1.1 知识点 2. new ArrayList 2.1 避雷预警 3. Collections.addAll II. 列表转数组 III. 小结 总结 前言 这个考题比较常见,也比较简单,难道就这也有什么可以说到的门路不成? 接下来本文好好的说一说它的几种实现姿势,总有一款你喜欢的 I. 数组转 List 1. Array.asList 这个考题太简单了,直接使用Array.asList不就完事了么,比如 @Test publ

  • Java面试题冲刺第二十六天--实战编程

    目录 面试题1:你们是怎样保存用户密码等敏感数据的? 面试题2:怎么控制用户请求的幂等性的? 1.设置唯一索引:防止新增脏数据 2.token机制:防止页面重复提交 3.悲观锁 4.乐观锁 5.分布式锁 面试题3:你们是如何预防SQL注入问题的? 预防方式: 1.PreparedStatement(简单有效) 2.使用正则表达式过滤传入的参数 3.使用正则表达式过滤传入的URL 总结 面试题1:你们是怎样保存用户密码等敏感数据的? 本题回答参考朱晔的<Java业务开发常见错误100例> 我们知

  • Java面试题冲刺第二十六天--实战编程2

    目录 面试题2:怎么理解负载均衡的?你处理负载均衡都有哪些途径? 1.[协议层]http重定向 2.[协议层]DNS轮询 3.[协议层]CDN 4.[协议层]反向代理负载均衡 5.[网络层]IP负载均衡 面试题3:你平时是怎样定位线上问题的? 总结 面试题1:当你发现一条SQL很慢,你的处理思路是什么? 发现Bug 确定Bug不是自己造成的,如果无法确定,不要理会步骤1 向组内宣传"程序里有一个未知Bug,错不在我" 谁响应,谁对Bug负责 没人响应,就要求特定人员配合调试 如果不配合

  • Java面试题冲刺第二十五天--实战编程2

    目录 面试题2:怎么理解负载均衡的?你处理负载均衡都有哪些途径? 1.[协议层]http重定向 2.[协议层]DNS轮询 3.[协议层]CDN 4.[协议层]反向代理负载均衡 5.[网络层]IP负载均衡 面试题3:你平时是怎样定位线上问题的? 总结 面试题1:当你发现一条SQL很慢,你的处理思路是什么? 发现Bug 确定Bug不是自己造成的,如果无法确定,不要理会步骤1 向组内宣传"程序里有一个未知Bug,错不在我" 谁响应,谁对Bug负责 没人响应,就要求特定人员配合调试 如果不配合

  • Java面试题冲刺第二十四天--并发编程

    目录 面试题1:说一下你对ReentrantLock的理解? CAS: AQS: 追问1:你认为 ReentrantLock 相比 synchronized 都有哪些区别? 面试题2:解释一下公平锁和非公平锁? 面试题3:能详细说一下CAS具体实现原理么? 追问1:那CAS的缺陷有哪些呢? 1.ABA: 2.自旋消耗资源: 3.多变量共享一致性问题: 追问2:讲一下什么是ABA问题?怎么解决? 总结 面试题1:说一下你对ReentrantLock的理解? ReentrantLock是JDK1.5

  • Java面试题冲刺第二十五天--并发编程3

    目录 面试题1:你了解线程池么?简单介绍一下. 追问1:连接池 和 线程池是一个意思么?有什么区别? 不同点 面试题2:线程池中核心线程数量大小你是怎么设置的? 追问1:核心线程数量过大或过小会造成什么后果? 面试题3:线程池都有哪些状态呀? 追问1:什么条件下会进入TERMINATED状态 总结 面试题1:你了解线程池么?简单介绍一下. java提供的一个java.util.concurrent.Executor接口的实现用于创建线程池. 线程池是一种多线程处理形式,处理过程中将任务提交到线程

  • Java面试题冲刺第二十五天--JVM2

    目录 面试题1:简单说一下java的垃圾回收机制. 面试题2:JVM会在什么时候进行GC呢? 追问1:介绍一下不同代空间的垃圾回收机制 追问2:能说一下新生代空间的构成与执行逻辑么? 追问3:说一下发生OOM时,垃圾回收机制的执行流程. 面试题3:Full GC .Major GC和 Minor GC有什么不同 (1)Minor GC / Young GC (2)Old GC (3)Full GC (4)Major GC (5)Mixed GC 总结 面试题1:简单说一下java的垃圾回收机制.

  • Java面试题冲刺第二十九天--JVM3

    目录 面试题1:如何判断对象是否存活 1.引用计数算法 2.可达性分析算法 面试题2:哪些对象可以作为GC Roots? 面试题3:你了解的对象引用方式都有哪些? 1 强引用 2 软引用 3 弱引用 4 虚引用 总结 面试题1:如何判断对象是否存活 对于判断对象是否存活,主要是两种基本算法,引用计数和可达性分析,目前java主要采用的是可达性分析算法 1.引用计数算法 判断对象是否存活的方式如:在对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加一:当引用失效时,计数器值就减一:任何

  • Java面试题冲刺第十六天--消息队列

    目录 面试题1:说说你对消息队列的理解,消息队列为了解决什么问题? 解耦 异步 削峰 追问1:消息队列有什么优缺点 面试题2:对于消息中间机,你们是怎么做技术选型的? 面试题3:如何确保消息正确地发送至 RabbitMQ?如何确保消息接收方消费了消息? 发送方确认模式 接收方确认机制 追问1:如何保证MQ消息的可靠传输? 总结 面试题1:说说你对消息队列的理解,消息队列为了解决什么问题? 我们公司业务系统一开始体量较小,很多组件都是单机版就足够,后来随着用户量逐渐扩大,我们程序也采用了微服务的设

  • Java面试题冲刺第二十五天--并发编程1

    目录 面试题1:简单说下你对线程和进程的理解? 正经回答: 深入追问: 追问1:那进程和线程有哪些区别呢? 面试题2:守护线程和用户线程的区别? 正经回答: 面试题3:什么是线程死锁? 正经回答: 深入追问: 追问1:形成死锁的四个必要条件是什么? 追问2:我们该如何避免死锁? 追问3:死锁避免和死锁预防有啥不同? 总结 面试题1:简单说下你对线程和进程的理解? 正经回答: 进程 一个在内存中运行的应用程序.每个进程都有自己独立的一块内存空间,一个进程可以有多个线程,比如在Windows系统中,

  • Java面试题冲刺第二十五天--并发编程2

    目录 面试题1:简单说下你对线程和进程的理解? 正经回答: 深入追问: 追问1:那进程和线程有哪些区别呢? 面试题2:守护线程和用户线程的区别? 正经回答: 面试题3:什么是线程死锁? 正经回答: 深入追问: 追问1:形成死锁的四个必要条件是什么? 追问2:我们该如何避免死锁? 追问3:死锁避免和死锁预防有啥不同? 总结 面试题1:简单说下你对线程和进程的理解? 正经回答: 进程 一个在内存中运行的应用程序.每个进程都有自己独立的一块内存空间,一个进程可以有多个线程,比如在Windows系统中,

随机推荐