Статьи

Путь от ACEGI до Spring Security 2.0


Ранее называвшийся
ACEGI Security for Spring, обновленный бренд Spring Security 2.0
обещал упростить его использование и повысить производительность труда разработчиков.
Spring Security 2.0,
уже считавшаяся наиболее
широко используемой платформой корпоративной безопасности для платформы Java и имеющей
более 250 000 загрузок
, предоставляет множество новых функций.

В этой статье описывается, как преобразовать существующее приложение Spring на основе ACEGI для использования Spring Security 2.0.

Что такое Spring Security 2.0

Spring Security 2.0 недавно был выпущен в качестве замены ACEGI и предоставляет множество новых функций безопасности:

  • Существенно упрощенная конфигурация.
  • Интеграция OpenID, единый вход по стандарту.
  • Поддержка Windows NTLM, единый вход в корпоративные сети Windows.
  • Поддержка аннотаций безопасности JSR 250 («EJB 3»).
  • Поддержка языка выражений AspectJ pointcut.
  • Комплексная поддержка авторизации веб-запросов RESTful.
  • Давно запрашиваемая поддержка групп, иерархических ролей и API управления пользователями.
  • Улучшенная реализация «запомнить меня» на основе базы данных.
  • Новая поддержка авторизации веб-состояний и переходов через выпуск Spring Web Flow 2.0.
  • Улучшенная поддержка WSS (ранее WS-Security) в выпуске Spring Web Services 1.5.
  • Намного больше …

Цель

В настоящее время я работаю над веб-приложением Spring, которое использует ACEGI для контроля доступа к защищенным ресурсам. Пользователи хранятся в базе данных, и поэтому мы настроили ACEGI для использования службы UserDetails на основе JDBC. Аналогично, все наши веб-ресурсы хранятся в базе данных, и ACEGI настроен на использование пользовательского AbstractFilterInvocationDefinitionSource для проверки деталей авторизации для каждого запроса.
С выпуском Spring Security 2.0 я хотел бы посмотреть, смогу ли я заменить ACEGI и сохранить текущую возможность использовать базу данных в качестве нашего источника аутентификации и авторизации вместо файлов конфигурации XML (как демонстрирует большинство примеров).

Вот шаги, которые я предпринял …

