Spring Security在成功login后redirect到上一页

我知道这个问题之前已经被问过了,但是我在这里面临着一个特殊的问题。

我使用弹簧安全3.1.3。

我的Web应用程序中有3个可能的login情况:

  1. 通过login页面login:OK。
  2. 通过受限页面login:也可以。
  3. 通过非限制性页面login:不行…每个人都可以访问“产品”页面,如果用户login,用户可以发表评论。 因此,login表单被包含在同一页面中以允许用户连接。

情况3)的问题是我无法将用户redirect到“产品”页面。 无论如何,他们会在成功login后redirect到主页。

请注意,对于情况2)成功login后,redirect到受限制页面的方式是开箱即用的。

这是我的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> 

我怀疑“每个页面的身份validation策略”是对这个问题负责。 但是,如果我删除它,我不能再login… j_spring_security_check发送404错误。


编辑:

感谢拉尔夫,我能find一个解决scheme。 所以这是事情:我使用的财产

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

拉尔夫给我看。 之后,我遇到了一个问题1):通过login页面进行login时,用户停留在同一页面(而不是redirect到主页面,就像以前一样)。 直到这个阶段的代码如下:

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

至less在理论上这应该起作用,但事实并非如此。 我仍然不知道为什么,所以如果有人有这个答案,我会很乐意创造一个新的话题,让他分享他的解决scheme。

与此同时,我来了一个解决方法。 不是最好的解决办法,但正如我所说,如果有人有更好的展示,我全部耳朵。 所以这是login页面的新authentication策略:

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

这里的解决scheme非常明显:login页面只允许匿名用户使用。 一旦用户连接,error handling程序将他redirect到主页。

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

login后(用户redirect到哪个url)会发生什么情况由AuthenticationSuccessHandler处理。

这个接口(一个实现它的具体类是SavedRequestAwareAuthenticationSuccessHandler )被AbstractAuthenticationProcessingFilter或者它的一个子类调用,例如方法successfulAuthentication中的( UsernamePasswordAuthenticationFilter )。

所以为了在SavedRequestAwareAuthenticationSuccessHandler情况下有一个其他的redirect,你必须SavedRequestAwareAuthenticationSuccessHandler子类SavedRequestAwareAuthenticationSuccessHandler并使它做你想做的事情。


有时候(取决于你的确切用例)足够启用由SimpleUrlAuthenticationSuccessHandlerSavedRequestAwareAuthenticationSuccessHandler超类)调用的AbstractAuthenticationTargetUrlRequestHandleruseReferer标志。

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

我想扩展Olcay的很好的答案。 他的方法是好的,你的login页面控制器应该是这样的推荐人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); } } } 

然后,在您的springconfiguration中,您应该将此自定义类定义为一个bean,并将其用于您的安全性configuration。 如果您使用注释configuration ,它应该看起来像这样(您从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 ; } 

我有以下的解决scheme,它为我工作。

每当请求login页面时,将引用值写入会话:

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

然后,成功login后, SavedRequestAwareAuthenticationSuccessHandler自定义实现将用户redirect到上一页:

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

redirect用户:

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

以下通用解决scheme可以用于常规login,Spring社交login或大多数其他Spring Securityfilter。

在您的Spring MVC控制器中,加载产品页面时,如果用户尚未login,请将会话path保存到会话中。在XMLconfiguration中,设置默认目标URL。 例如:

在你的Spring MVC控制器中,redirect方法应该从会话中读出path并返回redirect:<my_saved_product_path>

所以,用户login后,他们将被发送到/redirect页面,这将立即将他们redirect到他们上次访问的产品页面。

在成功login后返回上一页,我们可以使用下面的自定义身份validationpipe理器,如下所示:

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

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

您可以使用Custom SuccessHandler扩展SimpleUrlAuthenticationSuccessHandler,以根据其分配的angular色login时将用户redirect到不同的URL。

CustomSuccessHandler类提供自定义redirectfunction:

 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()方法,该方法只需使用configuration的RedirectStrategy [默认情况下]用用户定义的determineTargetUrl()方法返回的URL调用redirect。 此方法从authentication对象中提取当前login用户的angular色,然后根据angular色构build适当的URL。 最后,RedirectStrategy负责Spring Security框架内的所有redirect,将请求redirect到指定的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是负责基于任何自定义逻辑的最终redirect的类,在这种情况下,根据他的angular色[USER / Student / SystemAdmin / UniversityAdmin / ExaminationsStaff / AcademicStaff]来redirect用户[给学生/pipe理员/员工]。

我发现UtkuÖzdemir的解决scheme在某种程度上起作用,但是由于会话属性将优先于保存的请求的目的, 这意味着redirect到安全页面将无法按预期工作 – login后,您将被发送到您所在的页面,而不是redirect目标。 所以作为替代scheme,您可以使用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); } } 

另外,当authentication失败时,您不希望保存引用者,因为引用者将成为login页面本身。 因此,手动检查错误参数或提供一个单独的RequestMapping,如下所示。

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