«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 — роли и привилегии.

@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.baeldung.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, поэтому его легко импортировать и запускать как есть.