Задачи безопасности, такие как аутентификация пользователя и авторизация пользователя для просмотра ресурсов приложения, обычно выполняются сервером приложений. Эти задачи могут быть делегированы потоку безопасности Spring, освобождающему сервер приложений от выполнения этих задач. Spring security в основном решает эти задачи путем реализации стандартного javax.servlet.Filter. Для инициализации безопасности Spring в вашем приложении вам нужно объявить следующий фильтр в вашем файле web.xml:
1
2
3
4
5
6
7
8
9
|
< 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 > |
Теперь этот фильтр (springSecurityFilterChain) просто делегирует запрос в среду безопасности Spring, где определенные задачи безопасности будут обрабатываться фильтрами безопасности, определенными в контексте приложения. Так как это происходит?
Внутри метода doFilter DelegatingFilterProxy (реализация javax.servlet.Filter) контекст приложения Spring будет проверен на предмет bean-компонента с именем springSecurityFilterChain. Этот bean-компонент «springSecurityFilterChain» на самом деле является псевдонимом, определенным для цепочки весенних фильтров.
1
|
< alias name = "filterChainProxy" alias = "springSecurityFilterChain" /> |
Поэтому, когда проверка выполняется в контексте приложения, она возвращает bean-компонент filterChainProxy. Эта цепочка фильтров отличается от цепочки javax.servlet.FilterChain, которая используется фильтрами Java, определенными в web.xml, для вызова следующего возможного фильтра, если он существует, или передачи запроса в сервлет / jsp. Bean filterChainProxy состоит из упорядоченного списка фильтров безопасности, которые определены в контексте приложения Spring. Итак, вот следующий набор вопросов:
1. Кто инициализирует / определяет этот фильтр ChainProxy?
2. Какие фильтры безопасности определены в контексте приложения Spring?
3. Чем эти фильтры безопасности отличаются от обычных фильтров, определенных в web.xml?
Теперь перейдем к первому вопросу: filterChainProxy инициализируется, когда в контексте приложения определен элемент ‹http из пространства имен безопасности. Вот основная структура элемента ‹http:
01
02
03
04
05
06
07
08
09
10
11
12
|
< sec:http auto-config = "true" > < sec:intercept-url pattern = "/**" access = "ROLE_USER" /> </ sec:http > < sec:authentication-manager id = "authenticationManager" > < sec:authentication-provider > < sec:user-service > < sec:user name = "admin" password = "password" authorities = "ROLE_USER, ROLE_ADMIN" /> < sec:user name = "user" password = "password" authorities = "ROLE_USER" /> </ sec:user-service > </ sec:authentication-provider > </ sec:authentication-manager > |
Теперь HttpSecurityBeanDefinitionParser из среды Spring считывает этот ‹http-элемент для регистрации filterChainProxy в контексте приложения. Элемент http с автоматической настройкой, установленной в true, на самом деле является сокращенной записью для следующего:
1
2
3
4
5
|
< sec:http > < sec:form-login /> < sec:http-basic /> < sec:logout /> </ sec:http > |
Мы обсудим подэлементы ‹http› позже. Итак, теперь перейдем ко второму вопросу, что все фильтры регистрируются в цепочке фильтров по умолчанию? Вот ответ из весенней документации:
Блок <http> namespace всегда создает SecurityContextPersistenceFilter , ExceptionTranslationFilter и FilterSecurityInterceptor . Они исправлены и не могут быть заменены альтернативами.
Поэтому по умолчанию, когда мы добавляем ‹http› элемент, будут добавлены три вышеуказанных фильтра. И так как мы установили для auto-config значение true, BasicAuthenticationFilter, LogoutFilter и UsernamePasswordAuthenticationFilter также добавляются в цепочку фильтров. Теперь, если вы посмотрите на исходный код любого из этих фильтров, это также стандартные реализации javax.servlet.Filter. Но, определяя эти фильтры в контексте приложения, а не в файле web.xml, сервер приложений передает управление в Spring для решения задач, связанных с безопасностью. И SpringC filterChainProxy позаботится о цепочке фильтров безопасности, которые должны применяться по запросу. Это отвечает на третий вопрос.
Чтобы получить более точный контроль над фильтрами безопасности, которые должны применяться к запросу, мы можем определить нашу собственную реализацию FilterChainProxy.
1
2
3
4
5
6
7
|
< bean id = "filterChainProxy" class = "org.springframework.security.web.FilterChainProxy" > < sec:filter-chain-map path-type = "ant" > < sec:filter-chain pattern = "/images/*" filters = "none" /> < sec:filter-chain pattern = "/**" filters="securityContextFilter, logoutFilter, formLoginFilter, servletApiFilter, anonFilter, exceptionTranslator, filterSecurityInterceptor, customFilter1, customeFilter2" /> </ sec:filter-chain-map > </ bean > |
Из приведенного выше xml мы видим, что мы не хотим, чтобы какие-либо фильтры применялись к изображениям, тогда как для остальных запросов указан список фильтров, которые должны быть применены. Итак, в общем, мы указываем цепочки фильтров в порядке наименьшего ограничения на наиболее ограниченные. Но такого рода регистрация наших собственных цепочек фильтров, как правило, не требуется. Spring, через элемент ‹http, предоставляет несколько хуков, с помощью которых мы можем получить более точный контроль над тем, как применяется безопасность. Итак, рассмотрим подробно, что можно настроить через элемент ‹http.
1. Аутентификация: HttpBasicAuthentication и аутентификация на основе формы входа
2. Поддержка авторизации через ACL (список контроля доступа)
3. Выйти из службы поддержки
4. Поддержка анонимного входа
5. Запомнить меня
6. Параллельное управление сессиями
(1) Аутентификация: Аутентификация может быть обработана двумя способами — HttpBasicAuthentication и аутентификацией на основе формы входа. Мы кратко обсудим эти два вопроса. Прежде чем понять их, было бы хорошо иметь базовое понимание AuthenticationManager, которое лежит в основе реализации аутентификации с помощью Spring Security. Внутри элемента диспетчера аутентификации мы определяем всех поставщиков аутентификации, доступных для приложения. А поставщик аутентификации содержит реализацию UserDetailsService. Spring загружает информацию о пользователе в UserDetailsService и сравнивает комбинацию имени пользователя и пароля с учетными данными, указанными при входе в систему. Вот интерфейс UserDetailsService:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
package org.springframework.security.core.userdetails; import org.springframework.dao.DataAccessException; /** * Core interface which loads user-specific data. * It is used throughout the framework as a user DAO and is the strategy used by the * {@link org.springframework.security.authentication.dao.DaoAuthenticationProvider DaoAuthenticationProvider}. * The interface requires only one read-only method, which simplifies support for new data-access strategies. * @see org.springframework.security.authentication.dao.DaoAuthenticationProvider * @see UserDetails * @author Ben Alex */ public interface UserDetailsService { /** * Locates the user based on the username. In the actual implementation, the search may possibly be case * insensitive, or case insensitive depending on how the implementation instance is configured. In this case, the * <code>UserDetails</code> object that comes back may have a username that is of a different case than what was * actually requested.. * * @param username the username identifying the user whose data is required. * * @return a fully populated user record (never <code>null</code>) * * @throws UsernameNotFoundException if the user could not be found or the user has no GrantedAuthority * @throws DataAccessException if user could not be found for a repository-specific reason */ UserDetails loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException; } |
Spring предоставляет две встроенные реализации этого сервиса:
(a) Сохраните данные логина / пароля пользователя в контексте приложения:
Это хорошо подходит, когда пользователей приложения мало. Это можно инициализировать следующим образом:
1
2
3
4
5
6
7
8
|
< sec:authentication-manager id = "authenticationManager" > < sec:authentication-provider > < sec:user-service > < sec:user name = "admin" password = "password" authorities = "ROLE_ADMIN,ROLE_USER" /> < sec:user name = "user" password = "password" authorities = "ROLE_USER" /> </ sec:user-service > </ sec:authentication-provider > </ sec:authentication-manager > |
Тег ‹authentication-provider› соответствует DaoAuthenticationProvider, который фактически вызывает реализацию предоставляемой UserDetailsService. В этом случае мы предоставляем имена пользователей и пароли непосредственно в XML. Когда пользовательская база приложения огромна, мы бы предпочли хранить информацию в базе данных.
Соответствующий bean-компонент, который инициализируется для ‹user-service›, является org.springframework.security.core.userdetails.memory.InMemoryDaoImpl
(б) Хранение пользовательских данных в базе данных: вот как это должно быть инициализировано.
1
2
3
4
5
|
< sec:authentication-manager id = "authenticationManager" > < sec:authentication-provider > < sec:jdbc-user-service data-source-ref = "dataSource" /> </ sec:authentication-provider > </ sec:authentication-manager > |
Соответствующий класс в Spring — org.springframework.security.core.userdetails.jdbc.JdbcDaoImpl. Если вы посмотрите на этот класс, то обнаружите, что имя пользователя и пароль хранятся в таблице пользователей, а роли, которые могут быть назначены пользователям, хранятся в таблице полномочий. Мы поговорим о ролях позже. Вот запросы, которые этот класс выполняет для получения учетных данных и полномочий пользователей из базы данных:
1
2
3
4
|
-- Fetch user credentials: select username,password,enabled from users where username = ? -- Fetch user authorities: select username,authority from authorities where username = ? |
Теперь предположим, что у вас есть унаследованная база данных, в которой ваши пользовательские данные хранятся в некоторых других таблицах, а затем мы можем настроить запросы выборки, которые выполняет Spring для получения учетных данных и полномочий пользователя. Скажем, у меня есть таблица участника, в которой есть поля id, username, password и таблица Role, в которой есть поля username, role. Вот как мы должны настроить:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
|
< sec:authentication-manager id = "authenticationManager" > < sec:authentication-provider > <!-- TBD <password-encoder hash="md5"/> --> < sec:jdbc-user-service id = "userDetailsService" data-source-ref = "dataSource" users-by-username-query= "SELECT username, password, true as enabled FROM MEMBER WHERE username=?" authorities-by-username-query= "SELECT member.username, role.role as authorities FROM ROLE role, MEMBER member WHERE role.member_id =member.id and member.username=?"/> </ sec:authentication-provider > </ sec:authentication-manager > |
Теперь перейдем к способам выполнения аутентификации:
HttpBasicAuthentication: это можно настроить следующим образом:
1
2
3
|
< sec:http auto-config = "true" > < sec:http-basic /> </ sec:http > |
По умолчанию, когда это включено, браузер обычно отображает диалог входа в систему для входа пользователей. Вместо диалога входа в систему мы можем настроить его для отображения конкретной страницы входа. Этот вид аутентификации формально определен в стандарте протокола передачи гипертекста. Учетные данные для входа в систему (в кодировке Base 64) отправляются на сервер под заголовком HTTP Authentication. Но у этого есть свои недостатки. Самая большая проблема связана с выходом с сервера. Большинство браузеров, как правило, кэшируют сеансы, другой пользователь не может войти в систему, обновив браузер. Определение ‹http-basic› фактически определяет фильтр BasicAuthenticationFilter за кулисами. При успешной аутентификации объект аутентификации будет помещен в Spring securityContext. К контексту безопасности можно получить доступ через класс SecurityContextHolder. Вот как выглядит объявление компонента BasicAuthenticationFilter:
01
02
03
04
05
06
07
08
09
10
|
< sec:custom-filter position = "BASIC_AUTH_FILTER" ref = "basicAuthenticationFilter" /> < bean id = "basicAuthenticationFilter" class = "org.springframework.security.web.authentication.www.BasicAuthenticationFilter" > < property name = "authenticationManager" ref = "authenticationManager" /> < property name = "authenticationEntryPoint" ref = "authenticationEntryPoint" /> </ bean > < bean id = "authenticationEntryPoint" class = "org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint" > < property name = "loginFormUrl" value = "/login.jsp" /> </ bean > |
Для получения дополнительной информации о позициях фильтра обратитесь к enum org.springframework.security.config.http.SecurityFilters
Форма авторизации на основе входа: вот как мы ее включаем:
1
|
< sec:form-login login-page = "/login.jsp" /> |
Но есть несколько хуков, предоставляемых Spring. Атрибут default-target-url указывает, куда должна перейти страница входа в систему после проверки подлинности пользователя, а authentication-fail-url определяет страницу, на которую должен перейти пользователь, если проверка подлинности не удалась.
1
2
|
< sec:form-login login-page = "/login.jsp" default-target-url = "/app/messagePost" authentication-failure-url = "/login.jsp?error=true" /> |
Следующий набор атрибутов: всегда-использовать-цель-по-умолчанию, аутентификация-успех-обработчик-ref и аутентификация-сбой-обработчик-ref . authentication-success-handler-ref вызывается при успешной аутентификации, а authentication-fail-handler-ref вызывается при сбое аутентификации. Вот интерфейсы для AuthenticationSuccessHandler и AuthenticationFailureHandler.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
|
/** * Strategy used to handle a successful user authentication. * <p> * Implementations can do whatever they want but typical behaviour would be to control the navigation to the * subsequent destination (using a redirect or a forward). For example, after a user has logged in by submitting a * login form, the application needs to decide where they should be redirected to afterwards * (see {@link AbstractAuthenticationProcessingFilter} and subclasses). Other logic may also be included if required. * * @author Luke Taylor * @since 3.0 */ public interface AuthenticationSuccessHandler { /** * Called when a user has been successfully authenticated. * * @param request the request which caused the successful authentication * @param response the response * @param authentication the <tt>Authentication</tt> object which was created during the authentication process. */ void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException; } /** * Strategy used to handle a failed authentication attempt. * <p> * Typical behaviour might be to redirect the user to the authentication page (in the case of a form login) to * allow them to try again. More sophisticated logic might be implemented depending on the type of the exception. * For example, a {@link CredentialsExpiredException} might cause a redirect to a web controller which allowed the * user to change their password. * * @author Luke Taylor * @since 3.0 */ public interface AuthenticationFailureHandler { /** * Called when an authentication attempt fails. * @param request the request during which the authentication attempt occurred. * @param response the response. * @param exception the exception which was thrown to reject the authentication request. */ void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException; } |
Spring имеет 2 встроенных реализации для обработчиков успеха. SimpleUrlAuthenticationSuccessHandler и SavedRequestAwareAuthenticationSuccessHandler. Последнее расширяет первое.
Цель SavedRequestAwareAuthenticationSuccessHandler — перевести пользователя на страницу, с которой он был перенаправлен на страницу входа для аутентификации. Это обработчик успеха по умолчанию, когда определен элемент ‹form-login›. Мы также можем переопределить это с помощью нашей пользовательской реализации. Предположим, что мы всегда хотим показывать определенную страницу, как только пользователь входит в систему, а не переводить его на страницу, на которой он находился ранее, мы можем установить Always-use-default-target в true.
Также есть 2 встроенные реализации для обработчиков ошибок: SimpleUrlAuthenticationFailureHandler и ExceptionMappingAuthenticationFailureHandler. Последнее расширяет первое.
Мы указываем только один URL-адрес в случае SimpleUrlAuthenticationFailureHandler, где пользователь будет перенаправлен в случае сбоя аутентификации, где, как и в случае ExceptionMappingAuthenticationFailureHandler, мы указываем несколько URL-адресов, куда пользователь будет перенаправлен в зависимости от типа исключения аутентификации (подклассы org.springframework .security.core.AuthenticationException) генерируется во время процесса аутентификации (реализация UserDetailsService выдает исключение).
Кроме того, когда мы определяем нашу пользовательскую страницу входа в систему, мы помечаем поля имени пользователя и пароля как j_username и j_password соответственно, и действие отправки по умолчанию будет j_spring_security_check . Мы также можем настроить имена этих полей и отправить действие, указав атрибуты: username-параметр, password-параметр и login-processing-url соответственно.
Вот как выглядит определение фильтра:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
< sec:custom-filter position = "FORM_LOGIN_FILTER" ref = "formLoginFilter" /> < bean id = "formLoginFilter" class = "org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter" > < property name = "authenticationManager" ref = "authenticationManager" /> < property name = "filterProcessesUrl" value = "/j_spring_security_check" /> < property name = "usernameParameter" value = "username " /> < property name = "passwordParameter" value = "password" /> < property name = "authenticationSuccessHandler" > < bean class = "org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler " > < property name = "alwaysUseDefaultTargetUrl" value = "true" /> < property name = "defaultTargetUrl" value = "/success.jsp" /> </ bean > </ property > < property name = "authenticationFailureHandler" > <!--bean class=" org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler "/--> < bean id = "authenticationFailureHandler" class = "org.springframework.security.web.authentication.ExceptionMappingAuthenticationFailureHandler" > < property name = "exceptionMappings" > < props > < prop key = "org.springframework.security.authentication.BadCredentialsException" >/login/badCredentials</ prop > < prop key = "org.springframework.security.authentication.CredentialsExpiredException" >/login/credentialsExpired</ prop > < prop key = "org.springframework.security.authentication.LockedException" >/login/accountLocked</ prop > < prop key = "org.springframework.security.authentication.DisabledException" >/login/accountDisabled</ prop > </ props > </ property > </ bean > </ property > </ bean > |
В случае входа в систему не будет проблем с выходом из системы, как обсуждалось в базовой аутентификации. Но недостатком является то, что имя пользователя и пароль отправляются в виде открытого текста в заголовках. Это может быть сделано путем кодирования паролей с использованием методов шифрования. Spring предоставляет встроенную поддержку для этого, используя ‹password-encoder› element в провайдере аутентификации. Вот как мы должны это настроить:
1
2
3
4
5
6
|
< sec:authentication-manager id = "authenticationManager" > < sec:authentication-provider > < sec:password-encoder hash = "md5" /> < sec:jdbc-user-service data-source-ref = "dataSource" /> </ sec:authentication-provider > </ sec:authentication-manager > |
2. Поддержка авторизации через ACL: Spring поддерживает авторизацию через ‹intercept-url› in ‹http›
01
02
03
04
05
06
07
08
09
10
|
< sec:http access-decision-manager-ref = "accessDecisionManager" > < sec:intercept-url pattern = "/app/messageList*" access = "ROLE_USER,ROLE_ANONYMOUS" /> < sec:intercept-url pattern = "/app/messagePost*" access = "ROLE_USER" /> < sec:intercept-url pattern = "/app/messageDelete*" access = "ROLE_ADMIN" /> < sec:intercept-url pattern = "/app/*" access = "ROLE_USER" /> < form-login login-page = "/login.jsp" default-target-url = "/app/messagePost" authentication-failure-url = "/login.jsp?error=true" /> <!-- Other settings --> </ sec:http > |
Каждый intercept-url указывает шаблон URL и роли, которые пользователь должен иметь для доступа к тем URL, которые соответствуют указанному шаблону. Обратите внимание, что шаблоны url всегда заканчиваются символом *. Если ‘*’ не указано, то проблема в том, что хакер может обойти механизм безопасности, просто передавая некоторые параметры в URL.
Так что происходит за кулисами, когда Spring передает все эти URL-адреса для перехвата в качестве метаданных в FilterSecurityInterceptor. Вот как это можно настроить без использования ‹intercept-url›:
01
02
03
04
05
06
07
08
09
10
11
12
13
|
< sec:custom-filter position = "FILTER_SECURITY_INTERCEPTOR" ref = "filterSecurityInterceptor" /> < bean id = "filterSecurityInterceptor" class = "org.springframework.security.web.access.intercept.FilterSecurityInterceptor" > < property name = "authenticationManager" ref = "authenticationManager" /> < property name = "accessDecisionManager" ref = "accessDecisionManager" /> < property name = "securityMetadataSource" > < sec:filter-security-metadata-source lowercase-comparisons = "true" request-matcher = "ant" use-expressions = "true" > < sec:intercept-url pattern = "/app/messageList*" access = "ROLE_USER,ROLE_ANONYMOUS" /> < sec:intercept-url pattern = "/app/messagePost*" access = "ROLE_USER" /> < sec:intercept-url pattern = "/app/messageDelete*" access = "ROLE_ADMIN" /> < sec:intercept-url pattern = "/app/*" access = "ROLE_USER" /> </ sec:filter-security-metadata-source > </ property > </ bean > |
Таким образом, из приведенного выше кода вы можете видеть, что анонимные пользователи могут получить доступ только к странице messageList, а для просмотра любых других страниц он должен быть зарегистрирован как пользователь в приложении. Также, если вы внимательно наблюдаете за объявлением bean-компонента, есть свойство accessDecisionManager. Какова цель этого?
Это боб, который на самом деле принимает решения об управлении доступом. Он должен реализовывать интерфейс AccessDecisionManager. Spring предоставляет три встроенных менеджера принятия решений о доступе. Прежде чем понять, как работает диспетчер принятия решений о доступе, нам необходимо узнать, что именно представляет собой AccessDecisionVoter. AccessDecisionManager фактически состоит из одного или нескольких избирателей, принимающих решения о доступе. Этот избиратель инкапсулирует логику, чтобы позволить / запретить / воздержаться от просмотра ресурса пользователем. Голосование за решение «воздержаться» более или менее похоже на отказ от голосования вообще. Поэтому результаты голосования представлены полями констант ACCESS_GRANTED, ACCESS_DENIED и ACCESS_ABSTAIN, определенных в интерфейсе AccessDecisionVoter. Мы можем определить избирателей, принимающих решения по индивидуальному доступу, и добавить их в определение нашего менеджера по принятию решений. Итак, теперь возвращаясь к встроенным менеджерам решений, вот они:
- AffirmatoryBased: как минимум один избиратель должен проголосовать, чтобы предоставить доступ
- ConsensusBased: большинство избирателей должны проголосовать, чтобы предоставить доступ
- UnanimousBased: все избиратели должны проголосовать за то, чтобы воздержаться или предоставить доступ (голосов избирателей не будет, чтобы отказать в доступе)
По умолчанию диспетчер принятия решений на основе AffirrativeBase будет инициализирован с двумя избирателями: RoleVoter и AuthenticatedVoter. RoleVoter предоставляет доступ, если пользователь играет определенную роль в качестве требуемого ресурса. Но обратите внимание, что роль должна начинаться с префикса «ROLE_», если избиратель должен предоставить доступ. Но это можно настроить и для другого префикса. Скоро увидим, как это сделать. AuthenticatedVoter предоставляет доступ, только если пользователь аутентифицирован. Допустимые уровни аутентификации: IS_AUTHENTICATED_FULLY, IS_AUTHENTICATED_REMEMBERED и IS_AUTHENTICATED_ANONYMOUSLY. Предположим, мы хотим определить собственного избирателя и добавить его в диспетчер принятия решений о доступе, вот что мы делаем:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
|
< sec:http access-decision-manager-ref = "accessDecisionManager" auto-config = "true" > <!-- filters declaration go here--> </ sec:http > < bean id = "accessDecisionManager" class = "org.springframework.security.access.vote.AffirmativeBased" > < property name = "decisionVoters" > < list > < bean class = "org.springframework.security.access.vote.RoleVoter" > <!-- Customize the prefix--> < property name = "rolePrefix" value = "ROLE_" /> </ bean > < bean class = "org.springframework.security.access.vote.AuthenticatedVoter" /> < bean class = "com.pramati.security.voters.CustomVoter" /> </ list > </ property > </ bean > |
3. Поддержка выхода: Spring предоставляет обработчик для обработки запросов на выход. Это можно настроить следующим образом:
1
2
3
4
5
6
7
|
< sec:http > <!-- Other filter declarations here --> < sec:logout /> </ sec:http > |
По умолчанию URL-адрес выхода отображается в / j_spring_security_logout . Мы можем настроить этот URL, указав атрибут logout-url . Также, когда пользователь вышел из системы, он будет перенаправлен в корневой каталог контекста. Если пользователь должен быть перенаправлен на какой-либо другой URL-адрес, его необходимо настроить с помощью logout-success-url . Вот как вы это делаете:
1
|
< sec:logout logout-url = "/j_logMeOut" logout-success-url = "/app/messageList" /> |
Если вы хотите, чтобы целевая страница отличалась в разных сценариях, а не по умолчанию с одним конкретным URL-адресом, мы должны реализовать LogoutSuccessHandler и предоставить ссылку на него в элемент ‹logout›
1
|
< sec:logout logout-url = "/j_logMeOut" success-handler-ref = "customLogoutSuccessHandler" /> |
Вот как можно определить базовый фильтр, если вы не хотите использовать элемент ‹logout›:
01
02
03
04
05
06
07
08
09
10
|
< sec:custom-filter position = "LOGOUT_FILTER" ref = "logoutFilter" /> < bean id = "logoutFilter" class = "org.springframework.security.web.authentication.logout.LogoutFilter" > < constructor-arg value = "/pages/Security/logout.html" /> < constructor-arg > < list > < bean class = "org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler" /> </ list > </ constructor-arg > < property name = "filterProcessesUrl" value = "/j_logMeOut" /> </ bean > |
4. Поддержка анонимного входа: по умолчанию Spring создает анонимную роль.
Поэтому, когда вы указываете роль как «ROLE_ANONYMOUS» или «IS_AUTHENTICATED_ANONYMOUSLY», любой анонимный пользователь может просматривать эту страницу. В диспетчере принятия решений о присоединении AffirrativedBased RoleVoter предоставляет доступ, когда он видит атрибут доступа, установленный в «ROLE_ANONYMOUS». Аналогичным образом AuthenticatedVoter предоставляет доступ, если для атрибута доступа установлено значение IS_AUTHENTICATED_ANONYMOUSLY.
Предположим, что вы хотите назначить другое имя роли анонимным пользователям, вы можете переопределить конфигурацию по умолчанию следующим образом:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
|
< sec:http > < sec:intercept-url pattern = "/login.jsp*" filters = "none" /> < sec:intercept-url pattern = "/*" access = "ROLE_USER" /> <!-- Defines a custom role in place of ROLE_ANONYMOUS. ROLE_ANONYMOUS will no more work, use ROLE_GUEST instead of it--> < sec:anonymous username = "guest" granted-authority = "ROLE_GUEST" /> </ sec:http > < p style = "text-align: justify;" >Here is the how the underlying filter can be defined if you don't want to use ‹anonymous› element:</ p > 1 < sec:custom-filter position = "ANONYMOUS_FILTER" ref = "anonymousFilter" /> < bean id = "anonymousFilter" class = "org.springframework.security.web.authentication.AnonymousAuthenticationFilter" > < property name = "userAttribute" value = "ROLE_GUEST" /> </ bean > |
5. Аутентификация «Помни меня»: это относится к веб-сайтам, которые могут помнить личность принципала между сеансами Spring достигает этого, отправляя файл cookie в браузер после успешной интерактивной аутентификации, причем файл cookie создается следующим образом:
base64 (имя пользователя + «:» + expirationTime + «:» + md5Hex (имя пользователя + «:» + expirationTime + «:» пароль + «:» + ключ))
Теперь, когда браузер делает следующий запрос к серверу, он также отправляет этот cookie вместе с ним. Теперь за кулисами Spring делает следующее:
(а) Получает пароль от серверной части для данного имени пользователя
(b) Извлекает пароль из базы данных для имени пользователя и вычисляет md5Hex () имени пользователя, пароля, expirationTime и ключа и сравнивает его со значением в файле cookie
(c) Если они совпадают — вы вошли! Если нет совпадения, значит, вы указали поддельный файл cookie или один из имени пользователя / пароля / ключа был изменен.
Мы можем включить проверку подлинности «помни меня», просто добавив элемент внутри ‹http›. Вот как мы это делаем:
1
2
3
4
5
6
7
|
< sec:http > <!-- Other filter declarations here --> < sec:remember-me key = "myAppKey" /> </ sec:http > |
UserDetailsService обычно выбирается автоматически. Если у вас есть более одного в контексте вашего приложения, вам нужно указать, какой из них следует использовать с атрибутом user-service-ref, где значением является имя вашего бина UserDetailsService. Следует отметить, что здесь существует потенциальная проблема безопасности, так как токен вспоминания может быть захвачен и может использоваться неправильно, поскольку он действителен до истечения срока его действия. Этого можно избежать, используя токены. Вот как вы можете реализовать сервис «Помни меня» на основе токенов:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
|
< sec:http access-decision-manager-ref = "accessDecisionManager" > <!-- Other filter declarations here --> < remember-me services-alias = "rememberMeService" data-source-ref = "dataSource" /> <!-- <remember-me data-source-ref="dataSource" key="pramati"/> --> </ sec:http > < bean id = "tokenRepository" class = "org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl" > < property name = "dataSource" ref = "dataSource" /> < property name = "createTableOnStartup" value = "true" /> </ bean > < bean id = "rememberMeService" class = "org.springframework.security.web.authentication.rememberme.PersistentTokenBasedRememberMeServices" > < property name = "userDetailsService" ref = "userDetailsService" /> < property name = "tokenRepository" ref = "tokenRepository" /> </ bean > |
Примечания к сведению:
(a) Новая таблица persistent_logins будет создана в базе данных, так как мы определили ‘createTableOnStartup’ как true при определении bean-компонента ‘tokenRepository’. Вот sql для создания таблицы:
1
2
3
4
5
|
create table persistent_logins ( username varchar( 64 ) not null , series varchar( 64 ) primary key, token varchar( 64 ) not null , last_used timestamp not null ); |
(б) Мы больше не даем свой собственный знак безопасности. Spring автоматически сгенерирует токен и поместит / обновит его в таблице persistent_tokens. Когда пользователь получает доступ к приложению из браузера и входит в приложение, выбрав опцию запомнить меня, в этой таблице будет создана запись. В следующий раз, когда пользователь войдет в систему из того же браузера, пользователь автоматически войдет в систему, и значение токена в БД будет изменено на новое значение, но значение серии останется неизменным. Предположим, что пользователь теперь входит в другой браузер, выбирая запомнить меня, для этого браузера будет создана новая запись. И последующие обновления происходят в этой конкретной строке, когда он обращается к приложению из этого браузера.
Таким образом, преимущество использования этого подхода заключается в том, что злоумышленник сможет использовать украденный cookie-файл до тех пор, пока пользователь-жертва не получит следующий доступ к приложению, а не в течение полного срока жизни запомненного cookie-файла, как это наблюдалось в более раннем подходе с одним токеном. Когда жертва в следующий раз заходит на веб-сайт, он будет использовать тот же файл cookie. Теперь Spring создает исключение CookieTheftException, которое можно использовать для информирования пользователя о том, что произошла кража.
Вместо того, чтобы использовать элемент, который мы использовали до сих пор, мы можем определить его как пользовательский фильтр в цепочке безопасности следующим образом:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
|
< sec:custom-filter position = "REMEMBER_ME_FILTER" ref = "rememberMeFilter" /> < bean id = "rememberMeFilter" class = "org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationFilter" > < property name = "rememberMeServices" ref = "rememberMeServices" /> < property name = "authenticationManager" ref = "theAuthenticationManager" /> </ bean > < bean id = "tokenRepository" class = "org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl" > < property name = "dataSource" ref = "dataSource" /> < property name = "createTableOnStartup" value = "false" /> </ bean > < bean id = "rememberMeServices" class = "org.springframework.security.web.authentication.rememberme.PersistentTokenBasedRememberMeServices" > < property name = "userDetailsService" ref = "userDetailsService" /> < property name = "tokenRepository" ref = "tokenRepository" /> </ bean > < bean id = "rememberMeAuthenticationProvider" class = "org.springframework.security.authentication.rememberme.RememberMeAuthenticationProvider" /> |
6. Управление параллельными сеансами. Предположим, что мы не хотим, чтобы пользователь входил в приложение из более чем одного места одновременно, мы должны включить эту функцию в Spring. Вот как мы это делаем:
1
2
3
4
5
6
7
8
9
|
< sec:http > <!-- Other filter declarations here --> < sec:session-management session-authentication-error-url = "/login.jsp?error=alreadyLoggedin" > < sec:concurrency-control max-sessions = "1" error-if-maximum-exceeded = "true" expired-url = "/login.jsp?error=alreadyLoggedin" /> </ sec:session-management > </ sec:http > |
Также мы должны определить слушателя в web.xml, который необходим для вызова события (org.springframework.security.core.session.SessionDestroyedEvent), когда пользователь выходит из приложения.
1
2
3
4
5
|
< listener > < listener-class > org.springframework.security.web.session.HttpSessionEventPublisher </ listener-class > </ listener > |
Так что же теперь происходит за кулисами? Как Spring достигает этой поддержки?
Spring использует фильтр безопасности, подобный тому, что мы обсуждали все время. Кроме того, он также использует ApplicationEvents. Когда Spring видит необходимость управления параллелизмом, она поддерживает список сеансов, связанных с принципалом. Структура карты выглядит так (фактически определена в org.springframework.security.core.session.SessionRegistryImpl):
1
2
|
ConcurrentMap<Object,Set<String>> principals = new ConcurrentHashMap<Object,Set<String>>(); |
Ключом карты здесь является объект User, а значением является набор идентификаторов сеансов, связанных с ним. Таким образом, возникает исключение, когда размер набора больше значения max-сессий, определенного в элементе ‹concurrency-control›. Когда Spring видит определенный элемент управления параллелизмом, SessionRegistryImpl (где определяется карта) создается внутри ConcurrentSessionControlStrategy и внедряется в UsernamePasswordAuthenticationFilter. Теперь, когда аутентификация пользователя прошла успешно, Spring помещает запись в карту, рассмотренную выше.
Теперь, когда пользователь выходит из системы, SessionDestroyedEvent будет вызываться, как показано выше, при определении слушателя в web.xml. SessionRegistryImpl прослушивает это событие и удаляет запись идентификатора сеанса из поддерживаемой карты. Без этого пользователь никогда не сможет снова войти в систему после того, как превысит свое разрешение сеанса, даже если он вышел из другого сеанса или истекло время ожидания. Итак, вот эквивалентная конфигурация для управления параллелизмом:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
< sec:http > < sec:custom-filter position = "CONCURRENT_SESSION_FILTER" ref = "concurrencyFilter" /> < sec:custom-filter position = "FORM_LOGIN_FILTER" ref = "myAuthFilter" /> <!-- Other filter declarations here --> < sec:session-management session-authentication-strategy-ref = "sessionAuthenticationStrategy" /> </ sec:http > < bean id = "concurrencyFilter" class = "org.springframework.security.web.session.ConcurrentSessionFilter" > < property name = "sessionRegistry" ref = "sessionRegistry" /> < property name = "expiredUrl" value = "/session-expired.htm" /> </ bean > < bean id = "myAuthFilter" class = "org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter" > < property name = "sessionAuthenticationStrategy" ref = "sessionAuthenticationStrategy" /> < property name = "authenticationManager" ref = "authenticationManager" /> </ bean > < bean id = "sessionAuthenticationStrategy" class = "org.springframework.security.web.authentication.session.ConcurrentSessionControlStrategy" > < constructor-arg name = "sessionRegistry" ref = "sessionRegistry" /> < property name = "maximumSessions" value = "1" /> </ bean > < bean id = "sessionRegistry" class = "org.springframework.security.core.session.SessionRegistryImpl" /> |
В заключение статья затрагивает основные конфигурации и базовые классы платформы, которые очень важны для настройки безопасности в соответствии с нашими конкретными требованиями.