Cannot expose request attribute 'userName' because of an existing model object of the same name

一、背景说明

     在预发布环境中进行功能验证时,点击“我的反馈”菜单时出现Cannot expose request attribute 'userName' because of an existing model object of the same name 错误,在测试环境正常显示。

二、认识异常

11-Jun-2019 16:43:57.429 SEVERE    org.apache.catalina.core.StandardWrapperValve.invoke Servlet.service() for servlet [springServlet] in context with path [] threw exception [Cannot expose request attribute 'userName' because of an existing model object of the same name] with root cause
javax.servlet.ServletException: Cannot expose request attribute 'userName' because of an existing model object of the same name
at org.springframework.web.servlet.view.AbstractTemplateView.renderMergedOutputModel(AbstractTemplateView.java:123)
at org.springframework.web.servlet.view.AbstractView.render(AbstractView.java:303)
at org.springframework.web.servlet.DispatcherServlet.render(DispatcherServlet.java:1257)
at org.springframework.web.servlet.DispatcherServlet.processDispatchResult(DispatcherServlet.java:1037)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:980)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:897)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:970)
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:861)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:622)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:846)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:729)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:291)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:239)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:197)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:239)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:212)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:106)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:502)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:141)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:79)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:88)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:521)
at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1096)
at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:674)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1500)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.run(NioEndpoint.java:1456)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:745)

错误异常说明:request中存在同名的userName属性,获取失败
注:该错误会在Tomcat的server/logs/localhost.log日志中展示

三、排查问题

1、在LoginInteceptor中通过 request.setAttribute 方式添加了userName属性

public class LoginInteceptor implements HandlerInterceptor{
    @Override
    boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
	    throws Exception{
        /**......**/
	request.setAttribute("userName",'test');
	/**......**/
    }
}

2、同时在Controller中通过modelAndView.addObject 方式添加了userName属性

@RequestMapping("/myCall")
public ModelAndView myCall(ModelAndView modelAndView){
	modelAndView.setViewName("my/call");
	modelAndView.addObject("userName","test");
	return modelAndView ;
}

3、同时在Spring MVC的viewResolver解析器中配置了exposeRequestAttributes和exposeSessionAttributes值为ture

<bean id="viewResolver" class="org.springframework.web.servlet.view.velocity.VelocityLayoutViewResolver">
	/**......**/
	<property name="exposeRequestAttributes" value="true"/>
	<property name="exposeSessionAttributes" value="true"/>
	/**......**/
</bean>

exposeRequestAttributes 和 exposeSessionAttributes 设置为true的话会将request和session中的键值和值合并到modelAndView的Map参数中。

通过以上三项最终导致问题的产生

四、解决问题

方案一:修改Controller中参数名称userName,改为modelAndView.addObject("myUserName","test"),简单粗暴

方案二:在Spring MVC的viewResolver解析器配置中添加allowRequestOverride=true和allowSessionOverride=true配置,允许request和session设置的键值被覆盖,具体配置如下所示:

<bean id="viewResolver" class="org.springframework.web.servlet.view.velocity.VelocityLayoutViewResolver">
	/**......**/
	<property name="exposeRequestAttributes" value="true"/>
	<property name="allowRequestOverride" value="true"/>
	<property name="exposeSessionAttributes" value="true"/>
	<property name="allowSessionOverride" value="true"/>
	/**......**/
</bean>

注:两种方案都可以解决Cannot expose request attribute 'userName' because of an existing model object of the same name 问题

