Статьи

Использование RESTful URL в вашем Spring 3 MVC Webapp

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