1. Обзор
Общим требованием к веб-приложению является перенаправление пользователей разных типов на разные страницы после входа в систему . Примером этого может быть перенаправление обычных пользователей на страницу /homepage.html и пользователей-администраторов, например, на страницу
/console.html
.
В этой статье будет показано, как быстро и безопасно реализовать этот механизм с помощью Spring Security. Статья также основана на руководстве по Spring MVC , в котором рассматривается настройка основных компонентов MVC, необходимых для проекта.
2. Конфигурация безопасности Spring
Spring Security предоставляет компонент, который непосредственно отвечает за принятие решения о том, что делать после успешной аутентификации — AuthenticationSuccessHandler
.
2.1. Базовая конфигурация
Давайте сначала настроим базовые классы @Configuration
и @Service
:
@Configuration
@EnableWebSecurity
public class SecSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(final HttpSecurity http) throws Exception {
http
.authorizeRequests()
// ... endpoints
.formLogin()
.loginPage("/login.html")
.loginProcessingUrl("/login")
.defaultSuccessUrl("/homepage.html", true)
// ... other configuration
}
}
Часть этой конфигурации, на которой следует сосредоточиться, — это метод defaultSuccessUrl()
. После успешного входа любой пользователь будет перенаправлен на homepage.html
.
Кроме того, нам нужно настроить пользователей и их роли. Для целей этой статьи мы реализуем простую службу UserDetailService
с двумя пользователями, каждый из которых имеет одну роль. Подробнее на эту тему читайте в нашей статье Spring Security — Roles and Privileges .
@Service
public class MyUserDetailsService implements UserDetailsService {
private Map<String, User> roles = new HashMap<>();
@PostConstruct
public void init() {
roles.put("admin2", new User("admin", "{noop}admin1", getAuthority("ROLE_ADMIN")));
roles.put("user2", new User("user", "{noop}user1", getAuthority("ROLE_USER")));
}
@Override
public UserDetails loadUserByUsername(String username) {
return roles.get(username);
}
private List<GrantedAuthority> getAuthority(String role) {
return Collections.singletonList(new SimpleGrantedAuthority(role));
}
}
Также обратите внимание, что в этом простом примере мы не будем использовать кодировщик паролей, поэтому пароли имеют префикс {noop}
.
2.2. Добавление пользовательского обработчика успеха
Теперь у нас есть два пользователя с двумя разными ролями: пользователь
и администратор
. После успешного входа оба будут перенаправлены на hompeage.html
. Давайте посмотрим, как у нас может быть другое перенаправление в зависимости от роли пользователя.
Во-первых, нам нужно определить собственный обработчик успеха как bean-компонент:
@Bean
public AuthenticationSuccessHandler myAuthenticationSuccessHandler(){
return new MySimpleUrlAuthenticationSuccessHandler();
}
А затем замените вызов defaultSuccessUrl на метод
successHandler
, который принимает наш пользовательский обработчик успеха в качестве параметра:
@Override
protected void configure(final HttpSecurity http) throws Exception {
http
.authorizeRequests()
// endpoints
.formLogin()
.loginPage("/login.html")
.loginProcessingUrl("/login")
.successHandler(myAuthenticationSuccessHandler())
// other configuration
}
2.3. XML-конфигурация
Прежде чем рассматривать реализацию нашего пользовательского обработчика успеха, давайте также рассмотрим эквивалентную конфигурацию XML:
<http use-expressions="true" >
<!-- other configuration -->
<form-login login-page='/login.html'
authentication-failure-url="/login.html?error=true"
authentication-success-handler-ref="myAuthenticationSuccessHandler"/>
<logout/>
</http>
<beans:bean id="myAuthenticationSuccessHandler"
class="com.foreach.security.MySimpleUrlAuthenticationSuccessHandler" />
<authentication-manager>
<authentication-provider>
<user-service>
<user name="user1" password="{noop}user1Pass" authorities="ROLE_USER" />
<user name="admin1" password="{noop}admin1Pass" authorities="ROLE_ADMIN" />
</user-service>
</authentication-provider>
</authentication-manager>
3. Пользовательский обработчик успешной аутентификации
Помимо интерфейса AuthenticationSuccessHandler
, Spring также предоставляет разумное значение по умолчанию для этого компонента стратегии — AbstractAuthenticationTargetUrlRequestHandler
и простую реализацию — SimpleUrlAuthenticationSuccessHandler
. Обычно эти реализации определяют URL-адрес после входа в систему и выполняют перенаправление на этот URL-адрес.
Несмотря на некоторую гибкость, механизм определения этого целевого URL-адреса не позволяет выполнять определение программно, поэтому мы собираемся реализовать интерфейс и предоставить пользовательскую реализацию обработчика успеха. Эта реализация будет определять URL-адрес для перенаправления пользователя после входа в систему в зависимости от роли пользователя.
Прежде всего, нам нужно переопределить метод onAuthenticationSuccess
:
public class MySimpleUrlAuthenticationSuccessHandler
implements AuthenticationSuccessHandler {
protected Log logger = LogFactory.getLog(this.getClass());
private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
@Override
public void onAuthenticationSuccess(HttpServletRequest request,
HttpServletResponse response, Authentication authentication)
throws IOException {
handle(request, response, authentication);
clearAuthenticationAttributes(request);
}
Наш настроенный метод вызывает два вспомогательных метода:
protected void handle(
HttpServletRequest request,
HttpServletResponse response,
Authentication authentication
) throws IOException {
String targetUrl = determineTargetUrl(authentication);
if (response.isCommitted()) {
logger.debug(
"Response has already been committed. Unable to redirect to "
+ targetUrl);
return;
}
redirectStrategy.sendRedirect(request, response, targetUrl);
}
Где следующий метод выполняет фактическую работу и сопоставляет пользователя с целевым URL-адресом:
protected String determineTargetUrl(final Authentication authentication) {
Map<String, String> roleTargetUrlMap = new HashMap<>();
roleTargetUrlMap.put("ROLE_USER", "/homepage.html");
roleTargetUrlMap.put("ROLE_ADMIN", "/console.html");
final Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
for (final GrantedAuthority grantedAuthority : authorities) {
String authorityName = grantedAuthority.getAuthority();
if(roleTargetUrlMap.containsKey(authorityName)) {
return roleTargetUrlMap.get(authorityName);
}
}
throw new IllegalStateException();
}
Обратите внимание, что этот метод вернет сопоставленный URL-адрес для первой роли, которую имеет пользователь. Таким образом, если у пользователя есть несколько ролей, сопоставленный URL-адрес будет тем, который соответствует первой роли, указанной в коллекции полномочий .
protected void clearAuthenticationAttributes(HttpServletRequest request) {
HttpSession session = request.getSession(false);
if (session == null) {
return;
}
session.removeAttribute(WebAttributes.AUTHENTICATION_EXCEPTION);
}
DefinitTargetUrl , являющийся ядром стратегии, просто смотрит на тип пользователя (определяемый уполномоченным органом) и выбирает
целевой URL-адрес на основе этой роли .
Таким образом, пользователь-администратор , определенный полномочиями ROLE_ADMIN
, будет перенаправлен на страницу консоли после входа в систему, а обычный пользователь , определенный ROLE_USER
, будет перенаправлен на домашнюю страницу.
4. Вывод
Как всегда, код, представленный в этой статье, доступен на GitHub . Это проект на основе Maven, поэтому его легко импортировать и запускать как есть.