Подтвердить что ты не робот

Spring Перенаправление безопасности на предыдущую страницу после успешного входа в систему

Я знаю, что этот вопрос задан раньше, однако я столкнулся с конкретной проблемой здесь.

Я использую spring security 3.1.3.

В моем веб-приложении есть 3 возможных случая входа:

  • Войдите через страницу входа в систему: OK.
  • Войдите через ограниченную страницу: ОК тоже.
  • Вход через страницу с неограниченным доступом: не в порядке... Доступ к странице "Продукт" доступен всем, и пользователь может опубликовать комментарий, если он зарегистрировался. Таким образом, форма входа находится на той же странице, чтобы пользователи могли подключаться.

Проблема с случаем 3) заключается в том, что я не могу перенаправить пользователей на страницу "продукт". Они перенаправляются на домашнюю страницу после успешного входа в систему, несмотря ни на что.

Обратите внимание, что в случае 2) перенаправление на ограниченную страницу выполняется из окна после успешного входа в систему.

Вот соответствующая часть моего файла security.xml:

<!-- Authentication policy for the restricted page  -->
<http use-expressions="true" auto-config="true" pattern="/restrictedPage/**">
    <form-login login-page="/login/restrictedLogin" authentication-failure-handler-ref="authenticationFailureHandler" />
    <intercept-url pattern="/**" access="isAuthenticated()" />
</http>

<!-- Authentication policy for every page -->
<http use-expressions="true" auto-config="true">
    <form-login login-page="/login" authentication-failure-handler-ref="authenticationFailureHandler" />
    <logout logout-url="/logout" logout-success-url="/" />
</http>

Я подозреваю, что "политика проверки подлинности для каждой страницы" отвечает за проблему. Однако, если я удалю его, я больше не могу войти... j_spring_security_check отправляет ошибку 404.


EDIT:

Благодаря Ральфу, я смог найти решение. Итак, вот что: я использовал свойство

<property name="useReferer" value="true"/>

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

<!-- Authentication policy for login page -->
<http use-expressions="true" auto-config="true" pattern="/login/**">
    <form-login login-page="/login" authentication-success-handler-ref="authenticationSuccessHandlerWithoutReferer" />
</http>

<!-- Authentication policy for every page -->
<http use-expressions="true" auto-config="true">
    <form-login login-page="/login" authentication-failure-handler-ref="authenticationFailureHandler" />
    <logout logout-url="/logout" logout-success-url="/" authentication-success-handler-ref="authenticationSuccessHandler"/>
</http>

<beans:bean id="authenticationSuccessHandler" class="org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler">
    <!-- After login, return to the last visited page -->
    <beans:property name="useReferer" value="true" />
</beans:bean>

<beans:bean id="authenticationSuccessHandlerWithoutReferer" class="org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler">
    <!-- After login, stay to the same page -->
    <beans:property name="useReferer" value="false" />
</beans:bean>

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

Тем временем я пришел к обходу. Не лучшее решение, но, как я уже сказал, если у кого-то есть что-то лучшее, чтобы показать, я все уши. Итак, это новая политика проверки подлинности для страницы входа:

<http use-expressions="true" auto-config="true" pattern="/login/**" >
    <intercept-url pattern="/**" access="isAnonymous()" />
    <access-denied-handler error-page="/"/>
</http>

Решение здесь довольно очевидно: страница входа в систему разрешена только для анонимных пользователей. Когда пользователь подключен, обработчик ошибок перенаправляет его на домашнюю страницу.

Я сделал несколько тестов, и все, кажется, работает хорошо.

4b9b3361

Ответ 1

Что происходит после входа в систему (к которому перенаправляется URL-адрес пользователя) обрабатывается AuthenticationSuccessHandler.

Этот интерфейс (конкретный класс, реализующий его SavedRequestAwareAuthenticationSuccessHandler) вызывается AbstractAuthenticationProcessingFilter или одним из его подклассов вроде (UsernamePasswordAuthenticationFilter) в методе successfulAuthentication.

Итак, чтобы иметь другую переадресацию в случае 3, вы должны подклассифицировать SavedRequestAwareAuthenticationSuccessHandler и сделать так, чтобы сделать то, что вы хотите.


