Один из способов сделать то, что вы описываете, - это отследить URL-адреса перед отправкой их вашему клиенту, используя хеш-функцию, которая включает в себя секретный перец . Идея состоит в том, что, если клиент изменяет и URL-адрес, отпечаток пальца больше не будет совпадать, и поскольку отпечаток пальца генерируется с секретом на стороне сервера, клиент не сможет сгенерировать новый, соответствующий новому URL-адресу.
Вот пример.
Пользователь получит доступ к /foo
и передаст пользовательский параметр data
. Приложение будет использовать этот параметр для создания настраиваемого URL-адреса в формате /my/secret/:one/path/:two
и перенаправления клиента на него. Это просто для простоты, и тот же подход может быть применен, если сгенерированный URL будет использоваться как <a href="...">
на странице.
Сгенерированный URL-адрес содержит отпечаток, и если клиент изменяет либо URL-адрес, либо отпечаток, либо если отпечаток отсутствует, приложение ответит 403.
Давайте посмотрим на код. Маршруты:
Rails.application.routes.draw do
get :foo, to: 'example#foo'
get '/my/secret/:one/path/:two', to: 'example#bar', as: 'bar'
end
И контроллер:
class ExampleController < ApplicationController
# GET /foo?data=xx foo_path
#
def foo
user_data = request[:data]
go_to_path = bar_path(one: user_data, two: "foobar")
go_to_path += "?check=#{url_fingerprint(go_to_path)}"
redirect_to go_to_path
end
# GET /my/secret/:one/path/:two bar_path
#
def bar
unless valid_request?
render plain: "invalid request!", status: 403
return
end
render plain: "this is a good request", status: 200
end
private
SECRET = ENV.fetch("URL_FINGERPRINT_SECRET", "default secret")
# Calculate the fingerprint of a URL path to detect
# manual tampering.
#
# If you want to restrict this to a single client, add
# some unique identifier stored in a cookie.
#
def url_fingerprint(path)
Digest::SHA2.hexdigest(path + SECRET)
end
def valid_request?
return false unless params[:check].present?
params[:check] == url_fingerprint(request.path)
end
end
При этом клиент будет начинать с:
$ curl -i -s http://localhost:3000/foo?data=hello | grep Location
Location: http://localhost:3000/my/secret/hello/path/foobar?check=da343dd84accb4c0f5f7ff1d6e68152ac124ca1a39ce4746623bcb7b9043cab3
А потом:
curl -i http://localhost:3000/my/secret/hello/path/foobar?check=da343dd84accb4c0f5f7ff1d6e68152ac124ca1a39ce4746623bcb7b9043cab3
HTTP/1.1 200 OK
this is a good request
Но если URL будет изменен:
curl -i http://localhost:3000/my/secret/IWASCHANGED/path/foobar?check=da343dd84accb4c0f5f7ff1d6e68152ac124ca1a39ce4746623bcb7b9043cab3
HTTP/1.1 403 Forbidden
invalid request!
То же самое произошло бы, если бы сам отпечаток был модифицирован или его не было.