<think>嗯,用户遇到了Vue项目中CORS请求被阻止的问题,需要解决无效或缺失响应头的错误。结合提供的四个引用片段来看,问题核心在于跨域配置的完整链路需要前后端协同解决。用户可能刚接触前后端分离开发,对CORS机制理解不够深入。从ta提问的方式看,已经意识到是响应头缺失问题,但没完全理清前后端各自的职责边界。这在实际开发中很常见,尤其当后端服务由其他团队维护时。仔细分析引用内容:引用[1]明确指出CORS链路中断的两个关键点——前端withCredentials设置和后端AllowedHeaders配置。引用[4]补充了默认暴露的简单响应头范围,说明自定义头部需要显式配置。引用[3]展示的响应拦截器代码特别实用,可以帮用户处理401等认证错误。用户的需求表面是解决CORS错误,但深层需求其实是建立完整的跨域认证机制。考虑到ta使用Vue,需要强调axios的配置细节,因为很多初学者会忽略credentials配置项。方案设计上必须分层说明:先快速修复让请求通过,再完善安全措施。特别要强调预检请求(OPTIONS)的非简单请求处理,这是引用[2]提到的难点。对于后端配置示例,选用express.js演示是因为其语法清晰,用户即使使用其他语言也能类推。最后加入的拦截器异常处理特别重要。实践中很多开发者配置完CORS就以为万事大吉,但引用[3]显示业务层面的令牌刷新也需纳入考虑——这正是用户可能遇到的后续问题。</think> 解决Vue中的CORS请求被阻止问题需要前后端协同配置。以下是完整解决方案: ### 🛠️ 前端关键配置(Vue/Axios) ```javascript // axios配置(request.js) const service = axios.create({ baseURL: process.env.VUE_APP_BASE_API, withCredentials: true, // 允许携带Cookie [^1] timeout: 5000 }) // 请求拦截器(添加X-XSRF-TOKEN) service.interceptors.request.use(config => { const token = getCookie('XSRF-TOKEN') // 从Cookie获取Token if (token) { config.headers['X-XSRF-TOKEN'] = token // 注入请求头 [^1] } return config }) ``` ### 🔧 后端关键配置(以Express为例) ```javascript // CORS配置(需在所有路由前加载) app.use(cors({ origin: 'https://your-vue-domain.com', // 指定允许的源 credentials: true, // 允许凭证传输 [^1] allowedHeaders: ['Content-Type', 'X-XSRF-TOKEN'] // 显式声明允许的请求头 [^1][^4] })) // 设置安全响应头(非简单响应首部需显式暴露) app.use((req, res, next) => { res.header('Access-Control-Expose-Headers', 'Authorization, X-Custom-Header') // [^4] next() }) ``` ### 🧩 核心问题解决链条 1. **凭证携带缺失** → 前端设置 `withCredentials: true` ```javascript // 错误示例:默认不携带Cookie导致认证失败 axios.get('https://api.com/data') // 跨域请求被浏览器拦截 ``` 2. **自定义请求头被过滤** → 后端配置 `allowedHeaders` ```javascript // 必需配置(缺失则X-XSRF-TOKEN被CORS策略过滤)[^1] app.use(cors({ allowedHeaders: ['X-XSRF-TOKEN'] })) ``` 3. **自定义响应头不可见** → 后端暴露响应头 ```javascript // 否则前端无法读取Authorization等非简单响应头[^4] res.header('Access-Control-Expose-Headers', 'Authorization') ``` ### 🚨 拦截器错误处理(Vue示例) ```javascript // 响应拦截器(处理401/403等CORS相关错误)[^3] service.interceptors.response.use( response => response, error => { if (error.response.status === 401) { // Token过期时跳转登录页 router.push('/login') } return Promise.reject(error) } ) ``` ### ✅ 验证步骤 1. 浏览器F12检查Network: - ✔️ 请求头包含 `X-XSRF-TOKEN` - ✔️ 响应头包含 `Access-Control-Allow-Origin` - ✔️ `withCredentials` 为 `true` 2. 后端日志确认收到 `Cookie` 和 `X-XSRF-TOKEN` ### 📜 相关RFC规范 - CORS协议要求:预检请求(OPTIONS)必须响应 `Access-Control-Allow-Headers` - 安全规范:`Access-Control-Allow-Origin` 禁止使用通配符 `*` 当携带凭证时 ---
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值