【Struts2】自定义登录检查拦截器

在Web应用开发中,登录状态检查是权限控制的基础环节。Struts2的拦截器(Interceptor)机制提供了一种灵活的方式,可在Action执行前后插入自定义逻辑(如登录验证、日志记录、参数预处理等)。通过自定义登录检查拦截器,我们可以统一拦截未登录用户的非法访问,保障系统安全性,同时避免在每个Action中重复编写登录验证代码。

本文将详细讲解Struts2自定义登录拦截器的实现步骤、配置方式,并结合最佳实践和常见问题分析,帮助读者快速掌握这一技术。

目录#

Struts2拦截器基础#

拦截器的概念#

Struts2的拦截器是AOP(面向切面编程)思想的实践,它允许在Action执行的生命周期中插入自定义逻辑(如登录验证、参数过滤、日志记录等)。拦截器围绕ActionInvocation(Action执行上下文)形成一个拦截器链,每个拦截器依次执行intercept()方法,最终调用目标Action。

工作原理#

  1. 当请求到达Struts2的核心控制器Dispatcher时,会根据配置找到对应的Action,并构建拦截器链(Interceptor Stack)。
  2. 拦截器链中的每个拦截器依次执行intercept()方法,通过invocation.invoke()调用下一个拦截器或目标Action。
  3. Action执行完成后,拦截器链会反向执行(类似递归回溯),允许在Action执行后再处理逻辑(如结果渲染、日志记录)。

与过滤器(Filter)的区别#

特性拦截器(Interceptor)过滤器(Filter)
技术规范Struts2框架特有Servlet规范
处理范围仅处理Action请求可处理所有请求(含静态资源)
底层实现基于Java反射(针对Action)基于Servlet API
执行时机Action执行前后请求进入容器后、响应输出前
灵活性可访问Action上下文、方法仅能访问请求/响应对象

自定义登录检查拦截器的步骤#

要实现一个自定义的登录检查拦截器,需遵循以下步骤:

  1. 创建拦截器类:继承AbstractInterceptor或实现Interceptor接口,重写intercept()方法。
  2. 实现登录检查逻辑:在intercept()中检查Session是否存在用户信息,决定是继续执行Action还是跳转到登录页。
  3. 配置拦截器:在struts.xml中定义拦截器、拦截器栈,并为Action指定拦截器引用。

示例:登录拦截器的实现与配置#

拦截器类编写#

我们通过继承AbstractInterceptor(推荐,已实现Interceptor接口的部分方法)来实现登录拦截器:

package com.example.interceptor;
 
import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.interceptor.AbstractInterceptor;
import org.apache.struts2.ServletActionContext;
 
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
 
/**
 * 登录检查拦截器:检查Session中是否存在用户信息,不存在则跳转登录页(或返回Ajax提示)
 */
public class LoginInterceptor extends AbstractInterceptor {
 
    @Override
    public String intercept(ActionInvocation invocation) throws Exception {
        // 1. 获取Action上下文、请求、Session
        ActionContext context = invocation.getInvocationContext();
        HttpServletRequest request = ServletActionContext.getRequest();
        HttpServletResponse response = ServletActionContext.getResponse();
        HttpSession session = request.getSession();
 
        // 2. 检查Session中的用户信息(假设登录后存入"user")
        Object user = session.getAttribute("user");
 
        // 3. 排除登录相关的Action/方法(避免循环拦截)
        String actionName = invocation.getAction().getClass().getName();
        String methodName = invocation.getProxy().getMethod();
        if (actionName.contains("LoginAction") && "login".equals(methodName)) {
            return invocation.invoke(); // 登录方法直接放行
        }
 
        // 4. 登录状态判断
        if (user != null) {
            // 已登录,继续执行后续拦截器或Action
            return invocation.invoke();
        } else {
            // 检查是否为Ajax请求
            String ajaxHeader = request.getHeader("X-Requested-With");
            if ("XMLHttpRequest".equals(ajaxHeader)) {
                // Ajax请求:返回JSON格式的未登录提示
                response.setContentType("application/json;charset=utf-8");
                response.getWriter().write("{\"code\":401,\"msg\":\"请登录后操作\"}");
                return null; // 终止执行,不返回结果码
            } else {
                // 普通请求:跳转到登录页(结果码为"login",需在struts.xml中配置)
                return "login";
            }
        }
    }
}

