Как вы, должно быть, знаете, хранить пользовательские данные на клиентской стороне плохо: их ведь может подделать злостный хакер. Но сегодня я вам расскажу, как можно безопасно хранить сессионные данные, не волнуясь за их сохранность. Расскажу на примере Snap Framework, на котором и написан этот блог.
Суть метода — шифрование (кто бы мог подумать). Snap для своих сессий использует библиотеку Web.ClientSession, а поэтому что и как обрабатывается можно смело подсмотреть в документации:
- Шифруем данные куки с помощью AES в режиме CTR. Это позволяет хранить важные данные на стороне клиента, не беспокоясь о том, что их кто-нибудь украдет.
- Подписываем зашифрованные данные с помощью Skein-MAC-512-256. Кроме определения потенциальных ошибок в хранении и передачи данных (целостность), MAC предотвращает изменения данных и подтверждает, что они действительно были сгенерированны этим сервером (подлинность).
- Кодируем результат с помощью Base64, таким образом избавляемся от различных непечатных символов и отдаем браузеру простую строку.
Изучая всякие википедии о AES, я не особо понял, какие преимущества нам даст режим CTR в плане безопасности, но судя по описанию коммитов к библиотеке в этом режиме результат получается чуть более компактным и сам алгоритм чуть проще реализовывается. Про любой другой язык я могу сказать, что почти наверняка есть библиотека, которая уже реализует этот алгоритм за вас, так что сильно думать не придется.
Skein — это просто хороший алгоритм хеширования. К сожалению, он не победил в конкуре на замену SHA-2, и название SHA-3 получил другой алгоритм. Поэтому самое время заменить Skein на новоявленный SHA-3 — Keccak.
Ну и напоследок несколько мыслей. Во-первых, этот метод хранения несколько надежнее традиционных, т.к. никакой форс-мажор на сервере не позволит данным потеряться. А если что-нибудь произойдет со стороны клиента и кука исчезнет, то на сервере у нас не останется никаких мусорных данных об уже не существующей сессии. Во-вторых, таким образом не получится хранить большие объемы данных, т.к. размер куки ограничен, да и передача каждый раз пары десятков-сотен килобайт вполне может увеличить нагрузку на сервер. В-третьих, увести сессию можно точно так же, как и обыкновенную, поэтому увеличения безопасности тут не будет.
В общем, идея имеет право на жизнь. Особенно на небольших, как в моем случае, объемах хранимых данных. Лично я храню только одно поле: является ли текущий пользователь админом или нет.
Хозяйке на заметку: чтобы вашу сессию нельзя было увести с помощью JavaScript, добавьте в заголовок Set-Cookie
параметр HttpOnly
. Куки с таким параметром не появляются в document.cookie
.
Осталась одна загадка, связанная с моим сайтом. Кука _session
передается как сессионная и должна удаляться с закрытием браузера. Так и происходит в Opera, но не Google Chrome и Firefox. Может есть какая-то стандартизированная или неофициальная фича, которая не дает сессионным кукам удалиться?
Случилась печаль: мой компьютер решил больше со мной не разговаривать. И даже больше — решил вообще не включаться. Вот и приходится теперь довольствоваться телефоном. Я так думаю, что это блок питания решил отойти от дел и почить на заслуженном отдыхе. Но хотелось бы убедиться, что это именно он, перед тем как покупать новый. В любом случае, никаких улучшений на сайте в ближайшее время не предвидится.
Как же я люблю Microsoft. Вот, например, есть у них поисковик Bing (им, кстати, кто-нибудь пользуется?). Авторы crawler’ов для этого поисковика совершенно не умеют работать с UTF-8. Почему-то только они присылают запросы с неверно закодированными кириллическими буквами.
Пример. Есть страница «работа» :: [dikmax’s blog]. Везде в ссылках на эту страницу у меня написано <a href=”/tag/работа”>
.
Какую страницу запрашивают все браузеры и поисковики: /tag/%D1%80%D0%B0%D0%B1%D0%BE%D1%82%D0%B0
. А что же пытается получить msnbot: /tag/%D1%E2%82%AC%D0%B0%D0%B1%D0%BE%D1%E2%80%9A%D0%B0
.
D1 (incomplete sequence)
U-000020AC E2 82 AC
U-00000430 D0 B0
U-00000431 D0 B1
U-0000043E D0 BE
D1 (incomplete sequence)
U-0000201A E2 80 9A
U-00000430 D0 B0
Я не представляю, какое преобразование нужно было сделать, чтобы получить такую последовательность байт.
Решение понятно, нужно кодировать ссылки на стороне сервера. Но зачем, если и так всё работает? А Bing — не тот поисковик, под который стоит подстраиваться. Хотя когда-нибудь я поправлю и это.
Во всех известных поисковиках!
Все ваши комментарии!
Да, именно так, я сделал синхронизацию комментариев с Disqus. Теперь, даже если в вашем браузере отключен JavaScript, вы всё равно сможете прочитать всё.
Могу с уверенностью сказать, что Haskell — не самый подходящий язык для подобных вещей. На каком-нибудь php всё это можно было бы реализовать гораздо быстрее, но мы ведь не ищем легких путей. Самая большая проблема — работа с JSON. Библиотека для работы есть, и она неплохо реализована, но сказывается специфика Haskell и его статической типизации. Ну например,
{
"code":0,
"response":[
{
"forum":"dikmax",
"parent":667510883,
"author":{
"username":"google-1b379da98470c64a0c4bc8c2230d616d",
"about":"",
"name":"...",
"url":"",
"joinedAt":"2012-09-25T03:14:42",
"avatar":{
"permalink":"...",
...
},
...
},
"message":"...",
...
},
...
]
}
Приблизительный процесс разбора:
- Что делать, если результат не является правильным JSON.
- Что делать, если корневой узел — не объект.
- Что делать, если у объекта нет свойства
code
. - Если
code
— не является числом. - Если
code
всё-таки число, но не 0. - Если у объекта нет свойства
response
. - Если
response
— не массив. - Преобазовываем
response
в два массива: первый с комментариями, второй с ошибками разбора комментариев.
И когда какого-то «если» нет, компилятор выдаст ошибку и придется искать, какой вариант ты еще не предусмотрел. Т.е. компилятор заботится, чтобы твоя программа умела работать при любых данных. И это, конечно, огромный плюс программирования на Haskell.
Весь код для синхронизации. Получилось не очень много, но времени на него было потрачено прилично.