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

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校验直接进入业务方法,自然首次点击就能正常发邮件,但会失去防重复提交的能力,用户多次点击按钮会发送多封重复邮件。

解决方案

按以下步骤调整即可:

  1. 修正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>
  1. 检查前端表单,添加Struts2的Token标签
    在你提交的forgotPassForm表单内部,必须加入<s:token />标签,页面加载时标签会自动生成携带有效Token值的隐藏域,提交时随表单数据一起传给后端做校验:
<form id="forgotPassForm" action="myActionName" method="post">
    <!-- 原有表单项:用户名、邮箱、验证码等 -->
    <s:token /> <!-- 这行必须添加,不能省略 -->
    <button type="button" id="myActionNameId">发送邮件</button>
</form>
  1. (可选,适配无刷新重复提交场景)更新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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 08:06:19