web-dev-qa-db-fra.com

Spring Security redirige vers la page précédente après une connexion réussie

Je sais que cette question a déjà été posée, mais je suis confronté à un problème particulier.

J'utilise la sécurité de printemps 3.1.3.

J'ai trois cas de connexion possibles dans mon application Web:

  1. Connexion via la page de connexion: OK.
  2. Connexion via une page restreinte: OK aussi.
  3. Connexion via une page non restreinte: pas OK ... une page "produit" est accessible à tout le monde et un utilisateur peut poster un commentaire s'il est connecté. Donc, un formulaire de connexion est contenu dans la même page afin de permettre aux utilisateurs de se connecter.

Le problème avec le cas 3) est que je ne parviens pas à rediriger les utilisateurs vers la page "produit". Ils sont redirigés vers la page d'accueil après une connexion réussie, quoi qu'il arrive.

Notez qu'avec le cas 2), la redirection vers la page restreinte fonctionne immédiatement après une connexion réussie.

Voici la partie pertinente de mon fichier 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>

Je soupçonne que la "stratégie d'authentification pour chaque page" est responsable du problème. Cependant, si je le supprime, je ne peux plus me connecter ... j_spring_security_check envoie une erreur 404.


MODIFIER:

Grâce à Ralph, j'ai pu trouver une solution. Alors voici la chose: j'ai utilisé la propriété

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

que Ralph m'a montré. Après cela, j'ai eu un problème avec mon cas 1): lors de la connexion via la page de connexion, l'utilisateur restait sur la même page (et n'était pas redirigé vers la page d'accueil, comme il l'était auparavant). Le code jusqu'à cette étape était le suivant:

<!-- 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>

Cela devrait fonctionner, du moins en théorie, mais ce ne l'était pas. Je ne sais toujours pas pourquoi, donc si quelqu'un a une réponse à ce sujet, je créerai volontiers un nouveau sujet pour lui permettre de partager sa solution.

En attendant, je suis arrivé à une solution de contournement. Pas la meilleure solution, mais comme je l'ai dit, si quelqu'un a quelque chose de mieux à montrer, je suis tout ouïe. Voici donc la nouvelle politique d'authentification pour la page de connexion:

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

La solution ici est assez évidente: la page de connexion n’est autorisée que pour les utilisateurs anonymes. Une fois qu'un utilisateur est connecté, le gestionnaire d'erreurs le redirige vers la page d'accueil.

J'ai fait des tests et tout semble bien fonctionner.

50

Que se passe-t-il après la connexion (l’URL vers laquelle l’utilisateur est redirigé) est géré par le AuthenticationSuccessHandler.

Cette interface (une classe concrète l'implémentant est SavedRequestAwareAuthenticationSuccessHandler) est appelée par le AbstractAuthenticationProcessingFilter ou l'une de ses sous-classes comme (UsernamePasswordAuthenticationFilter) dans la méthode successfulAuthentication.

Donc, pour avoir une autre redirection dans le cas 3, vous devez sous-classer SavedRequestAwareAuthenticationSuccessHandler et le faire faire ce que vous voulez.


Parfois (en fonction de votre cas d'utilisation exact) il suffit d'activer le drapeau useReferer de AbstractAuthenticationTargetUrlRequestHandler qui est appelé par SimpleUrlAuthenticationSuccessHandler (super classe de 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>
42
Ralph

Je veux prolonger la réponse de Olcay. Son approche est bonne, votre contrôleur de page de connexion devrait être comme ceci pour mettre l’URL de référence en session:

@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";
}

Et vous devriez étendre SavedRequestAwareAuthenticationSuccessHandler et surcharger sa méthode onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication). Quelque chose comme ça:

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);
        }
    }
}

Ensuite, dans votre configuration de ressort, vous devez définir cette classe personnalisée en tant que bean et l’utiliser dans votre configuration de sécurité. Si vous utilisez annotation config, cela devrait ressembler à ceci (la classe que vous étendez de WebSecurityConfigurerAdapter):

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

Dans la méthode configure:

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

J'ai la solution suivante et cela a fonctionné pour moi.