Иногда (в зависимости от вашей точной usecase) достаточно включить флаг useReferer AbstractAuthenticationTargetUrlRequestHandler, который вызывается SimpleUrlAuthenticationSuccessHandler (суперкласс SavedRequestAwareAuthenticationSuccessHandler).

<bean id="authenticationFilter"
      class="org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter">
    <property name="filterProcessesUrl" value="/login/j_spring_security_check" />
    <property name="authenticationManager" ref="authenticationManager" />
    <property name="authenticationSuccessHandler">
        <bean class="org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler">
            <property name="useReferer" value="true"/>
        </bean>
    </property>
    <property name="authenticationFailureHandler">
        <bean class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler">
            <property name="defaultFailureUrl" value="/login?login_error=t" />
        </bean>
    </property>
</bean>

Ответ 2

Я хочу добавить Olcay хороший ответ. Его подход хорош, ваш логин для входа в систему должен быть таким, чтобы включить URL-адрес реферера в сеанс:

@RequestMapping(value = "/login", method = RequestMethod.GET)
public String loginPage(HttpServletRequest request, Model model) {
    String referrer = request.getHeader("Referer");
    request.getSession().setAttribute("url_prior_login", referrer);
    // some other stuff
    return "login";
}

И вы должны расширить SavedRequestAwareAuthenticationSuccessHandler и переопределить его метод onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication). Что-то вроде этого:

public class MyCustomLoginSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
    public MyCustomLoginSuccessHandler(String defaultTargetUrl) {
        setDefaultTargetUrl(defaultTargetUrl);
    }

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws ServletException, IOException {
        HttpSession session = request.getSession();
        if (session != null) {
            String redirectUrl = (String) session.getAttribute("url_prior_login");
            if (redirectUrl != null) {
                // we do not forget to clean this attribute from session
                session.removeAttribute("url_prior_login");
                // then we redirect
                getRedirectStrategy().sendRedirect(request, response, redirectUrl);
            } else {
                super.onAuthenticationSuccess(request, response, authentication);
            }
        } else {
            super.onAuthenticationSuccess(request, response, authentication);
        }
    }
}

Затем в конфигурации spring вы должны определить этот пользовательский класс как bean и использовать его в своей конфигурации безопасности. Если вы используете конфигурацию аннотации, она должна выглядеть так (класс, который вы расширяете от WebSecurityConfigurerAdapter):

@Bean
public AuthenticationSuccessHandler successHandler() {
    return new MyCustomLoginSuccessHandler("/yourdefaultsuccessurl");
}

В configure метод:

@Override
protected void configure(HttpSecurity http) throws Exception {
    http
            // bla bla
            .formLogin()
                .loginPage("/login")
                .usernameParameter("username")
                .passwordParameter("password")
                .successHandler(successHandler())
                .permitAll()
            // etc etc
    ;
}

Ответ 3

У меня есть следующее решение, и это сработало для меня.

Всякий раз, когда запрашивается страница входа в систему, введите значение реферера в сеанс:

@RequestMapping(value="/login", method = RequestMethod.GET)
public String login(ModelMap model,HttpServletRequest request) {

    String referrer = request.getHeader("Referer");
    if(referrer!=null){
        request.getSession().setAttribute("url_prior_login", referrer);
    }
    return "user/login";
}

Затем после успешной регистрации пользовательская реализация SavedRequestAwareAuthenticationSuccessHandler перенаправит пользователя на предыдущую страницу:

HttpSession session = request.getSession(false);
if (session != null) {
    url = (String) request.getSession().getAttribute("url_prior_login");
}

Перенаправить пользователя:

if (url != null) {
    response.sendRedirect(url);
}

Ответ 4

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

В вашем контроллере MVC Spring при загрузке страницы продукта сохраните путь к странице продукта в сеансе, если пользователь не выполнил вход в систему. В конфигурации XML задайте целевой URL-адрес по умолчанию. Например:

В вашем контроллере MVC Spring метод перенаправления должен прочитать путь из сеанса и вернуть redirect:<my_saved_product_path>.

Итак, после входа пользователя в систему они будут отправлены на страницу /redirect, которая незамедлительно перенаправит их на страницу продукта, которую они посетили в последний раз.

