User Tools

Site Tools


java_security

На главную

Реализация системы безопасности в Java

Всё нижеописанное исходит из предположения что в Sun хорошо подумали когда
проектировали пермишены, секьюрити-менеджер и всё с ними связанное.

О чём это вообще

В типичном веб-приложении далеко не каждый пользователь может менять любые данные. У кого-то есть доступ только на чтение, кто-то может менять только свои данные, кто-то только данные пользователей из определённой группы или находящиеся в определённом состоянии. Возникает задача разграничивания прав. Есть несколько вариантов её решения. Вариант первый - логика разрешения/запрещения жёстко задаётся в коде. Существует жёстко заданный набор групп с наборами правам. Этот подход работает только до тех пор пока не возникает необходимости изменить эти наборы. Другой подход связан с динамическим созданием групп и динамическим же назначением им прав. И логика по проверке просто проверяет, есть ли у текущего пользователя право на то действие, которое он пытается совершить.

Что защищать

Любые данные.

Зачем защищать

Чтобы разделять, кому что можно, а кому что нельзя делать.

Аутентификация

Несортированный трэш

    //Authorization init
    Policy.setPolicy(new Policy()
    {
      public boolean implies(final ProtectionDomain domain, final Permission permission)
      {
        for (final Principal principal : domain.getPrincipals()) {
          if (principal instanceof MyPrincipal) {
            //получить откуда-то коллекцию прав принципала и вызвать для них implies, передав в качестве аргумента проверяемый пермишен.
          }
        }
        return false;
      }
    });
    //Authentication init
    final Configuration orig = Configuration.getConfiguration();
    Configuration.setConfiguration(new Configuration()
    {
      public AppConfigurationEntry[] getAppConfigurationEntry(final String name)
      {
        return (name.equals("MY_APP")) ?
            new AppConfigurationEntry[]{
                new AppConfigurationEntry(
                    "com.myApp.security.MyLoginModule", //наша реализация LoginModule.
                    AppConfigurationEntry.LoginModuleControlFlag.REQUISITE,
                    Collections.<String, Object>emptyMap()
                )
            } : orig.getAppConfigurationEntry(name);
      }
 
      public void refresh()
      {
        orig.refresh();
      }
    });
    System.setSecurityManager(new SecurityManager());
    //Аутентификация
    final String name = "login";
    final String password = "password";
    final LoginContext ctx = new LoginContext("MY_APP", new CallbackHandler()
    {
      public void handle(final Callback[] callbacks) throws IOException, UnsupportedCallbackException
      {
        for (final Callback cb : callbacks)
        {
          if (cb instanceof NameCallback)
          {
            ((NameCallback) cb).setName(name);
          }
          else if (cb instanceof PasswordCallback)
          {
            ((PasswordCallback) cb).setPassword(password.toCharArray());
          }
          else
          {
            throw new UnsupportedCallbackException(cb);
          }
        }
      }
    });
    ctx.login();
    //выполнение кода в контексте аутентифицированного пользователя
    Subject.doAs(ctx.getSubject(), new PrivilegedAction<Object>()
    {
      public Object run()
      {
        //Do something
        return null;
      }
    });
    ctx.logout();

Авторизация

Краткое описание проверки пермишена

В Java существует встроенный механизм прав.

Компоненты системы авторизации

  • SecurityManager - просто фасад над всей системой прав.
  • AccessControlContext - контекст с информацией о том, в каких ProtectionDomain'ах выполняется сейчас код.
  • ProtectionDomain - содержит информацию об URL'ах (возможно с сертификатами), из которых получен выполняющийся код, ClassLoader, через который этот код был получен и (внимание) коллекцию Principal'ов, от имени которых код выполняется.
  • Policy - хранилище пермишенов, которое определяет какие права у кого есть.

Внедрение своих правил авторизации

Principal

Скорее всего у вас в приложении есть класс User. Нужно будет написать реализацию Principal'а для него (Внимание! Не надо делать так чтобы ваш User реализовывал Principal'а, т.к. отсутствие юзера не означает отсутствие Principal'а).

Примерная реализация
public class UserPrincipal implements java.security.Principal {
  // ------------------------------ FIELDS ------------------------------
  /**
   * Константа для незарегистрированного пользователя.
   */
  private static final UserPrincipal nullUserPrincipal = new UserPrincipal (null);
  private final User user;
 
  // -------------------------- STATIC METHODS --------------------------
 
  public static UserPrincipal getPrincipal (final User user) {
    return user == null ? nullUserPrincipal : new UserPrincipal (user);
  }
 
