我已经进行了几个月的网络开发并且一直存在这个棘手的问题。页面通常使用查询字符串来请求内容,查询字符串通常包含有意义的数据,例如数据库中的id。一个例子是一个链接,例如:
http://www.example.com/posts?id=5
我一直在努力想出一个好的策略来阻止用户手动输入id的值,而无需从链接访问它 - 我只想确认我的链接所提出的请求网站。此外,该网站可能没有身份验证系统,并允许匿名浏览;话虽如此,信息并不是特别敏感,但我仍然不喜欢无法控制对某些信息的访问。我想,一种选择是对这类页面使用HTTP POST请求 - 我不相信用户可以模拟帖子请求,但我可能错了。
此外,用户可以为id放置任意数字,并最终请求数据库中不存在的记录。当然,我可以验证所请求的ID,但之后我会浪费资源来适应这种检查。
有什么想法?我正在使用django,但任何编程语言的一般策略都会很好。感谢。
答案 0 :(得分:4)
首先,在GET和POST之间进行选择:用户可以模拟任何类型的请求,因此POST不会帮助您。在两者之间进行选择时,最好根据用户采取的操作决定或者他们如何与您的内容进行互动。他们是在获取页面还是向您发送数据(表格是一个明显的例子)?对于您检索某种帖子的情况,GET是合适的。
另外值得注意的是,如果内容适合书签,GET是正确的选择。仅根据引荐来源提供URL - 正如您所说,“防止用户手动输入id的值而无需从链接访问它” - 这是一个可怕的想法。这会给你带来无数令人头疼的问题,对用户来说可能不是一个很好的体验。
作为一般原则,避免依赖数据库记录的主键。该密钥(在您的情况下为id = 5)应该纯粹作为自动增量字段来处理,以防止记录冲突,即保证表中的所有记录始终具有唯一字段。该ID字段是后端实用程序。不要将其暴露给您的用户,也不要自己依赖它。
如果你不能使用ID,你会用什么?一个常见的习惯用法是使用记录的日期, slug 或两者。如果您正在处理帖子,请使用已发布/创建的日期。然后添加一个文本字段,该字段将包含URL友好和描述性单词。称之为slug并阅读有关Django的models.SlugField的更多信息。另外,请参阅基本上任何新闻网站上的文章的URL。您的最终网址将类似于http://www.example.com/posts/2012/01/19/this-is-cool/
现在你的网址很友好,有谷歌搜索引擎优化的好处,是书签能力,是不可猜测的。因为您不依赖于后端数据库修复任意ID,所以您可以自由地...还原备份数据库转储,移动数据库,将自动增量数字ID更改为UUID哈希等等。只有您的数据库会关心,而不是您作为程序员而不是您的用户。
哦,不要过分担心用户“请求不存在的记录”或“验证所请求的ID”......无论如何你必须这样做。它不消耗不必要的资源。这是数据库支持的网站的工作方式。您必须将请求连接到数据。如果请求不正确,则为404.您的网络服务器针对不存在的URL执行此操作,您需要针对不存在的数据执行此操作。查看Django get_object_or_404()的想法/实施。
答案 1 :(得分:1)
我知道有两种方法可以有效地执行此操作,因为基本上没有办法阻止某人伪造任何请求。
第一个是不在查询参数中使用裸ID。相反,生成一个大的随机数,并使其链接。您必须在数据库中保留一个表,将随机数映射到它们所代表的实际ID,并且最终必须清理表。这实现起来相当简单,但需要一些存储空间,偶尔也会对存储数据进行一些管理。
第二种方法是在建立链接时对数据进行签名。通过在数据上附加加密签名,并在发出请求时验证签名,可以确保只有您的Web服务可能已创建链接。即使请求本身是“伪造的” - 可能是书签,写下,复制并粘贴到另一个浏览器 - 您知道您的网站已经授权该URL。
为此,您需要创建一个消息验证代码(MAC),其中包含您要签名的数据(例如,只是'id'值,或者可能是id和您签署数据的时间)以及您只保留在服务器上的密钥。
在您的视图中,您获取id值(或id和时间戳,如果这就是您正在使用的那个)并再次构造MAC,并查看它们是否匹配。如果有任何差异,您拒绝该请求已被篡改。
查看hmac module的python文档,以及所有详细信息的hashlib module。
您可以在python中生成一个链接:
settings.py:
hmac_secret_key = '12345'
views.py:
import time, hmac, hashlib
from django.conf import settings
def some_view(request):
...
id = 5
time = int(time.time())
mac = hmac.new(
settings.hmac_secret_key,
'%d/%d' % (id, time),
hashlib.sha1)
url = 'http://www.example.com/posts/id=%d&ts=%d&mac=%s' % (
id, time, mac.hexdigest())
# Now return a template with that url in it somewhere
要在另一个视图中验证它,你可以使用这样的代码:(警告,警告,不健壮,还有很多错误检查)
def posts_view(request):
id = int(request.GET['id'])
ts = int(request.GET['ts'])
mac_from_url = request.GET['mac']
computed_mac = hmac.new(
settings.hmac_secret_key,
'%d/%d' % (id, time),
hashlib.sha1)
if mac_from_url <> computed_mac:
raise SomeSecurityException()
# Now you know that the request is legit.
# You can check the timestamp here, too, if you like.
答案 2 :(得分:0)
我不知道这是否是正确的做法,但也许你可以保存他将在GET
请求后重定向到会话的网址,然后写一个decorator
所以如果会话有那个网址,那就把他带到那个页面。否则会出现404错误或其他内容。