②、表单页面增加隐藏域存储tokenId
③、提交表单,后端进行是否重复判断
上面主要是利用一次回话中session域存储的数据是保持不变的,而request域只能保存一次请求的数据。
注意:页面首先要通过 servlet 进行跳转过去,不能直接访问jsp页面。先在 servlet 中生成一个 tokenId,然后将tokenId存入到session域中,在转发到jsp表单页面,在表单页面中,通过隐藏域存放生成的tokenId,然后点击提交按钮,会将隐藏域的tokenId 也一起提交到后端。后端首先判断表单中的tokenId值,以及和session域中的tokenId 值进行对比,表单中的tokenId为null,则说明是直接访问的jsp页面,session域中的tokenId 为null,则说明不是第一次提交,因为第一次提交成功之后会清空session域中的tokenId。都不为null,且两者不相等,则说明可能是伪造的tokenId;不为null,且相等,则说明是第一次提交。
这里要注意销毁session域中的tokenId时机,是在判断完是否重复提交的方法中最后就销毁了,这样可以防止还没销毁session域中的tokenId,客户端的请求又来了。
5、session共享问题
通过上面前后端的解决表单重复提交的问题,我们看似解决了,其实不然,对于各种分布式项目,为了解决高并发的问题,我们会将前端请求通过 nginx 负载到多个tomcat服务器,如下:
这里会存在这样一个问题:
首先通过 tomcat1 将请求跳转到表单页面,这时候tokenId 是存放在tomcat1 session域中,然后点击提交按钮,nginx 可能会将我们的请求分发到 tomcat2 上,而tomcat2 的session 域中是不存在 tokenId 的,这时候我们提交不了表单。
这也是session共享问题。也就是说我们必须找到一个存放 tokenId 的公共介质,无论是哪个服务器去处理请求,都是从公共介质中获取 tokenId,那么当然不会存在tokenId 不一致的问题。
解决办法:
①、利用数据库同步:也就说将 tokenId 存放在数据库中,每次获取的时候从数据库中查询,这能解决,但是对数据的访问压力增大,不太合适。
②、利用 cookie 同步:因为 cookie 是存在本地客户端的,第一次请求我们将tokenId 存放在cookie中,然后从cookie进行是否重复提交校验,这也能解决问题。但是cookie 存在安全性问题,而且每次http请求都要带上参数也增加了带宽消耗。
③、利用 Redis 同步:这是最好的一种办法,Redis是一个高性能缓存框架,我们将 tokenId 存放在Redis中,获取也从Redis中获取,而且Redis性能极佳。