  // --------------------------- CONSTRUCTORS ---------------------------
 
  protected UserPrincipal (final User user) {
    super ();
    this.user = user;
  }
 
  // ------------------------ CANONICAL METHODS ------------------------
 
  public boolean equals (final Object obj) {
    if (obj == null || !(obj instanceof UserPrincipal)) {
      return false;
    }
    final UserPrincipal o = (UserPrincipal) obj;
    final User myUser = this.getUser ();
    final User oUser = o.getUser ();
    if (myUser == null && oUser == null) {
      return true;
    } else if (myUser != null && oUser != null) {
      return myUser.equals (oUser);
    } else {
      return false;
    }
  }
 
  public int hashCode () {
    return this.getClass ().hashCode () + (this.user == null ? 13 : this.user.hashCode ());
  }
 
  // ------------------------ INTERFACE METHODS ------------------------
 
  // --------------------- Interface Nullable ---------------------
 
  public boolean isNull () {
    return this.user == null;
  }
 
  // --------------------- Interface Principal ---------------------
 
  public String getName () {
    return this.getClass ().getName () + " " + (this.user == null ? "unregistered" : this.getUser ().getUserId ());
  }
 
  // -------------------------- OTHER METHODS --------------------------
 
  public User getUser () {
    return this.user;
  }
}

Policy

Policy - это как раз то, куда надо всовывать свой код, чтобы делать проверку прав по своей базе, а не по стандартной реализации в policy-файле. Делается это просто - пишется свой класс, наследующийся от Policy (внимание, если в вашем приложении права могут динамически изменяться, то надо переопределять метод implies, т.к. в стандартной реализации он активно занимается кэшированием пермишенов.

ВНИМАНИЕ Считается, что право разрешено, если оно разрешено для ВСЕХ ProtectionDomain'ов, находящихся в AccessControlContext'е вызывающего кода.

Собираем вместе

Установка Policy
Policy.setPolicy (new MyPolicy ());
Включение SecurityManager'а
if (System.getSecurityManager () != null) {
  System.setSecurityManager (new SecurityManager ());
}
Вызов защищённого кода
try {
  final Subject subject = ...;
  final T result = Subject.doAs (subject, new PrivilegedExceptionAction<T>() {
    public Object run () throws IOException, ServletException {
      //выполнить какой-то код, защищённый правами.
    }
  });
} catch (PrivilegedActionException e) {
  ...
}
SecurityUtils

Пара методов для удобства.

public final class SecurityUtils {
  public static boolean hasPermission (final Permission permission) {
    try {
      checkPermission (permission);
      return true;
    } catch (final SecurityException e) {
      return false;
    }
  }
 
  public static void checkPermission (final Permission permission) throws SecurityException {
    if (System.getSecurityManager () != null) {
      System.getSecurityManager ().checkPermission (permission);
    }
  }
}

Альтернативы

Permissions Evangelizm

FIXME

JAAS vs Acegi Security

Java Security (JAAS)

Является стандартом, поддерживается большим количеством технологий (EJB, Servlets, etc)

Простой API

Хорошо документирован

Довольно скудная базовая функциональность

Ограниченное встроенное кэширование (не предполагает изменения набора прав в процессе работы приложения), в случае отключения которого необходимо реализовывать кэширование на уровне приложения

Отсутствуют теги для проверки прав к различным элементам интерфейса (однако возможно их написание)

Политики принятия решений необходимо реализовывать вручную

В некоторых случаях контейнеро-зависим (однако возможно написание контейнеро-независимого кода)

Acegi Security

Хорошо документирован

Простая интеграция со Spring

Встроенные аудиты

Встроенные интерцепторы (например, для удаления из коллекций недоступных объектов)

Различные политики принятия решений (если все полиси разрешают, если хоть одна полиси разрешает, на основе голосования и очков)

Встроенные теги для проверки прав к различным элементам интерфейса (правда примитивные)

Встроенное кэширование

Контейнеро-независим

Встроенная поддержка опции «запомнить меня»

Множество адаптеров для различных механизмов аутентификации

Многократно более сложный API

Не специфицирован

При этом следует учитывать, что имеется возможность оборачивать использование сторонним кодом JAAS’а в обращения к Acegi и наоборот. Так же необходимо понимать, что права виртуальной машины (доступ к файлам, сокетам, работа с класслоадерами и т.д. проверяются через JAAS, поэтому при использовании Acegi в любом случае будет необходимо направлять в него обращения к JAAS).

java_security.txt · Last modified: 2019/06/12 16:08 by 127.0.0.1