成功登录后,Spring Security会重定向到上一页

时间:2013-01-29 00:19:49

标签: spring redirect login spring-security

我知道之前已经问过这个问题,但是我在这里遇到了一个特殊的问题。

我使用spring security 3.1.3。

我的网络应用程序中有3个可能的登录案例:

  1. 通过登录页面登录:确定。
  2. 通过限制页面登录:也可以。
  3. 通过非限制性页面登录:不行......每个人都可以访问“产品”页面,如果用户登录,用户可以发表评论。因此,登录表单包含在同一页面中,以便用户可以连接。
  4. 案例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错误。


    编辑:

    感谢拉尔夫,我找到了解决方案。所以这就是:我使用了属性

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

    此处的解决方案非常明显:只允许匿名用户使用登录页面。连接用户后,错误处理程序会将其重定向到主页。

    我做了一些测试,一切似乎都很好。

9 个答案:

答案 0 :(得分:42)

登录后会发生什么(用户被重定向到哪个网址)由AuthenticationSuccessHandler处理。

此接口(实现它的具体类是SavedRequestAwareAuthenticationSuccessHandler)由方法AbstractAuthenticationProcessingFilter中的UsernamePasswordAuthenticationFilter或其子类之一(successfulAuthentication)调用。 / p>

因此,为了在案例3中进行其他重定向,您必须继承SavedRequestAwareAuthenticationSuccessHandler并使其成为您想要的。


有时(取决于您的确切用例),只需启用由useReferer(超级AbstractAuthenticationTargetUrlRequestHandler)调用的SimpleUrlAuthenticationSuccessHandler SavedRequestAwareAuthenticationSuccessHandler标志即可。< / p>

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

答案 1 :(得分:35)

我想扩展 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
    ;
}

答案 2 :(得分:5)

我有以下解决方案,它对我有用。

每当请求登录页面时,将referer值写入会话:

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

答案 3 :(得分:0)

以下通用解决方案可用于常规登录,Spring Social登录或大多数其他Spring Security过滤器。

在Spring MVC控制器中,加载产品页面时,如果用户尚未登录,请在会话中保存产品页面的路径。在XML config中,设置默认目标URL。例如:

在Spring MVC控制器中,重定向方法应该从会话中读出路径并返回redirect:<my_saved_product_path>

因此,在用户登录后,他们会被发送到/redirect页面,这会立即将他们重定向回他们上次访问的产品页面。

答案 4 :(得分:0)

成功登录后返回上一页,我们可以使用以下自定义身份验证管理器:

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

CustomUserDetailsS​​ervice类

@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 Class

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

答案 5 :(得分:0)

您可以使用自定义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 [本例中为默认]使用用户定义的determineTargetUrl()方法返回的URL调用重定向。此方法从Authentication对象中提取当前登录用户的角色,然后根据角色构造适当的URL。最后,RedirectStrategy负责Spring Security框架内的所有重定向,将请求重定向到指定的URL。

使用SecurityConfiguration类注册CustomSuccessHandler:

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 /重定向用户[到student / admin / staff] AcademicStaff]。

答案 6 :(得分:0)

我发现Utku Özdemir's solution在某种程度上有效,但是由于会话属性优先于它,因此会破坏保存请求的目的。这意味着重定向到安全页面将无法按预期工作 - 登录后您将被发送到您所在的页面而不是重定向目标。因此,作为替代方案,您可以使用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,如下所示。

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

答案 7 :(得分:0)

我具有自定义的OAuth2授权,request.getHeader("Referer")在决定时不可用。但是安全请求已保存在ExceptionTranslationFilter.handleSpringSecurityException中:

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

因此,我们所需要的只是与Spring bean共享requestCache

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

答案 8 :(得分:0)

为了重定向到特定页面,无论用户角色是什么,都可以简单地使用 Spring的配置文件中的defaultSucessUrl。

@Override
protected void configure(HttpSecurity http) throws Exception {
    
    
      http.authorizeRequests() 
      .antMatchers("/admin").hasRole("ADMIN") 
      .and()
      .formLogin() .loginPage("/login") 
                    .defaultSuccessUrl("/admin",true)
      .loginProcessingUrl("/authenticateTheUser")
      .permitAll();