You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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)。但当前实现存在两个问题:

  1. 先调用response.sendRedirect(redirectURL)强制跳转到角色主页,此时响应已提交,后续调用super.onAuthenticationSuccess(...)时再尝试处理重定向,就会抛出cannot call sendRedirect错误。
  2. 强制跳转的逻辑覆盖了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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.14 03:20:36