Ответ 5

Назад на предыдущую страницу после succesfull login, мы можем использовать следующий пользовательский менеджер проверки подлинности следующим образом:

<!-- enable use-expressions -->
    <http auto-config="true" use-expressions="true">
        <!-- src** matches: src/bar.c src/baz.c src/test/bartest.c-->
        <intercept-url pattern="/problemSolution/home/**" access="hasRole('ROLE_ADMIN')"/>
        <intercept-url pattern="favicon.ico" access="permitAll"/>
        <form-login
                authentication-success-handler-ref="authenticationSuccessHandler"
                always-use-default-target="true"
                login-processing-url="/checkUser"
                login-page="/problemSolution/index"

                default-target-url="/problemSolution/home"
                authentication-failure-url="/problemSolution/index?error"
                username-parameter="username"
                password-parameter="password"/>
        <logout logout-url="/problemSolution/logout"
                logout-success-url="/problemSolution/index?logout"/>
        <!-- enable csrf protection -->
        <csrf/>
    </http>

    <beans:bean id="authenticationSuccessHandler"
            class="org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler">
        <beans:property name="defaultTargetUrl" value="/problemSolution/home"/>
    </beans:bean>

    <!-- Select users and user_roles from database -->
    <authentication-manager>
        <authentication-provider user-service-ref="customUserDetailsService">
            <password-encoder hash="plaintext">
            </password-encoder>
        </authentication-provider>
    </authentication-manager>

CustomUserDetailsService class

@Service
public class CustomUserDetailsService implements UserDetailsService {

        @Autowired
        private UserService userService;

        public UserDetails loadUserByUsername(String userName)
                        throws UsernameNotFoundException {
                com.codesenior.telif.local.model.User domainUser = userService.getUser(userName);

                boolean enabled = true;
                boolean accountNonExpired = true;
                boolean credentialsNonExpired = true;
                boolean accountNonLocked = true;

                return new User(
                                domainUser.getUsername(),
                                domainUser.getPassword(),
                                enabled,
                                accountNonExpired,
                                credentialsNonExpired,
                                accountNonLocked,
                                getAuthorities(domainUser.getUserRoleList())
                );
        }

        public Collection<? extends GrantedAuthority> getAuthorities(List<UserRole> userRoleList) {
                return getGrantedAuthorities(getRoles(userRoleList));
        }

        public List<String> getRoles(List<UserRole> userRoleList) {

                List<String> roles = new ArrayList<String>();

                for(UserRole userRole:userRoleList){
                        roles.add(userRole.getRole());
                }
                return roles;
        }

        public static List<GrantedAuthority> getGrantedAuthorities(List<String> roles) {
                List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();

                for (String role : roles) {
                        authorities.add(new SimpleGrantedAuthority(role));
                }
                return authorities;
        }

}

Пользовательский класс

import com.codesenior.telif.local.model.UserRole;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

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


@Service
public class CustomUserDetailsService implements UserDetailsService {

        @Autowired
        private UserService userService;

        public UserDetails loadUserByUsername(String userName)
                        throws UsernameNotFoundException {
                com.codesenior.telif.local.model.User domainUser = userService.getUser(userName);

                boolean enabled = true;
                boolean accountNonExpired = true;
                boolean credentialsNonExpired = true;
                boolean accountNonLocked = true;

                return new User(
                                domainUser.getUsername(),
                                domainUser.getPassword(),
                                enabled,
                                accountNonExpired,
                                credentialsNonExpired,
                                accountNonLocked,
                                getAuthorities(domainUser.getUserRoleList())
                );
        }

        public Collection<? extends GrantedAuthority> getAuthorities(List<UserRole> userRoleList) {
                return getGrantedAuthorities(getRoles(userRoleList));
        }

        public List<String> getRoles(List<UserRole> userRoleList) {

                List<String> roles = new ArrayList<String>();

                for(UserRole userRole:userRoleList){
                        roles.add(userRole.getRole());
                }
                return roles;
        }

        public static List<GrantedAuthority> getGrantedAuthorities(List<String> roles) {
                List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();

                for (String role : roles) {
                        authorities.add(new SimpleGrantedAuthority(role));
                }
                return authorities;
        }

}

