如何打造一个安全的服务接口

4 篇文章 0 订阅

相关文章

从数据库层谈安全措施

这篇文章是通过对话的形式展现,也是我第一次尝试这样的风格。不知道阅读起来的感觉和以前 这种感觉哪种好,希望多多指点。

正文

老A: 老大交代下来任务了,要写一套接口服务,用来查询订单的。主要接口有这么几个,登录、注册、查询用户订单、添加用户订单、删除用户订单、查询用户信息、修改用户信息、注销。小Z,这个就交给你做了,先把接口文档写一下。ヽ(ー_ー)ノ

小Z: 好的没问题,交给我吧。()

十分钟之后·····

小Z :接口文档好了,老A你看一下。()

登录接口
接口地址/login.do
请求方法post
请求参数username /password
返回内容User对象
注册接口
接口地址/regist.do
请求方法post
请求参数username /password /nickname
返回内容User对象
查询用户订单
接口地址/orderList.do
请求方法get
请求参数userId
返回内容OrderList对象
添加用户订单
接口地址/addOrder.do
请求方法post
请求参数Order对象
返回内容成功/失败
删除用户订单
接口地址/removeOrder.do
请求方法post
请求参数orderId
返回内容成功/失败
查询用户信息
接口地址/getUser.do
请求方法get
请求参数userId
返回内容User对象
修改用户信息
接口地址/editUser.do
请求方法post
请求参数User对象
返回内容User对象
注销
接口地址/logout.do
请求方法post
请求参数userId
返回内容成功/失败

老A看完后,语重心长的对小Z说:“写接口,不光要考虑功能的实现,你还得考虑一下安全方面啊!”ヽ(ー_ー)ノ

小Z道:“我也考虑到了,请求使用HTTPS,提交的用post方法,获取的用get方法,这样应该比较安全吧。” !!!∑(゚Д゚ノ)ノ

