Этот блог является ответом на вопрос читателя. Сейчас, как правило, я не делаю запрос блогов, но я подумал, что это звучит как интересная тема. Вопрос относится к моему блогу от 11 июля о доступе к параметрам запроса с помощью Spring 3 MVC и был «Знаете ли вы, как кодировать uuid, чтобы он не был виден пользователю из соображений безопасности?». Ответ «Да», но это сделало бы блог очень коротким.
В этом случае я предполагаю, что речь идет об использовании URL-адресов RESTful для поддержания информации о состоянии сеанса между страницами. REST — это тема, о которой написано много слов, но, говоря об этом очень упрощенно, я определяю ее как не хранение информации о сеансе или состоянии на вашем веб-сервере между вызовами браузера. Проще говоря, это означает не использовать реализации интерфейса HttpSession. Не поддержание состояния на сервере дает вам как минимум два очевидных преимущества; первое улучшение загрузки. Это связано с тем, что ваш сервер не использует память для хранения данных о состоянии, которые могут или не могут быть необходимы. Второе преимущество — масштабирование. Если вы используете домен с несколькими серверами,тогда связывание состояния сеанса с одним сервером проблематично, поскольку обычно вы не можете гарантировать, что последующие последовательные вызовы домена будут направлены на один и тот же физический сервер.
Существуют различные методы, используемые для решения этой проблемы, включая создание всего на основе форм и хранение информации о состоянии в скрытых полях, использование файлов cookie и управление URL-адресами. В этом блоге демонстрируется третий метод шифрования данных о состоянии и маркирования их в конце URL-адреса, чтобы они передавались между браузером и сервером в качестве параметра запроса:
http://www.mywebsite.com/thepageIwant?session=ThisIsEncodedData
Примером сценария для этого метода будет сайт электронной коммерции. В этом случае вы выберете пару предметов и добавите их в корзину. Затем вы должны войти в систему и просмотреть свою корзину, а затем, возможно, изменить адрес доставки, прежде чем приступить к оплате. Делая все это, вы концептуально остаетесь в системе на сервере, но в мире RESTful сервер вас не помнит.
Этот пример использует три экрана, чтобы продемонстрировать эту идею. Первый экран регистрирует вас, второй содержит URL-адрес, содержащий некоторые данные сеанса, а третий отображает данные сеанса для просмотра.
Снимок экрана выше показывает простой экран входа в систему. Обратите внимание, что я использовал открытый текстовый пароль только для демонстрационных целей.
Второй экран в этом сценарии — это тот, который содержит зашифрованную информацию о сеансе; а именно имя пользователя и пароль, которые были отображены и приклеены к концу href тега привязки.
Последний экран показывает расшифрованную информацию о сеансе, демонстрирующую, что мы все еще получили и что это правильно.
Переходя к фактическому Java-коду для этой демонстрации … Простой бин ниже, я назвал Session, и он содержит все данные о состоянии, которые мы хотим кодировать.
public class Session { private static final String SEP = "=/="; private String name; private String password; public Session() { // Blank } public Session(String combined) { String[] split = combined.split(SEP); name = split[0]; password = split[1]; } public String getName() { return name; } public void setName(String username) { this.name = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } @Override public String toString() { return name + SEP + password; } }
Если вы посмотрите на приведенный выше код, вы увидите, что он содержит две функции, которые дают ему контекст в приложении. ToString (…) возвращает строку, которую приложение может закодировать, и конструктор с одним аргументом может создать полный компонент из своего аргумента.
Это работает в связке с кодом контроллера, который показан ниже …
@Controller public class RewriteController { private static final String ENCODING_KEY = "ThisWillBeTheKey"; /** * Create the initial blank form */ @RequestMapping(value = "/loginform", method = RequestMethod.GET) public String getCreateForm(Model model) { model.addAttribute("Session", new Session()); return "login.page"; } /** * This is where you login... */ @RequestMapping(value = "/login", method = RequestMethod.POST) public String login(Session userDetails, Model model) throws UnsupportedEncodingException { byte[] encodedBytes = encodeURL(userDetails); String encodedURL = new String(Base64.encodeBase64(encodedBytes)); model.addAttribute("encodedURL", encodedURL); return "display.encoded.url"; } private byte[] encodeURL(Session userDetails) throws UnsupportedEncodingException { RC4Lite rc4 = getRC4Lite(); byte[] in = userDetails.toString().getBytes("UTF8"); byte[] out = new byte[in.length]; rc4.encrypt(in, 0, out, 0, in.length); return out; } private RC4Lite getRC4Lite() throws UnsupportedEncodingException { RC4Lite rc4 = new RC4Lite(); rc4.setKey(ENCODING_KEY.getBytes("UTF8")); return rc4; } /** * This is where you're still in the REST session and re-validate the user */ @RequestMapping(value = "/decode", method = RequestMethod.GET) public String someSessionMethod( @RequestParam(value = "session") String sessionParam, Model model) throws UnsupportedEncodingException { byte[] encodedBytes = Base64 .decodeBase64(sessionParam.getBytes("UTF8")); String decodedString = decodeURL(encodedBytes); Session session = new Session(decodedString); model.addAttribute(session); return "display.decoded.url"; } private String decodeURL(byte[] encodedBytes) throws UnsupportedEncodingException { RC4Lite rc4 = getRC4Lite(); byte[] out = new byte[encodedBytes.length]; rc4.decrypt(encodedBytes, 0, out, 0, encodedBytes.length); return new String(out); } }
Приведенный выше класс контроллера содержит три метода-обработчика. Первый метод-обработчик, getCreateForm (…), довольно прост и просто добавляет Session-компонент в модель перед отображением имени входа в форме. Второй метод-обработчик, login (…),
— это место, где происходит все действие. Кодирование данных сеанса состоит из двух частей (имя и пароль). Первая часть заключается в шифровании данных, и для этого я просто позаимствовал свой класс RC4Lite из своего
блога RC4 Encryption 1 . После шифрования байты RC4 необходимо будет преобразовать в ASCII, и для этого я использовал класс Base64 Apache, как описано в
моем блоге base64., Преобразование в ASCII гарантирует, что символы печатаются и не вызывают никаких проблем. Затем строка добавляется в модель, где она прикреплена к тегу привязки, как показано в следующем фрагменте JSP:
<a href='decode?session=${encodedURL}' } ><spring:message code="label.the.next.page"/></a>
Последний метод-обработчик someSessionMethod (…) получает входные данные из вышеуказанной ссылки, декодирует их с помощью обратного процесса кодирования. Затем создает новый объект Session и помещает его в модель, которая затем отображается на последней странице.
Помните, что это только пример, он просто демонстрирует идею шифрования информации о сеансе и добавления URL-адреса в качестве параметра. Здесь вы можете внести улучшения, например: использование перехватчика для аутентификации на сервере при каждом запросе перед выполнением бизнес-логики в контроллере или использование лучшего класса шифрования, но в сущности этот принцип применим к любому веб-приложению. Есть и другие методы, которые вы можете использовать для достижения безопасной веб-связи, так что подробнее об этом позже …
1 Есть более эффективные способы шифрования, чем в RC4, он используется здесь только для удобства в этой демонстрации.
С http://www.captaindebug.com/2011/09/using-restful-urls-on-your-spring-3-mvc.html