меры

  1. Первым (и самым хитрым) шагом была загрузка новой среды Spring Security 2.0 Framework и проверка того, что файлы JAR развернуты в правильном месте. ( / WEB-INF / lib / )
    Существует 22 файла jar, которые поставляются с загрузкой Spring Security 2.0. Мне не нужно было использовать их все (особенно не пакеты * sources). Для этого упражнения мне нужно было только включить:
    • весна-безопасности ACL-2.0.0.jar
    • весна-безопасности ядро-2.0.0.jar
    • весна-безопасность ядро-тигр-2.0.0.jar
    • весна-безопасность-библиотека теги-2.0.0.jar
  2. Настройте DelegatingFilterProxy в файле web.xml.
    <filter>
    <filter-name>springSecurityFilterChain</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    </filter>
    <filter-mapping>
    <filter-name>springSecurityFilterChain</filter-name>
    <url-pattern>/*</url-pattern>
    </filter-mapping>
  3. Конфигурация Spring Security 2.0 гораздо более лаконична, чем ACEGI, поэтому вместо изменения моего текущего файла конфигурации на основе ACEGI мне было проще начать с пустого файла. Если вы хотите изменить существующий файл конфигурации, я уверен, что вы будете удалять больше строк, чем добавлять.

    Первая часть конфигурации должна указывать детали для фильтра защищенных ресурсов, это позволяет считывать защищенные ресурсы из базы данных, а не из фактического файла конфигурации. Это пример того, что вы увидите в большинстве примеров:

    <http auto-config="true" access-denied-page="/403.jsp">
    <intercept-url pattern="/index.jsp" access="ROLE_ADMINISTRATOR,ROLE_USER"/>
    <intercept-url pattern="/securePage.jsp" access="ROLE_ADMINISTRATOR"/>
    <intercept-url pattern="/**" access="ROLE_ANONYMOUS" />
    </http>

    Замените это на:

    <authentication-manager alias="authenticationManager"/>

    <beans:bean id="accessDecisionManager" class="org.springframework.security.vote.AffirmativeBased">
    <beans:property name="allowIfAllAbstainDecisions" value="false"/>
    <beans:property name="decisionVoters">
    <beans:list>
    <beans:bean class="org.springframework.security.vote.RoleVoter"/>
    <beans:bean class="org.springframework.security.vote.AuthenticatedVoter"/>
    </beans:list>
    </beans:property>
    </beans:bean>

    <beans:bean id="filterInvocationInterceptor" class="org.springframework.security.intercept.web.FilterSecurityInterceptor">
    <beans:property name="authenticationManager" ref="authenticationManager"/>
    <beans:property name="accessDecisionManager" ref="accessDecisionManager"/>
    <beans:property name="objectDefinitionSource" ref="secureResourceFilter" />
    </beans:bean>

    <beans:bean id="secureResourceFilter" class="org.security.SecureFilter.MySecureResourceFilter" />

    <http auto-config="true" access-denied-page="/403.jsp">
    <concurrent-session-control max-sessions="1" exception-if-maximum-exceeded="true" />
    <form-login login-page="/login.jsp" authentication-failure-url="/login.jsp" default-target-url="/index.jsp" />
    <logout logout-success-url="/login.jsp"/>
    </http>

    Основной частью этой части конфигурации является secureResourceFilter , это класс, который реализует FilterInvocationDefinitionSource и вызывается, когда Spring Security необходимо проверить полномочия для запрашиваемой страницы.
    Вот код для MySecureResourceFilter :

    package org.security.SecureFilter;

    import java.util.Collection;
    import java.util.List;

    import org.springframework.security.ConfigAttributeDefinition;
    import org.springframework.security.ConfigAttributeEditor;
    import org.springframework.security.intercept.web.FilterInvocation;
    import org.springframework.security.intercept.web.FilterInvocationDefinitionSource;


    public class MySecureResourceFilter implements FilterInvocationDefinitionSource {

    public ConfigAttributeDefinition getAttributes(Object filter) throws IllegalArgumentException {

    FilterInvocation filterInvocation = (FilterInvocation) filter;

    String url = filterInvocation.getRequestUrl();

    // create a resource object that represents this Url object
    Resource resource = new Resource(url);

    if (resource == null) return null;
    else{
    ConfigAttributeEditor configAttrEditor = new ConfigAttributeEditor();
    // get the Roles that can access this Url
    List<Role> roles = resource.getRoles();
    StringBuffer rolesList = new StringBuffer();
    for (Role role : roles){
    rolesList.append(role.getName());
    rolesList.append(",");
    }
    // don't want to end with a "," so remove the last ","
    if (rolesList.length() > 0)
    rolesList.replace(rolesList.length()-1, rolesList.length()+1, "");
    configAttrEditor.setAsText(rolesList.toString());
    return (ConfigAttributeDefinition) configAttrEditor.getValue();
    }
    }

    public Collection getConfigAttributeDefinitions() {
    return null;
    }

    public boolean supports(Class arg0) {
    return true;
    }

    }

    Этот метод getAttributes (), приведенный выше, по существу возвращает имя Authorities (которые я называю Roles), которым разрешен доступ к текущему URL.

  4. Итак, теперь мы настроили ресурсы на основе базы данных, и теперь следующим шагом является получение Spring Security для чтения сведений о пользователе из базы данных. Примеры, поставляемые с Spring Security 2.0, показывают, как сохранить список пользователей и прав доступа в файле конфигурации следующим образом:
    <authentication-provider>
    <user-service>
    <user name="rod" password="password" authorities="ROLE_SUPERVISOR, ROLE_USER" />
    <user name="dianne" password="password" authorities="ROLE_USER,ROLE_TELLER" />
    <user name="scott" password="password" authorities="ROLE_USER" />
    <user name="peter" password="password" authorities="ROLE_USER" />
    </user-service>
    </authentication-provider>

    Вы можете заменить эти примеры этой конфигурацией
    так что вы можете читать данные пользователя прямо из базы данных, например:

    <authentication-provider>
    <jdbc-user-service data-source-ref="dataSource" />
    </authentication-provider>

    Хотя это очень быстрый и простой способ настройки безопасности баз данных, это означает, что вы должны соответствовать схеме баз данных по умолчанию. По умолчанию для <jdbc-user-service> требуются следующие таблицы: пользователь, полномочия, группы, group_members и group_authorities.
    В моем случае это не сработало, поскольку моя схема безопасности не совпадает с той, что требуется для <jdbc-user-service> , поэтому я был вынужден изменить <authentication-provider> :

    <authentication-provider>
    <jdbc-user-service data-source-ref="dataSource"
    users-by-username-query="SELECT U.username, U.password, U.accountEnabled AS 'enabled' FROM User U where U.username=?"
    authorities-by-username-query="SELECT U.username, R.name as 'authority' FROM User U JOIN Authority A ON u.id = A.userId JOIN Role R ON R.id = A.roleId WHERE U.username=?"/>
    </authentication-provider>

    Добавляя свойства users-by-username-query и полномочия-by-username-query, вы можете переопределить операторы SQL по умолчанию своими собственными. Как и в случае безопасности ACEGI, вы должны убедиться, что столбцы, которые возвращает ваш оператор SQL, совпадают с ожидаемыми в Spring Security. Есть еще одно свойство group-authority-by-username-query, которое я не использую и поэтому исключило его из этого примера, но оно работает точно так же, как и два других оператора SQL.

    Эта особенность <jdbc-user-service>был включен только в прошлом месяце или около того и не был доступен в предварительных версиях Spring Security. К счастью, это было добавлено, поскольку это делает жизнь намного проще. Вы можете прочитать об этом здесь и здесь .

    Bean-компонент dataSource указывает, к какой базе данных подключаться, он не включен в мой файл конфигурации, поскольку не относится к безопасности. Вот пример bean-компонента dataSource для тех, кто не уверен:

    	<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
    <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql://localhost/db_name?useUnicode=true&characterEncoding=utf-8"/>
    <property name="username" value="root"/>
    <property name="password" value="pwd"/>
    </bean>
  5. И это все для конфигурации Spring Security. Моей последней задачей было изменить мой текущий экран входа в систему. В ACEGI вы можете создать свой собственный вход <form> , убедившись, что вы поместили правильно названные элементы ввода HTML в правильный URL-адрес. Хотя вы все еще можете сделать это в Spring Security 2.0, некоторые имена изменились.
    Вы по-прежнему можете вызывать свое поле имени пользователя j_ username и поле пароля j_password, как и раньше.
    <input type="text" name="j_username" id="j_username"/>
    <input type="password" name="j_password" id="j_password"/>

    Однако вы должны установить свойство action вашей <form> так, чтобы оно указывало на j_spring_security_check, а не на j_acegi_security_check .

    <form method="post" id="loginForm" action="<c:url value='j_spring_security_check'/>"

    В нашем приложении есть несколько мест, где пользователь может выйти из системы, это ссылка, которая перенаправляет запрос на выход в инфраструктуру безопасности, чтобы он мог обрабатываться соответствующим образом. Это должно быть изменено с j_acegi_logout на j_spring_security_logout .

    <a href='<c:url value="j_spring_security_logout"/>'>Logout</a>

Вывод

Это краткое руководство о том, как настроить Spring Security 2.0 с доступом к ресурсам, хранящимся в базе данных, не приближается к иллюстрации множества новых функций, доступных в Spring Security 2.0, однако я думаю, что оно показывает некоторые из наиболее часто используемых возможности фреймворка и я надеюсь, что вы найдете его полезным.

Одним из преимуществ Spring Security 2.0 по сравнению с ACEGI является возможность писать больше файлов конфигурации consice, это ясно видно, когда я сравниваю мой старый файл конфигурации ACEGI ( 172 строки) с моим новым ( 42 строки).
Вот мой полный файл securityContext.xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/security" 
xmlns:beans="http://www.springframework.org/schema/beans" 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
xsi:schemaLocation="http://www.springframework.org/schema/beans,http://www.springframework.org/schema/beans/spring-beans-2.0.xsd,http://www.springframework.org/schema/security,http://www.springframework.org/schema/security/spring-security-2.0.xsd"> <authentication-manager alias="authenticationManager"/>  <beans:bean id="accessDecisionManager" class="org.springframework.security.vote.AffirmativeBased"> <beans:property name="allowIfAllAbstainDecisions" value="false"/> <beans:property name="decisionVoters"> <beans:list> <beans:bean class="org.springframework.security.vote.RoleVoter"/> <beans:bean class="org.springframework.security.vote.AuthenticatedVoter"/> </beans:list> </beans:property> </beans:bean> <beans:bean id="filterInvocationInterceptor" class="org.springframework.security.intercept.web.FilterSecurityInterceptor"> <beans:property name="authenticationManager" ref="authenticationManager"/> <beans:property name="accessDecisionManager" ref="accessDecisionManager"/> <beans:property name="objectDefinitionSource" ref="secureResourceFilter" /> </beans:bean>  <beans:bean id="secureResourceFilter" class="org.security.SecureFilter.MySecureResourceFilter" /> <http auto-config="true" access-denied-page="/403.jsp">  <concurrent-session-control max-sessions="1" exception-if-maximum-exceeded="true" /> <form-login login-page="/login.jsp" authentication-failure-url="/login.jsp" default-target-url="/index.jsp" /> <logout logout-success-url="/login.jsp"/> </http>  <beans:bean id="loggerListener" class="org.springframework.security.event.authentication.LoggerListener"/>  <authentication-provider> <jdbc-user-service data-source-ref="dataSource" users-by-username-query="SELECT U.username, U.password, U.accountEnabled AS 'enabled' FROM User U where U.username=?" authorities-by-username-query="SELECT U.username, R.name as 'authority' FROM User U JOIN Authority A ON u.id = A.userId JOIN Role R ON R.id = A.roleId WHERE U.username=?" /> </authentication-provider> </beans:beans>

Как я сказал в шаге 1, загрузка Spring Security была самым хитрым шагом из всех. Оттуда это было просто плавание …