Класс UserRole

@Entity
public class UserRole {
        @Id
        @GeneratedValue
        private Integer userRoleId;

        private String role;

        @ManyToMany(fetch = FetchType.LAZY, mappedBy = "userRoleList")
        @JsonIgnore
        private List<User> userList;

        public Integer getUserRoleId() {
                return userRoleId;
        }

        public void setUserRoleId(Integer userRoleId) {
                this.userRoleId= userRoleId;
        }

        public String getRole() {
                return role;
        }

        public void setRole(String role) {
                this.role= role;
        }

        @Override
        public String toString() {
                return String.valueOf(userRoleId);
        }

        public List<User> getUserList() {
                return userList;
        }

        public void setUserList(List<User> userList) {
                this.userList= userList;
        }
}

Ответ 6

Вы можете использовать Custom SuccessHandler, расширяющий SimpleUrlAuthenticationSuccessHandler для перенаправления пользователей на разные URL-адреса при входе в соответствии с назначенными ролями.

Класс CustomSuccessHandler предоставляет настраиваемые функции перенаправления:

package com.mycompany.uomrmsweb.configuration;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.web.DefaultRedirectStrategy;
import org.springframework.security.web.RedirectStrategy;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
import org.springframework.stereotype.Component;

@Component
public class CustomSuccessHandler extends SimpleUrlAuthenticationSuccessHandler{

    private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();

    @Override
    protected void handle(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException {
        String targetUrl = determineTargetUrl(authentication);

        if (response.isCommitted()) {
            System.out.println("Can't redirect");
            return;
        }

        redirectStrategy.sendRedirect(request, response, targetUrl);
    }

    protected String determineTargetUrl(Authentication authentication) {
        String url="";

        Collection<? extends GrantedAuthority> authorities =  authentication.getAuthorities();

        List<String> roles = new ArrayList<String>();

        for (GrantedAuthority a : authorities) {
            roles.add(a.getAuthority());
        }

        if (isStaff(roles)) {
            url = "/staff";
        } else if (isAdmin(roles)) {
            url = "/admin";
        } else if (isStudent(roles)) {
            url = "/student";
        }else if (isUser(roles)) {
            url = "/home";
        } else {
            url="/Access_Denied";
        }

        return url;
    }

    public void setRedirectStrategy(RedirectStrategy redirectStrategy) {
        this.redirectStrategy = redirectStrategy;
    }
    protected RedirectStrategy getRedirectStrategy() {
        return redirectStrategy;
    }

    private boolean isUser(List<String> roles) {
        if (roles.contains("ROLE_USER")) {
            return true;
        }
        return false;
    }

    private boolean isStudent(List<String> roles) {
        if (roles.contains("ROLE_Student")) {
            return true;
        }
        return false;
    }

    private boolean isAdmin(List<String> roles) {
        if (roles.contains("ROLE_SystemAdmin") || roles.contains("ROLE_ExaminationsStaff")) {
            return true;
        }
        return false;
    }

    private boolean isStaff(List<String> roles) {
        if (roles.contains("ROLE_AcademicStaff") || roles.contains("ROLE_UniversityAdmin")) {
            return true;
        }
        return false;
    }
}

Расширение класса Spring SimpleUrlAuthenticationSuccessHandler и переопределения метода handle(), который просто вызывает перенаправление с использованием настроенной RedirectStrategy [по умолчанию в этом случае] с URL-адресом, возвращаемым определяемым пользователем методом defineTargetUrl(). Этот метод извлекает роли текущего пользователя во время входа в систему из объекта проверки подлинности и затем создает соответствующий URL на основе ролей. Наконец RedirectStrategy, которая отвечает за все перенаправления в рамках Spring Security, перенаправляет запрос на указанный URL.

Регистрация CustomSuccessHandler с использованием класса SecurityConfiguration:

package com.mycompany.uomrmsweb.configuration;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;

@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Autowired
    @Qualifier("customUserDetailsService")
    UserDetailsService userDetailsService;

    @Autowired
    CustomSuccessHandler customSuccessHandler;