Chaque fois que la page de connexion est demandée, écrivez la valeur du référent dans la session:

@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";
}

Ensuite, après la connexion, la mise en œuvre personnalisée de SavedRequestAwareAuthenticationSuccessHandler redirigera l'utilisateur vers la page précédente:

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

Rediriger l'utilisateur:

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

J'ai l'autorisation OAuth2 personnalisée et request.getHeader("Referer") n'est pas disponible au moment de la décision. Mais la demande de sécurité est déjà enregistrée dans ExceptionTranslationFilter.handleSpringSecurityException:

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

Donc, tout ce dont nous avons besoin est de partager requestCache comme bean Spring:

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

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

et l'utiliser quand l'autorisation est terminée:

@Autowired
private RequestCache requestCache;

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

La solution générique suivante peut être utilisée avec une connexion normale, une connexion Spring Social ou la plupart des autres filtres Spring Security.

Dans votre contrôleur Spring MVC, lors du chargement de la page du produit, enregistrez le chemin d'accès à la page du produit dans la session si l'utilisateur n'a pas été connecté. Dans la configuration XML, définissez l'URL cible par défaut. Par exemple:

Dans votre contrôleur Spring MVC, la méthode de redirection doit extraire le chemin de la session et renvoyer redirect:<my_saved_product_path>.

Ainsi, après que l'utilisateur se soit connecté, il sera envoyé à /redirect _ page, qui les redirigera rapidement vers la page du dernier produit visité.

0
Victor Lyuboslavsky

De retour à la page précédente après une connexion réussie, nous pouvons utiliser le gestionnaire d'authentification personnalisé suivant comme suit:

<!-- 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>

Classe CustomUserDetailsService

@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;
        }

}

Classe d'utilisateur

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;
        }

}

Classe 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;
        }
}
0
olyanren

Vous pouvez utiliser un SuccessHandler personnalisé étendant SimpleUrlAuthenticationSuccessHandler pour rediriger les utilisateurs vers différentes URL lors de la connexion en fonction des rôles qui leur ont été attribués.

La classe CustomSuccessHandler fournit une fonctionnalité de redirection personnalisée:

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;
    }
}

Extension de la classe Spring SimpleUrlAuthenticationSuccessHandler et substitution de la méthode handle () qui invoque simplement une redirection à l'aide de la méthode RedirectStrategy [valeur par défaut] configurée, avec l'URL renvoyée par la méthode defineTargetUrl () définie par l'utilisateur. Cette méthode extrait les rôles de l'utilisateur actuellement connecté à partir de l'objet Authentication, puis construit l'URL appropriée en fonction de ces rôles. Enfin, RedirectStrategy, responsable de toutes les redirections dans la structure Spring Security, redirige la demande vers l'URL spécifiée.

Enregistrement de CustomSuccessHandler à l'aide de la classe 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 est la classe responsable de la redirection éventuelle basée sur une logique personnalisée, qui consiste dans ce cas à rediriger l'utilisateur [vers student/admin/staff] en fonction de son rôle [USER/Student/SystemAdmin/UniversityAdmin/ExaminationsStaff/AcademicStaff].

0
Shanika Ediriweera

J'ai trouvé la solution de Utku Özdemir fonctionne dans une certaine mesure, mais en quelque sorte il défait le but de la demande sauvegardée puisque l'attribut de session aura la priorité sur elle. Cela signifie que les redirections vers des pages sécurisées ne fonctionneront pas comme prévu - après la connexion, vous serez envoyé à la page où vous vous trouviez à la place de la cible de la redirection. Par conséquent, vous pouvez également utiliser une version modifiée de SavedRequestAwareAuthenticationSuccessHandler au lieu de l’étendre. Cela vous permettra de mieux contrôler quand utiliser l'attribut de session.

Voici un exemple:

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);
    }
}

En outre, vous ne souhaitez pas enregistrer le référent lorsque l'authentification a échoué, car le référent sera alors la page de connexion elle-même. Vérifiez donc le paramètre error manuellement ou fournissez un RequestMapping distinct, comme ci-dessous.

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