代码说明:

  • ActionInvocation:拦截器链的上下文,可获取Action、请求、Session等。
  • session.getAttribute("user"):检查Session中是否存在用户信息(登录后需存入"user")。
  • 排除逻辑:通过actionName和methodName判断是否为登录Action的login方法,避免拦截自身导致循环跳转。
  • Ajax处理:检查请求头X-Requested-With,若为Ajax则返回JSON提示,否则跳转登录页。

Action类与业务逻辑#

定义一个处理登录和首页的Action:

package com.example.action;
 
import com.opensymphony.xwork2.ActionSupport;
import com.opensymphony.xwork2.ActionContext;
 
public class UserAction extends ActionSupport {
    private String username;
    private String password;
 
    // Getter/Setter
    public String getUsername() { return username; }
    public void setUsername(String username) { this.username = username; }
    public String getPassword() { return password; }
    public void setPassword(String password) { this.password = password; }
 
    /**
     * 登录方法:验证用户名密码,成功则存入Session
     */
    public String login() {
        // 模拟简单登录验证(实际应调用Service层)
        if ("admin".equals(username) && "123456".equals(password)) {
            ActionContext context = ActionContext.getContext();
            context.getSession().put("user", username); // 登录成功,存入Session
            return SUCCESS;
        } else {
            this.addActionError("用户名或密码错误"); // 错误信息,用于页面展示
            return INPUT;
        }
    }
 
    /**
     * 首页方法:需要登录后访问
     */
    public String home() {
        // 业务逻辑(如加载用户信息、渲染首页)
        return SUCCESS;
    }
}

Struts配置文件#

在struts.xml中配置拦截器、拦截器栈和Action:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE struts PUBLIC
        "-//Apache Software Foundation//DTD Struts Configuration 2.5//EN"
        "http://struts.apache.org/dtds/struts-2.5.dtd">
<struts>
    <constant name="struts.devMode" value="true"/> <!-- 开发模式,便于调试 -->
 
    <package name="default" namespace="/" extends="struts-default">
        <!-- 1. 定义自定义拦截器 -->
        <interceptors>
            <!-- 注册登录拦截器 -->
            <interceptor name="loginInterceptor" class="com.example.interceptor.LoginInterceptor"/>
            
            <!-- 2. 定义拦截器栈(组合默认拦截器和自定义拦截器) -->
            <interceptor-stack name="myStack">
                <!-- 包含Struts默认拦截器(处理参数、国际化等) -->
                <interceptor-ref name="defaultStack"/>
                <!-- 引用自定义登录拦截器 -->
                <interceptor-ref name="loginInterceptor"/>
            </interceptor-stack>
        </interceptors>
 
        <!-- 3. 全局结果集:未登录时跳转到login.jsp -->
        <global-results>
            <result name="login">/login.jsp</result>
        </global-results>
 
        <!-- 4. 登录Action:不使用登录拦截器(避免循环拦截) -->
        <action name="login" class="com.example.action.UserAction" method="login">
            <result name="success">/home.jsp</result>
            <result name="input">/login.jsp</result>
            <!-- 仅使用默认拦截器(处理参数、错误信息等) -->
            <interceptor-ref name="defaultStack"/>
        </action>
 
        <!-- 5. 首页Action:使用自定义拦截器栈 -->
        <action name="home" class="com.example.action.UserAction" method="home">
            <interceptor-ref name="myStack"/> <!-- 应用包含登录拦截的栈 -->
            <result name="success">/home.jsp</result>
        </action>
    </package>
</struts>

配置说明:

  • defaultStack:Struts2的默认拦截器栈,包含参数处理、国际化、异常处理等核心拦截器。
  • myStack:自定义拦截器栈,组合了默认拦截器和登录拦截器,确保既有基础功能又有登录验证。
  • 登录Action(login):显式指定defaultStack,避免被loginInterceptor拦截(否则会循环跳转到登录页)。
  • 首页Action(home):使用myStack,强制进行登录检查。

