【Struts2】自定义登录检查拦截器
在Web应用开发中,登录状态检查是权限控制的基础环节。Struts2的拦截器(Interceptor)机制提供了一种灵活的方式,可在Action执行前后插入自定义逻辑(如登录验证、日志记录、参数预处理等)。通过自定义登录检查拦截器,我们可以统一拦截未登录用户的非法访问,保障系统安全性,同时避免在每个Action中重复编写登录验证代码。
本文将详细讲解Struts2自定义登录拦截器的实现步骤、配置方式,并结合最佳实践和常见问题分析,帮助读者快速掌握这一技术。
目录#
- Struts2拦截器基础
- 自定义登录检查拦截器的步骤
- 示例:登录拦截器的实现与配置
- 拦截器类编写
- Action类与业务逻辑
- Struts配置文件
- 常见问题与最佳实践
- 排除登录Action的拦截
- 处理Ajax请求的未登录场景
- 拦截器栈的使用
- 错误处理与日志记录
- 总结
- 参考资料
Struts2拦截器基础#
拦截器的概念#
Struts2的拦截器是AOP(面向切面编程)思想的实践,它允许在Action执行的生命周期中插入自定义逻辑(如登录验证、参数过滤、日志记录等)。拦截器围绕ActionInvocation(Action执行上下文)形成一个拦截器链,每个拦截器依次执行intercept()方法,最终调用目标Action。
工作原理#
- 当请求到达Struts2的核心控制器
Dispatcher时,会根据配置找到对应的Action,并构建拦截器链(Interceptor Stack)。 - 拦截器链中的每个拦截器依次执行
intercept()方法,通过invocation.invoke()调用下一个拦截器或目标Action。 - Action执行完成后,拦截器链会反向执行(类似递归回溯),允许在Action执行后再处理逻辑(如结果渲染、日志记录)。
与过滤器(Filter)的区别#
| 特性 | 拦截器(Interceptor) | 过滤器(Filter) |
|---|---|---|
| 技术规范 | Struts2框架特有 | Servlet规范 |
| 处理范围 | 仅处理Action请求 | 可处理所有请求(含静态资源) |
| 底层实现 | 基于Java反射(针对Action) | 基于Servlet API |
| 执行时机 | Action执行前后 | 请求进入容器后、响应输出前 |
| 灵活性 | 可访问Action上下文、方法 | 仅能访问请求/响应对象 |
自定义登录检查拦截器的步骤#
要实现一个自定义的登录检查拦截器,需遵循以下步骤:
- 创建拦截器类:继承
AbstractInterceptor或实现Interceptor接口,重写intercept()方法。 - 实现登录检查逻辑:在
intercept()中检查Session是否存在用户信息,决定是继续执行Action还是跳转到登录页。 - 配置拦截器:在
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权限控制的核心手段,通过以下步骤可快速实现:
- 继承
AbstractInterceptor,重写intercept()方法,实现Session用户检查。 - 在
struts.xml中定义拦截器、拦截器栈,为Action指定拦截器引用。 - 结合最佳实践(如排除登录Action、处理Ajax请求、拦截器栈组合),提升系统安全性和用户体验。
通过拦截器的灵活配置,可在不侵入Action代码的前提下,统一管控所有请求的登录状态,是企业级Web应用权限控制的最佳实践之一。
参考资料#
- Struts2官方文档:https://struts.apache.org/docs/
- 《Struts2权威指南》(第二版)
- 拦截器与过滤器的区别:https://www.runoob.com/struts_2/struts-2-interceptors.html
- Ajax请求处理:https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/X-Requested-With