老A:“那我问你,你的登录和后面的接口有什么关系?” o(´^`)o

小Z:“登录进来获取到user对象,user里有userId,通过userId请求后面的数据。不登录是没有的” ─=≡Σ(((つ•̀ω•́)つ

老A:“那么如果我知道UserId,是不是意味着我不需要登录就可以获取到数据了?” <( ̄ ﹌  ̄)>

小Z:“不登录你怎么获取?” (〃‘▽’〃)

老A:“userId是不是不变的,那我上次登录过后我记录下来UserId,下次再用可不可以?我换个电脑还用这个userId,是不是还能获取到数据?”
(▼ヘ▼#)

小Z:“好像是这么个情况,那我该怎么办呢?” ⊙(・◇・)?

老A:“不是好像,就是这么个情况!登录请求不是仅仅为了验证密码返回用户信息的,而是登录要管理起来后面所有跟用户权限有关的接口,不登录是不能用的。
我们登陆接口中可以加上一个token验证机制,就是说,如果登录成功,我们随机生成一串字符,把这个字符作为key,关联存储user对象。然后我们返回这个key。之后的所有请求都需要携带这个key,后面的请求过来后,先通过key查找缓存中是否有User对象。如果有,说明已经登录过了,就有权限访问,反之则没有权限访问。
同时,由于我们使用了key关联user对象,那么我们也不需要返回user对象。后面的请求也不需要携带user对象中的信息过来。因为我们缓存中已经存储了user对象,通过key查了出来,我们直接使用查出来的user对象作为参数就可以了。这样也避免了user对象的泄露。
key也可以添加有效时间的信息,比如说我们可以记录上一次请求key的时间,然后下一次请求就有了一个现在的时间。如果说登录操作有效时间是2小时,那么就可以拿现在的时间减去上一次请求的时间,得到一个差值,然后比较这个差值是否小于2小时,如果小于的话则是在有效时间内,允许请求。同时更新一下最后一次请求时间为当前时间,下一次请求继续判断。如果是超时了,就可以将缓存的user给删掉,直接返回错误,重新登录。用户退出也是一样的,删除掉缓存的user对象,那么以后的请求就找不到user对象,就不允许请求了

小Z:“不愧是老A,我醍醐灌顶,豁然开朗。我甘拜下风,五体投地。对您的崇拜犹如滔滔江水连绵不绝” (。♥ᴗ♥。)

老A:“别拍马屁了,还不快回去改接口” (▼ヘ▼#)

小Z:“好的师傅,马上就去”

又十分钟后。。。

小A:“我的接口改完了,再看一下吧”

登录接口
接口地址/login.do
请求方法post
请求参数username /password
返回内容token
注册接口
接口地址/regist.do
请求方法post
请求参数username /password /nickname
返回内容成功
查询用户订单
接口地址/orderList.do
请求方法get
请求参数token
返回内容OrderList对象
添加用户订单
接口地址/addOrder.do
请求方法post
请求参数Order对象、token
返回内容成功/失败
删除用户订单
接口地址/removeOrder.do
请求方法post
请求参数orderId、token
返回内容成功/失败
查询用户信息
接口地址/getUser.do
请求方法get
请求参数token
返回内容User对象
修改用户信息
接口地址/editUser.do
请求方法post
请求参数User对象、token
返回内容User对象
注销
接口地址/logout.do
请求方法post
请求参数token
返回内容成功/失败

老A:用户登录验证是解决了,但这还不够安全。

小Z:“这还不够安全?那还缺什么呢?”

老A:“你怎么知道给你发消息的就是就是真的用户呢?或者说,你怎么知道给你接口进行请求的,是你想让请求的客户端呢?”

小Z:“这个··· 我还真不知道请求是不是用户发的,我用请求工具填这些参数也是可以请求成功的,也就是说请求是可以被伪造的。”

老A:“是的,你这脑袋还算灵光”

小Z:“那该如何解决呢?”

老A:“我们可以使用验签机制,来防止参数的伪造篡改
验签的机制是,将参数进行某个规则的加密,这个规则只有你的服务端和客户端知道,参数中除了正常的请求参数外,多了一个验签参数 sign。后台拿到请求参数后,可以将参数用同样的方法加密,然后与sign比对,如果一致就认为没有被篡改。

小Z:“那如果别人直接拿抓包的数据进行请求,参数不变,那不是没什么作用吗?他甚至都不用知道加密规则是什么”

老A:“孺子可教也,知道思考了。为了应对这个问题,我们可以在请求中再加一个参数,时间戳”

小Z:“我明白了。请求参数和时间戳一起加密,因为时间戳是变化的,所以说加密出来的结果也是变化的。同时请求参数中带着时间戳参数,服务端可以根据时间戳来限制单次请求的有效时间,比如说单次请求1分钟内有效。那么之后的请求,就算原封不动的拿过来,因为时间戳的限制,不是当时的请求就是无效的,获取不到数据。然而要修改参数中的时间戳,又跟加密验签sign不一致,也得不到返回。这一招真是高啊

老A:“还有更高的呢。如果说黑客通过你的参数和解密后的结果,得到了加密规则,你咋办?”

老A:“拿到原文和密文,想要破译加密规则也不是不可能的事情。那么如果我们呈现出来的不是原文,那是不是就更安全一些了?”

小Z:“您快别绕关子了,到底是该怎么做?”

老A:“你也不多动动脑袋。服务端个客户端都是我们自己的程序,或者说都是我们授权的。那么我就可以在服务端设置一个参数secret。然后我们把secret告诉给客户端。当然不是走网络 请求,而是通过线下的方式,比如说发邮件啊啥的。然后客户端会把secret存在自己本地,服务器也存一个secret。加密的时候,将secret拼接在要加密的字符串上,secret本身不进行网络传输。这样,在原文中就有一个不可见的字符,别人通过网络也拿不到真正的原文。这样破解起来就不容易了

小Z:“老A你真是天才啊!”

老A:“什么天才,我也是慢慢学的。这也不是我第一个发明的。”

小Z:“那么我们还可以设置加密秘钥,服务端和客户端知道,也不参与传输。这样也更安全。而且加密不用选择对称加密,采用MD5这种非对称加密就可以,因为服务器不需要解密,服务器只用通过相同的加密规则得到一个相同的结果就可以了

老A:“学会举一反三了,不错不错。其实 我们也可以产生动态秘钥,用两个渠道传输。比如说以前银行会用一个动态令牌,你要登录网银就需要动态令牌的验证码。”

小Z:“其实现在的手机验证码也感觉像是这种,通过短信这个渠道去发送验证码,也提高了安全性。”

老A:“不错嘛,还想到了手机短信。不过一般是这种操作只有在登录的时候使用。因为虽然是安全,但是用户操作麻烦。为了提高用户的便捷性,只有在关键时刻才会使用,比如登录,比如转账这些。”

小Z:“难怪呢,这下对于 安全又了更深刻的认识。”

老A:“还有一些能够提高安全的你可以做的,那就是传输内容加密和混淆。”

小Z:“加密和混淆?”

老A:“对。你看你现在的接口,参数名别人一看就知道是啥,username这不就是用户名?你的请求参数不也直接明文,别人一眼就看到你传的值?你返回的内容User,别人不也就看到了用户信息?别人也不需要仿造请求,只是进行一个拦截,就看到了数据。”

小Z:“说的对啊,这么重要的事情给忘记了,这就需要双方进行一个对称加密规则,和名称定义了。对称加密的秘钥同样的也需要线下放到客户端和服务端两边。”

老A:“哈哈哈,脑袋转的挺快”

小Z:“那我回去改改,您再来看”

叕十分钟后

登录接口
接口地址/login.do
请求方法post
请求参数u 用户名/n 密码
返回内容加密的json字符 token
注册接口
接口地址/regist.do
请求方法post
请求参数s 用户名/ a 密码 /e 昵称
返回内容成功
查询用户订单
接口地址/orderList.do
请求方法get
请求参数tn
返回内容加密的json字符 OrderList对象
添加用户订单
接口地址/addOrder.do
请求方法post
请求参数加密的json字符 Order对象、tn
返回内容成功/失败
删除用户订单
接口地址/removeOrder.do
请求方法post
请求参数od 订单id、tn
返回内容成功/失败
查询用户信息
接口地址/getUser.do
请求方法get
请求参数tn
返回内容加密的json字符 User对象
修改用户信息
接口地址/editUser.do
请求方法post
请求参数加密的json字符 User对象、tn
返回内容成功
注销
接口地址/logout.do
请求方法post
请求参数tn
返回内容成功/失败

小Z:“这下够安全了吧”

老A:“这是比之前安全等级高了很多,但不能说百分百安全。比如以前的SQL注入,就是直接将SQL写到参数中,让查询结果直接返回。当然现在是很少了,也不能说没有。我们都会对入参和返回值做处理,很少有直接返回的情况,你应该没这么干吧?”

小Z:“我当然知道,我的Dao层是用的MyBatis,传参用的还是 # 号,#号 就是为了防止SQL注入的”

老A:“那你知道为什么#号能防止SQL注入么?”

小Z:“我还真不知道,您给我讲讲吧”

老A:“唉,看样子你知道SQL注入这回事,但也是没真正明白。以后换了Hibernate或者其他的,你该咋办?你这还只是考虑java开发,安全是要考虑通用性的。你看上面的接口,是不是并不关心你用什么语言实现?”

小Z:“哎呀,您老先别教训我了,我这如饥似渴的小眼神瞅着您,您快说说。”

老A:“SQL注入,说白了就是在参数中写SQL语句,然后后台服务操作数据库时,参数中的SQL语言直接当SQL运行了,就查出来了数据。其实防范也很简单,就是不让参数当SQL语句执行,而是当成字符串。mybatis的#号,实际上就是将传入的参数在SQL中显示为字符串,这样SQL语句就不会被执行。同时,返回的结果也需要进行处理,不能直接返回,这样就避免了SQL注入

小Z:“原来如此啊”

老A:“除了考虑接口数据的安全外,还需要考虑稳定性、可靠性。这也是安全要考虑的事情。因为不是所有的黑客攻击都是冲着你的数据来的,也有就是冲着你破坏你来的。让你的服务失效,不能用。比如DDoS攻击,就是 通过大规模的访问,让你的服务拥挤不堪,最后崩溃。这种情况下就该考虑限制请求和提高服务器并发量等去解决了”

小Z:“原来安全有这么多事情啊”

老A:“安全方面,还有很多事情呢,慢慢学吧”

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值