Spring Security登录成功后重定向至用户输入URL的问题求助
问题分析与解决方案
我用Spring Security实现基于角色的认证,管理员和普通用户共用登录入口,登录后分别跳转到adminhome和userhome页面。但未登录时直接访问管理员/用户仪表盘URL,系统会要求登录,登录成功后却不会重定向到输入的URL,反而抛出cannot call sendRedirect错误。
安全配置代码
import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.authentication.dao.DaoAuthenticationProvider; 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.Authentication; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; import org.springframework.web.util.UrlPathHelper; @Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private UserDetailsService userDetailsService; @Autowired private LoginSuccessHandler loginSuccessHandler; @Bean AuthenticationProvider authenticationProvider() { DaoAuthenticationProvider provider=new DaoAuthenticationProvider(); provider.setUserDetailsService(userDetailsService); provider.setPasswordEncoder(new BCryptPasswordEncoder()); return provider; } @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests().antMatchers("/").permitAll() .antMatchers("/userhome").hasAuthority("USER"). antMatchers("/adminhome").hasAuthority("ADMIN") .antMatchers("/register").permitAll() .and().formLogin().loginPage("/login") .successHandler(loginSuccessHandler) .permitAll().and().logout().logoutSuccessHandler(new LogoutSuccessHandler() { @Override public void onLogoutSuccess(HttpServletRequest request,HttpServletResponse response,Authentication authentication) throws IOException,ServletException{ System.out.println("The User "+authentication.getName() + " has logged out"); UrlPathHelper helper=new UrlPathHelper(); String context=helper.getContextPath(request); response.sendRedirect(context+"/home"); } }).permitAll(); http.csrf().disable(); } @Bean public BCryptPasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } }
登录成功处理器代码
package strictly.cinema.config; import java.io.IOException; import java.util.Collection; import javax.servlet.ServletException; 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.authentication.SavedRequestAwareAuthenticationSuccessHandler; import org.springframework.stereotype.Component; import strictly.cinema.service.CustomUserDetails; @Component public class LoginSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler{ @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws ServletException, IOException { CustomUserDetails userDetails = (CustomUserDetails) authentication.getPrincipal(); Collection<? extends GrantedAuthority> authorities =userDetails.getAuthorities(); authorities.forEach(auth->System.out.println(auth.getAuthority())); String redirectURL=request.getContextPath(); if(userDetails.hasRole("USER")) redirectURL+="/userhome"; else if(userDetails.hasRole("ADMIN")) redirectURL+="/adminhome"; response.sendRedirect(redirectURL); super.onAuthenticationSuccess(request, response, authentication); } }
问题原因
你的LoginSuccessHandler继承了SavedRequestAwareAuthenticationSuccessHandler,这个类的核心作用就是在登录成功后,重定向到用户原本想要访问的受保护URL(即登录前输入的仪表盘URL)。但当前实现存在两个问题:
- 先调用
response.sendRedirect(redirectURL)强制跳转到角色主页,此时响应已提交,后续调用super.onAuthenticationSuccess(...)时再尝试处理重定向,就会抛出cannot call sendRedirect错误。 - 强制跳转的逻辑覆盖了
SavedRequestAwareAuthenticationSuccessHandler原本的"重定向到原请求URL"的机制。
解决方案
修改登录成功处理器,优先处理原请求URL,只有当用户直接从登录页登录时,才按角色跳转对应主页。推荐两种实现方式:
方式一:直接重写跳转逻辑
package strictly.cinema.config; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.security.core.Authentication; import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler; import org.springframework.stereotype.Component; import strictly.cinema.service.CustomUserDetails; @Component public class LoginSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler { @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws ServletException, IOException { // 获取用户登录前试图访问的URL String targetUrl = determineTargetUrl(request, response, authentication); // 存在有效原请求URL时,直接重定向过去 if (targetUrl != null && !targetUrl.equals(request.getContextPath() + "/") && !targetUrl.equals(request.getContextPath() + "/login")) { getRedirectStrategy().sendRedirect(request, response, targetUrl); return; } // 无原请求时,按角色跳转对应主页 CustomUserDetails userDetails = (CustomUserDetails) authentication.getPrincipal(); String redirectURL = request.getContextPath(); if (userDetails.hasRole("USER")) { redirectURL += "/userhome"; } else if (userDetails.hasRole("ADMIN")) { redirectURL += "/adminhome"; } getRedirectStrategy().sendRedirect(request, response, redirectURL); } }
方式二:重写目标URL判断方法
package strictly.cinema.config; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.security.core.Authentication; import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler; import org.springframework.stereotype.Component; import strictly.cinema.service.CustomUserDetails; @Component public class LoginSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler { @Override protected String determineTargetUrl(HttpServletRequest request, HttpServletResponse response, Authentication authentication) { // 先尝试获取原请求URL String targetUrl = super.determineTargetUrl(request, response, authentication); // 如果是直接从登录页/根路径登录,按角色跳转 if (targetUrl.equals(request.getContextPath() + "/login") || targetUrl.equals(request.getContextPath() + "/")) { CustomUserDetails userDetails = (CustomUserDetails) authentication.getPrincipal(); if (userDetails.hasRole("USER")) { return request.getContextPath() + "/userhome"; } else if (userDetails.hasRole("ADMIN")) { return request.getContextPath() + "/adminhome"; } } // 否则返回原请求URL return targetUrl; } }
修改后效果:
- 用户未登录直接访问
/adminhome或/userhome,登录成功后自动重定向到该URL(需用户角色有权限) - 用户直接从登录页登录时,跳转到对应角色的主页
- 不再出现cannot call sendRedirect错误
内容的提问来源于stack exchange,提问作者Subhash Yuvaraj
相关产品推荐
相关产品推荐

