Этот блог является ответом на вопрос читателя. Сейчас, как правило, я не делаю запрос блогов, но я подумал, что это звучит как интересная тема. Вопрос относится к моему блогу от 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


