Struts2 token拦截器致邮件发送首次点击失效问题排查
问题产生原因
核心是Struts2 Token拦截器的配置和使用不符合规范,具体有两点:
- 拦截器配置错误:你手动在Action下零散配置了
token、params、basicStack三个拦截器,覆盖了Struts2默认加载的完整拦截器栈defaultStack,既缺少Token运行需要的配套逻辑,也打乱了拦截器执行顺序。首次加载表单时,服务端没有正常生成和当前表单绑定的有效Token,第一次提交时Token校验不通过,不会执行发邮件的业务逻辑;第一次请求触发后服务端才生成有效Token写入session,第二次点击提交时Token匹配,才会正常执行业务。 - Ajax场景下的Token结果配置错误:你给
invalid.token(Token校验失败的结果)配置的是跳转到/index.jsp的页面视图,Ajax请求拿到完整页面内容后无法正常解析成你预期的JSON结构,直接走到失败分支,没有抛出明确的校验失败提示。
补充:注释掉Token拦截器配置后,请求会跳过Token校验直接进入业务方法,自然首次点击就能正常发邮件,但会失去防重复提交的能力,用户多次点击按钮会发送多封重复邮件。
解决方案
按以下步骤调整即可:
- 修正
struts.xml中的拦截器和结果配置
不要手动零散拼接拦截器,直接引用默认完整拦截器栈后追加Token拦截器,同时把Token校验失败的结果改成JSON格式,适配Ajax请求的解析逻辑:
<action name="myActionName" class="actions.myActionName" method="myActionName"> <!-- 引用默认完整拦截器栈,保证参数解析、异常处理等基础逻辑正常 --> <interceptor-ref name="defaultStack"/> <!-- 追加Token防重复提交拦截器 --> <interceptor-ref name="token"/> <exception-mapping result="passRecoveryException" exception="exceptions.PasswordRecoveryException" /> <result name="success" type="json" /> <result name="passRecoveryException" type="json" /> <!-- Token校验失败时返回JSON格式提示,适配Ajax请求 --> <result name="invalid.token" type="json"> <param name="success">false</param> <param name="message">token:页面状态已过期,请刷新后重试</param> </result> </action>
- 检查前端表单,添加Struts2的Token标签
在你提交的forgotPassForm表单内部,必须加入<s:token />标签,页面加载时标签会自动生成携带有效Token值的隐藏域,提交时随表单数据一起传给后端做校验:
<form id="forgotPassForm" action="myActionName" method="post"> <!-- 原有表单项:用户名、邮箱、验证码等 --> <s:token /> <!-- 这行必须添加,不能省略 --> <button type="button" id="myActionNameId">发送邮件</button> </form>
- (可选,适配无刷新重复提交场景)更新Ajax回调逻辑
如果你需要支持页面不刷新的情况下多次提交,需要在Ajax请求的成功回调里,把服务端返回的新Token更新到表单隐藏域中,避免上次提交后旧Token失效导致下次请求失败。在你原有回调逻辑开头追加Token更新代码即可:
submitByAjaxWithConfirmwithCallBackJSON(formAction,formdata,function(data){ // 追加Token更新逻辑 if(data.token && data.tokenName){ document.querySelector('input[name="'+data.tokenName+'"]').value = data.token; } // 以下是你原有的回调逻辑,不需要修改 if(data.exception==undefined){ if(data.success==true){ $('#loading').hide(); showToaster(1,data.message.split(':')[1],data.message.split(':')[0]); }else{ showToaster(2,data.message.split(':')[1],data.message.split(':')[0]); $('#loading').hide(); } }else{ var msg = data.exception.message.split(':'); showToaster(2,msg[1],msg[0]); $('#loading').hide(); } })
内容的提问来源于stack exchange,提问作者Suraj Nath
相关产品推荐
相关产品推荐