常见问题与最佳实践#

排除登录Action的拦截#

如果需要更灵活地排除某些Action或方法,可在拦截器中添加排除逻辑:

方法1:根据Action类名排除#

// 检查当前Action是否为登录Action
if (invocation.getAction() instanceof LoginAction) {
    return invocation.invoke();
}

方法2:根据方法名排除(需拦截器支持excludeMethods参数)#

private String excludeMethods; // 注入excludeMethods参数
 
public void setExcludeMethods(String excludeMethods) {
    this.excludeMethods = excludeMethods;
}
 
@Override
public String intercept(ActionInvocation invocation) throws Exception {
    String method = invocation.getProxy().getMethod();
    // 检查方法是否在排除列表中
    if (excludeMethods != null && excludeMethods.contains(method)) {
        return invocation.invoke();
    }
    // 其他登录检查逻辑...
}

在struts.xml中为拦截器指定排除方法:

<interceptor name="loginInterceptor" class="com.example.interceptor.LoginInterceptor">
    <param name="excludeMethods">login,register</param> <!-- 排除login和register方法 -->
</interceptor>

处理Ajax请求的未登录场景#

如果系统包含Ajax请求,直接跳转登录页会导致前端报错(Ajax期望JSON/XML响应,而非HTML)。因此需在拦截器中区分请求类型:

String ajaxHeader = request.getHeader("X-Requested-With");
if ("XMLHttpRequest".equals(ajaxHeader)) {
    // Ajax请求:返回JSON格式的错误信息
    response.setContentType("application/json;charset=utf-8");
    response.getWriter().write("{\"code\":401,\"msg\":\"请登录后操作\"}");
    return null; // 终止执行,不返回结果码
} else {
    // 普通请求:跳转登录页
    return "login";
}

拦截器栈的使用#

实际项目中,通常需要组合多个拦截器(如登录拦截+权限拦截+日志拦截),此时可通过拦截器栈统一管理:

<interceptor-stack name="bizStack">
    <interceptor-ref name="defaultStack"/>
    <interceptor-ref name="loginInterceptor"/> <!-- 登录拦截 -->
    <interceptor-ref name="permissionInterceptor"/> <!-- 权限拦截 -->
    <interceptor-ref name="logInterceptor"/> <!-- 日志拦截 -->
</interceptor-stack>

拦截器的顺序会影响执行逻辑:loginInterceptor先执行(检查登录),再执行permissionInterceptor(检查权限),最后执行logInterceptor(记录日志)。

错误处理与日志记录#

在拦截器中加入异常捕获和日志记录,提升系统稳定性:

@Override
public String intercept(ActionInvocation invocation) throws Exception {
    try {
        // 登录检查逻辑...
        return invocation.invoke();
    } catch (Exception e) {
        // 记录异常日志
        org.apache.log4j.Logger logger = org.apache.log4j.Logger.getLogger(LoginInterceptor.class);
        logger.error("登录拦截器执行异常:" + e.getMessage(), e);
        // 抛出异常或返回错误页面
        throw e; // 或返回"error"结果
    }
}

总结#

自定义登录检查拦截器是Struts2权限控制的核心手段,通过以下步骤可快速实现:

  1. 继承AbstractInterceptor,重写intercept()方法,实现Session用户检查。
  2. 在struts.xml中定义拦截器、拦截器栈,为Action指定拦截器引用。
  3. 结合最佳实践(如排除登录Action、处理Ajax请求、拦截器栈组合),提升系统安全性和用户体验。

通过拦截器的灵活配置,可在不侵入Action代码的前提下,统一管控所有请求的登录状态,是企业级Web应用权限控制的最佳实践之一。

参考资料#

  1. Struts2官方文档:https://struts.apache.org/docs/
  2. 《Struts2权威指南》(第二版)
  3. 拦截器与过滤器的区别:https://www.runoob.com/struts_2/struts-2-interceptors.html
  4. Ajax请求处理:https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/X-Requested-With