response.sendRedirect所引发的问题及解决

本文探讨了在Servlet跳转到JSP时遇到的样式和图片丢失问题, 提出了使用绝对路径代替不稳定相对路径的方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在servlet跳转到 jsp 中,有时使用response.sendRedirect(request.getContextPath()+"/login.jsp"); 跳转到登录页面,却发现没有任何样式和图片。

因为“当前路径”这个概念在J2EE中是不稳定的。

所以最好都是绝对路径,类似于:
  <% String cp = request.getContextPath();%>  这句放JSP页面开头
  <img src="<%=cp%>/images/xxoo.jpg" />  后面都类似这么写

其中前面的<% %>这段文字主要是得到你应用发布的Context名称,将所有的路径都转化为绝对路径,并没有直接写死,这样就算改了项目名称也不会影响到函数调用。




package com.kucun.Config; import java.io.IOException; import java.io.InputStream; import java.util.Map; import javax.json.Json; import javax.servlet.Filter; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.security.authentication.AuthenticationServiceException; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 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.config.http.SessionCreationPolicy; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.security.web.access.AccessDeniedHandler; import org.springframework.security.web.authentication.AuthenticationFailureHandler; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import com.fasterxml.jackson.databind.ObjectMapper; import com.kucun.Config.user.CustomUserDetails; // 2. 基础安全配置 @Configuration @EnableWebSecurity // 启用Web安全功能 public class SecurityConfig extends WebSecurityConfigurerAdapter{ /** * 核心安全过滤器链配置 * @param http HTTP安全构建器 * @return 安全过滤器链 * @throws Exception 配置异常 * * █ 配置逻辑说明: * 1. authorizeHttpRequests: 定义访问控制规则 * 2. formLogin: 配置表单登录 * 3. logout: 配置注销行为 * 4. exceptionHandling: 处理权限异常[^3] */ // 修正后的配置方法 @Override protected void configure(HttpSecurity http) throws Exception { http .csrf().disable() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .addFilterBefore(jsonAuthFilter(), UsernamePasswordAuthenticationFilter.class) // 关键配置 .authorizeRequests() .antMatchers("/login.html").permitAll() .antMatchers(HttpMethod.POST, "/users/login").permitAll() .antMatchers("/users/guanli/**").hasAuthority("ROLE_ADMIN") .antMatchers("/js/**", "/css/**", "/fonts/**", "/images/**","/*").permitAll() .anyRequest().authenticated() .and() .formLogin().disable() // .loginPage("/login.html") // .loginProcessingUrl("/users/login") // // .successHandler(ajaxAuthenticationSuccessHandler()) // 自定义成功处理器 // .failureHandler(ajaxAuthenticationFailureHandler()) // 自定义失败处理器 // .defaultSuccessUrl("/index.html") // .failureUrl("/login.html?error=true") // .usernameParameter("andy") // 修改用户名参数名 // .passwordParameter("pass") // 修改密码参数名 // .and() .logout() .logoutUrl("/logout") .logoutSuccessUrl("/login.html") .and() .csrf() .ignoringAntMatchers("/users/login") .and() .headers() .frameOptions().sameOrigin() .and() .exceptionHandling() .accessDeniedHandler(accessDeniedHandler()); // 统一使用Handler } // 返回JSON格式的成功响应 @Bean public AuthenticationSuccessHandler ajaxAuthenticationSuccessHandler() { return (request, response, authentication) -> { response.setContentType(MediaType.APPLICATION_JSON_VALUE); CustomUserDetails userDetails = (CustomUserDetails) authentication.getPrincipal(); response.setStatus(HttpStatus.OK.value()); System.out.println(authentication.getPrincipal()+""+authentication.getName()); if (request.getHeader("X-Requested-With") == null) { // 非AJAX请求 response.sendRedirect("/index.html"); } else { //String re=userDetails.getUser().toString() new ObjectMapper().writeValue(response.getWriter(), userDetails.getUser() ); } }; } // 返回401状态码和错误信息 @Bean public AuthenticationFailureHandler ajaxAuthenticationFailureHandler() { return (request, response, exception) -> { if (request.getHeader("X-Requested-With") == null) { response.sendRedirect("/login.html?error=true"); } else { response.setStatus(HttpStatus.UNAUTHORIZED.value()); response.getWriter().write("{\"error\":\"Authentication failed\"}"); } }; } // 处理未认证请求 @Bean public AuthenticationEntryPoint ajaxAuthenticationEntryPoint() { return (request, response, exception) -> { if (request.getHeader("X-Requested-With") == null) { response.sendRedirect("/login.html?error=true"); } else { response.setStatus(HttpStatus.UNAUTHORIZED.value()); response.getWriter().write("{\"error\":\"Authentication failed\"}"); } }; } @Bean public JsonUsernamePasswordAuthenticationFilter jsonAuthFilter() throws Exception { JsonUsernamePasswordAuthenticationFilter filter = new JsonUsernamePasswordAuthenticationFilter(); filter.setAuthenticationManager(authenticationManagerBean()); filter.setUsernameParameter("andy"); // 设置自定义参数名 filter.setPasswordParameter("pass"); filter.setFilterProcessesUrl("/users/login"); filter.setAuthenticationSuccessHandler(ajaxAuthenticationSuccessHandler()); filter.setAuthenticationFailureHandler(ajaxAuthenticationFailureHandler()); return filter; } /** * 密码编码器(必须配置) * 使用BCrypt强哈希算法加密 */ @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Bean public AccessDeniedHandler accessDeniedHandler() { System.out.println("0000"); return (request, response, ex) -> { if (!response.isCommitted()) { response.sendRedirect("/error/403"); } }; } } class JsonUsernamePasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter { private final ObjectMapper objectMapper = new ObjectMapper(); @Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { System.out.println("收到认证请求,路径:" + request.getRequestURI()); System.out.println("请求方法:" + request.getMethod()); System.out.println("Content-Type:" + request.getContentType()); if (request.getContentType() != null && request.getContentType().startsWith(MediaType.APPLICATION_JSON_VALUE)) { try (InputStream is = request.getInputStream()) { Map<String, String> authMap = objectMapper.readValue(is, Map.class); String username = authMap.getOrDefault(getUsernameParameter(), ""); String password = authMap.getOrDefault(getPasswordParameter(), ""); // 调试日志 System.out.println("Authentication attempt with: " + username+'_'+ password); UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password); setDetails(request, authRequest); return this.getAuthenticationManager().authenticate(authRequest); } catch (IOException e) { throw new AuthenticationServiceException("认证请求解析失败", e); } } return super.attemptAuthentication(request, response); } } package com.kucun.Config; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.stereotype.Service; import com.kucun.Config.Role.RoleConverter; import com.kucun.Config.user.CustomUserDetails; import com.kucun.data.entity.User; import com.kucun.dataDo.UserRepository; /** * 获取数据 * @author Administrator * */ @Service public class CustomUserDetailsService implements UserDetailsService { @Autowired private UserRepository userRepository; @Autowired private RoleConverter roleConverter; public CustomUserDetailsService() { super(); System.out.println("11111"); } /** * 获取数据库中用户信息 * @param andy 账号 * * @return * */ @Override public UserDetails loadUserByUsername(String andy) { System.out.println(andy); User user = userRepository.findByAndy(andy); System.out.println(user); return new CustomUserDetails(user, roleConverter.convert(user.getRole()) // 关键转换点[^1] ); } }package com.kucun.Config.user; import java.util.Collection; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import com.kucun.data.entity.User; public class CustomUserDetails implements UserDetails { /** * */ private static final long serialVersionUID = 1L; private final String andy; // 对应andy字段 private final String name; private final int role; private final String password; private final User users; private final Collection<? extends GrantedAuthority> authorities; public CustomUserDetails(User user, Collection<? extends GrantedAuthority> authorities) { this.andy = user.getAndy(); this.name = user.getName(); this.role = user.getRole(); this.password = user.getPass(); user.setPass(null); this.users=user; this.authorities = authorities; } // 实现UserDetails接口方法 @Override public String getUsername() { return andy; } @Override public String getPassword() { return password; } @Override public Collection<? extends GrantedAuthority> getAuthorities() { return authorities; } // 自定义字段访问方法 public String getName() { return name; } public User getUser() { return users; } public int getRole() { return role; } // 其他必要方法 @Override public boolean isAccountNonExpired() { return true; } @Override public boolean isAccountNonLocked() { return true; } @Override public boolean isCredentialsNonExpired() { return true; } @Override public boolean isEnabled() { return true; } } index.js function checkLoginStatus() { if(window.location.href== '/KuCun2/login.html')return; const username = localStorage.getItem("name"); if(!username) window.location.href = '/KuCun2/login.html?11212121'; } login.js // 登录按钮点击事件 function login(datas) { var data={} datas.each(function(a,element){ alert() data[$(element).attr('name')]=$(element).val() }) //var data={ andy,pass } // 模拟 AJAX 请求 https("/KuCun2/users/login",data,function (response) { alert("1"); if (response.name) { localStorage.setItem("name", response.name); // 保存用户名到本地存储 localStorage.setItem("role", response.role); // 保存权限到本地存储 window.location.href = '/KuCun2/index.html'; } else { alert("登录失败,请检查用户名和密码!"); } }) };登陆之后跳转index.html又会被重定向登录界面
最新发布
05-30
### 错误原因分析 `java.lang.IllegalStateException: Cannot call sendRedirect() after the response has been committed` 是一种常见的异常,通常发生在尝试在响应已经被提交的情况下再次修改 HTTP 响应头或重定向时。HTTP 协议规定,在服务器向客户端发送数据之后,不能再更改响应的状态码或头部信息。 此问题可能由以下几个因素引起: - **过滤器链中的顺序不当**:如果 `sendRedirect()` 被调用前已经有部分响应被写入到输出流中,则会抛出该异常[^1]。 - **拦截器逻辑错误**:Spring MVC 的拦截器方法(如 `preHandle` 或 `postHandle`)可能会因为不恰当的操作导致响应提前结束[^4]。 - **Session 创建时机不对**:某些情况下,开发者试图在已经完成的部分响应后再创建 Session 或执行其他操作,这也会引发类似的异常[^3]。 --- ### 解决方案 #### 方法一:调整代码逻辑以避免过早提交响应 确保所有的业务逻辑都在响应未提交之前处理完毕。可以通过以下方式实现: ```java @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletResponse httpResponse = (HttpServletResponse) response; HttpServletRequest httpRequest = (HttpServletRequest) request; // 验证逻辑 String registrationKey = httpRequest.getParameter("registrationKey"); boolean isValid = validateRegistrationCode(registrationKey); if (!isValid) { // 如果验证失败,立即跳转并终止后续流程 httpResponse.sendRedirect(httpRequest.getContextPath() + "/alert/macError"); return; // 终止进一步的请求处理 } // 只有当验证通过时才继续执行后续逻辑 chain.doFilter(request, response); } ``` 在此示例中,`sendRedirect()` 和 `chain.doFilter()` 不会被同时触发,从而防止响应冲突。 --- #### 方法二:延迟创建 Session 有时,开发人员会在 `sendRedirect()` 后再尝试获取或创建新的 Session 对象,这种做法会导致异常。因此建议尽早创建 Session 并将其存储起来供后续使用。 以下是改进后的代码片段: ```java if (!result) { HttpSession session = request.getSession(false); // 尝试获取现有session而不强制创建新session if (session == null) { session = request.getSession(true); // 显式创建一个新的session对象 } response.sendRedirect("/alert/macError"); // 执行重定向 } else { chain.doFilter(request, response); // 正常放行请求 } ``` 这里的关键在于先判断是否存在有效的 Session 实例;如果没有则显式地创建它,而不是等到后面再去访问。 --- #### 方法三:自定义缓冲区大小控制 默认情况下,Tomcat 等容器会对每个请求分配一定大小的缓冲区来暂存输出内容。一旦超出这个限制就会自动刷新缓存并将部分内容写出给客户端。此时即使尚未完全构建好整个页面也可能被认为已提交了相应的一部分数据而无法再做任何改变。 可以考虑增加服务端配置项或者设置更大的初始缓冲容量: ```xml <Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" buffer_size="65536"/> <!-- 设置较大的buffer size --> ``` 另外也可以直接编程设定 Response Buffer Size : ```java response.setBufferSize(65536); // 设定较大数值作为预设缓冲空间 ``` 这种方法适用于那些确实存在大量动态生成 HTML 数据场景下减少频繁刷盘次数提高性能的同时也间接降低了因超限而导致提前commit的风险[^1]. --- ### 总结 以上三种策略分别针对不同层面进行了优化——从程序设计角度出发重新梳理业务流程确保不会发生重复操作;合理安排资源申请次序规避潜在风险以及适当调节底层参数提升整体稳定性。具体采用哪种取决于实际项目需求和个人偏好。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值