Ранее называвшийся
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 (как демонстрирует большинство примеров).
Вот шаги, которые я предпринял …
меры
- Первым (и самым хитрым) шагом была загрузка новой среды 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
- Настройте 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> - Конфигурация 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.
- Итак, теперь мы настроили ресурсы на основе базы данных, и теперь следующим шагом является получение 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> - И это все для конфигурации 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 была самым хитрым шагом из всех. Оттуда это было просто плавание …