    @Autowired
    public void configureGlobalSecurity(AuthenticationManagerBuilder auth) throws Exception {
            auth.userDetailsService(userDetailsService);
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
        .antMatchers("/", "/home").access("hasRole('USER')")
        .antMatchers("/admin/**").access("hasRole('SystemAdmin') or hasRole('ExaminationsStaff')")
        .antMatchers("/staff/**").access("hasRole('AcademicStaff') or hasRole('UniversityAdmin')")
        .antMatchers("/student/**").access("hasRole('Student')")  
                    .and().formLogin().loginPage("/login").successHandler(customSuccessHandler)
        .usernameParameter("username").passwordParameter("password")
        .and().csrf()
        .and().exceptionHandling().accessDeniedPage("/Access_Denied");
    }
}

successHandler - это класс, ответственный за возможное перенаправление на основе любой пользовательской логики, которая в этом случае будет перенаправлять пользователя [ученику/администратору/персоналу] на основе его роли [USER/Student/SystemAdmin/UniversityAdmin/ExaminationsStaff/AcademicStaff].

Ответ 7

Я нашел решение Utku Özdemir, в какой-то мере работает, но как бы поражает цель сохраненного запроса, поскольку атрибут session будет иметь приоритет над ним. Это означает, что перенаправление на защищенные страницы не будет работать должным образом - после входа в систему вы будете отправлены на страницу, на которой вы были, а не на цель перенаправления. Таким образом, вы можете использовать модифицированную версию SavedRequestAwareAuthenticationSuccessHandler вместо ее расширения. Это позволит вам лучше контролировать, когда использовать атрибут сеанса.

Вот пример:

private static class MyCustomLoginSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {

    private RequestCache requestCache = new HttpSessionRequestCache();

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
            Authentication authentication) throws ServletException, IOException {
        SavedRequest savedRequest = requestCache.getRequest(request, response);

        if (savedRequest == null) {
            HttpSession session = request.getSession();
            if (session != null) {
                String redirectUrl = (String) session.getAttribute("url_prior_login");
                if (redirectUrl != null) {
                    session.removeAttribute("url_prior_login");
                    getRedirectStrategy().sendRedirect(request, response, redirectUrl);
                } else {
                    super.onAuthenticationSuccess(request, response, authentication);
                }
            } else {
                super.onAuthenticationSuccess(request, response, authentication);
            }

            return;
        }

        String targetUrlParameter = getTargetUrlParameter();
        if (isAlwaysUseDefaultTargetUrl()
                || (targetUrlParameter != null && StringUtils.hasText(request.getParameter(targetUrlParameter)))) {
            requestCache.removeRequest(request, response);
            super.onAuthenticationSuccess(request, response, authentication);

            return;
        }

        clearAuthenticationAttributes(request);

        // Use the DefaultSavedRequest URL
        String targetUrl = savedRequest.getRedirectUrl();
        logger.debug("Redirecting to DefaultSavedRequest Url: " + targetUrl);
        getRedirectStrategy().sendRedirect(request, response, targetUrl);
    }
}

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

@RequestMapping(value = "/login", params = "error")
public String loginError() {
    // Don't save referrer here!
}

Ответ 8

У меня есть пользовательская авторизация OAuth2 и request.getHeader("Referer") недоступен в момент принятия решения. Но запрос безопасности уже сохранен в ExceptionTranslationFilter.handleSpringSecurityException:

protected void sendStartAuthentication(HttpServletRequest request,...
    ...
    requestCache.saveRequest(request, response);

Итак, все, что нам нужно, это поделиться requestCache как Spring bean:

@Bean
public RequestCache requestCache() {
   return new HttpSessionRequestCache();
}

@Override
protected void configure(HttpSecurity http) throws Exception {
   http.authorizeRequests()
   ... 
   .requestCache().requestCache(requestCache()).and()
   ...
}     

и используйте его, когда авторизация закончена:

@Autowired
private RequestCache requestCache;

public void authenticate(HttpServletRequest req, HttpServletResponse resp){
    ....
    SavedRequest savedRequest = requestCache.getRequest(req, resp);
    resp.sendRedirect(savedRequest == null ? "defaultURL" : savedRequest.getRedirectUrl());
}