六、 拦截器示例 : 实现权限控制
权限检查,当浏览者需要请求执行某个操作时,应用首先需要检查浏览者是否登录,以及是否有足够的权限来执行该操作
6.1 实现拦截器
在这里会实现所有例子,后面章节在一一解释
本示例应用要求用户登录,且必须为指定用户名才可以查看系统中某个视图资源: 否则,系统直接转入登录页面。
对于上述的需求,可以在每个 Action 的执行实际处理逻辑之前,先执行权限检查逻辑,为了代码复用,可以使用拦截器。
个人认为判断 session 用 过滤器比较好 如下:
web.xml
<filter> <filter-name>SessionInvalidate</filter-name> <filter-class>com.sysoft.baselib.web.SessionCheckFilter</filter-class> <init-param> <param-name>checkSessionKey</param-name> <param-value>APP_SESSION_TOKEN</param-value> </init-param> <init-param> <param-name>redirectURL</param-name> <param-value>/sessionInvalidate.jsp</param-value> </init-param> <init-param> <param-name>notCheckURLList</param-name> <param-value>/login.jsp,/logon.do,/logout.jsp,/Index2/index.jsp,/sessionInvalidate.jsp,/Index2/maintop.jsp,/html.jsp</param-value> </init-param> </filter> <filter-mapping> <filter-name>SessionInvalidate</filter-name> <url-pattern>*.do</url-pattern> </filter-mapping> <filter-mapping> <filter-name>SessionInvalidate</filter-name> <url-pattern>*.jsp</url-pattern> </filter-mapping>
SessionCheckFilter.java
package com.sysoft.baselib.web;
import java.io.IOException;
import java.util.HashSet;
import java.util.Set;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
/**
* 用于检测用户是否登陆的过滤器,如果未登录,则重定向到指的登录页面
* 配置参数
* checkSessionKey 需检查的在 Session 中保存的关键字
* redirectURL 如果用户未登录,则重定向到指定的页面,URL不包括 ContextPath
* notCheckURLList 不做检查的URL列表,以分号分开,并且 URL 中不包括 ContextPath
*/
public class SessionCheckFilter implements Filter {
protected FilterConfig filterConfig = null;
private String redirectURL = null;
private Set notCheckURLList = new HashSet();
private String sessionKey = null;
public void doFilter(ServletRequest servletRequest,
ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
HttpSession session = request.getSession();
if (sessionKey == null) {
filterChain.doFilter(request, response);
return;
}
if ((!checkRequestURIIntNotFilterList(request))
&& session.getAttribute(sessionKey) == null) {
response.sendRedirect(request.getContextPath() +redirectURL);
return;
}
filterChain.doFilter(servletRequest, servletResponse);
}
public void destroy() {
notCheckURLList.clear();
}
private boolean checkRequestURIIntNotFilterList(HttpServletRequest request) {
String uri = request.getServletPath()
+ (request.getPathInfo() == null ? "" : request.getPathInfo());
String temp = request.getRequestURI();
temp= temp.substring(request.getContextPath().length()+1);
//System.out.println("是否包括:"+uri+";"+notCheckURLList+"=="+notCheckURLList.contains(uri));
return notCheckURLList.contains(uri);
}
public void init(FilterConfig filterConfig) throws ServletException {
this.filterConfig = filterConfig;
redirectURL = filterConfig.getInitParameter("redirectURL");
sessionKey = filterConfig.getInitParameter("checkSessionKey");
String notCheckURLListStr = filterConfig
.getInitParameter("notCheckURLList");
if(notCheckURLListStr != null){
System.out.println(notCheckURLListStr);
String[] params = notCheckURLListStr.split(",");
for(int i=0;i<params.length;i++){
notCheckURLList.add(params[i].trim());
}
}
}
}
检查用户是否登录,通常都是通过跟踪用户的 HTTPSession 来完成的,通过 ActionContext 即可访问到 Session 中的属性,拦截器的 intercepte(ActionInvocation invocation) 的 invocation 参数可以访问到请求相关的 ActionContext 实例
权限查看拦截器的例子 :
AuthorityInterceptor.java
package js.authority;
import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.Action;
import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.interceptor.AbstractInterceptor;
import java.util.*;
public class AuthorityInterceptor extends AbstractInterceptor {
public String intercept(ActionInvocation invocation) throws Exception {
ActionContext ctx = invocation.getInvocationContext();
Map session = ctx.getSession();
String user = (String) session.get("user");
if (user != null && user.equals("crazyit")) {
return invocation.invoke();
}
ctx.put("tip", "您还没有登录,请输入crazyit,leegang登录系统");
//直接返回 login 的逻辑视图
return Action.LOGIN;
}
}
通过 ActionInvocation 参数取得 session 实例,然后取出 user ,来判断用户是否登录,从而判断是否需要转入登录页面
LoginAction.java
package js.authority;
import com.opensymphony.xwork2.ActionSupport;
import com.opensymphony.xwork2.ActionContext;
import java.util.*;
public class LoginAction extends ActionSupport {
private String username;
private String password;
public void setUsername(String username) {
this.username = username;
}
public String getUsername() {
return username;
}
public void setPassword(String password) {
this.password = password;
}
public String getPassword() {
return password;
}
public String execute() throws Exception {
Thread.sleep(1500);
if (getUsername().equals("crazyit") && getPassword().equals("leegang")) {
ActionContext ctx = ActionContext.getContext();
Map session = ctx.getSession();
session.put("user", getUsername());
return SUCCESS;
} else {
return ERROR;
}
}
}
struts.xml
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE struts PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN" "http://struts.apache.org/dtds/struts-2.2.dtd"> <struts> <constant name="struts.custom.i18n.resources" value="messageResource"/> <constant name="struts.i18n.encoding" value="GBK"/> <package name="js.authority" extends="struts-default" namespace="/authority"> <!-- 用户拦截器定义在该元素下 --> <interceptors> <!-- 定义了一个名为authority的拦截器 --> <interceptor name="authority" class="js.authority.AuthorityInterceptor"/> </interceptors> <!-- 定义全局Result --> <global-results> <!-- 当返回login视图名时,转入/login.jsp页面 --> <result name="login">/authority/login.jsp</result> </global-results> <action name="login" class="js.authority.LoginAction"> <result name="error">/authority/error.jsp</result> <result name="success">/authority/welcome.jsp</result> </action> <!-- 定义一个名为viewBook的Action,其实现类为ActionSupport --> <action name="viewBook"> <!-- 返回success视图名时,转入/WEB-INF/jsp/viewBook.jsp页面 --> <result>/WEB-INF/jsp/viewBook.jsp</result> <!-- 拦截器一般配置在result元素之后! --> <interceptor-ref name="defaultStack"/> <!-- 应用自定义拦截器 --> <interceptor-ref name="authority"/> </action> <action name=""> <result>.</result> </action> </package> </struts>
/authority/login.jsp
<%@ page contentType="text/html; charset=GBK" language="java" errorPage="" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<%@taglib prefix="s" uri="/struts-tags"%>
<html>
<head>
<title>登录页面</title>
</head>
<body>
<div style="color:red" align="center">${requestScope.tip}</div>
<form action="login.action" method="post">
<table align="center">
<caption><h3>用户登录</h3></caption>
<tr>
<td>用户名:<input type="text" name="username"/></td>
</tr>
<tr>
<td>密码:<input type="text" name="password"/></td>
</tr>
<tr align="center">
<td><input type="submit" value="登录"/>
<input type="reset" value="重填" /></td>
</tr>
</table>
</form>
<div align="center"><a href="viewBook.action">
查看作者李刚出版的图书</a>
</div>
</body>
</html>
/authority/welcome.jsp
<%@ page contentType="text/html; charset=GBK" language="java" errorPage="" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>成功页面</title>
</head>
<body>
您已经登录!<br />
<a href="viewBook.action">查看作者李刚出版的图书</a>
</body>
</html>
/authority/error.jsp
<%@ page contentType="text/html; charset=GBK" language="java" errorPage="" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>错误页面</title>
</head>
<body>
您不能登录!<br />
<a href="viewBook.action">查看作者李刚出版的图书</a>
</body>
</html>
/WEB-INF/jsp/viewBook.jsp
<%@ page contentType="text/html; charset=GBK" language="java" errorPage="" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>作者李刚已经出版的图书:</title>
</head>
<body>
<h3>作者李刚已经出版的图书:</h3>
疯狂Java讲义<br />
轻量级Java EE企业实战<br />
疯狂Ajax讲义<br />
</body>
</html>
<s:a href="" οnclick="newWin('authority/login.jsp');" cssStyle="cursor: hand;">authority/login.jsp</s:a>
6.2 配置权限控制拦截器
以下代码全部基于 6.1
6.1 中 struts.xml 中的 viewBook 的 Action , 没有指定 class 属性,默认使用 ActionSupport 类,配置该 Action 时,只指定了一个结果映射,指定系统返回 success 字符串时,系统将转入 /WEB-INF/jsp/viewBook.jsp ,但未配置 login 视图名对应的 jsp
考虑到这个拦截器的重复使用,可能多个 Action 都需要跳转到 login 逻辑视图,故将 login 的结果映射定义成一个全局结果映射
<!-- 定义全局Result --> <global-results> <!-- 当返回login视图名时,转入/login.jsp页面 --> <result name="login">/authority/login.jsp</result> </global-results>
为了简化 struts.xml 文件配置,避免在每个 Action 中重复配置该拦截器,可以将该拦截器配置成一个默认拦截器栈(这个默认拦截器栈应该包括 default-stack 拦截器栈和权限检查拦截器)
<!-- 用户拦截器定义在该元素下 --> <interceptors> <!-- 定义了一个名为authority的拦截器 --> <interceptor name="authority" class="js.authority.AuthorityInterceptor"/> <interceptor-stack name="mydefault"> <interceptor-ref name="defaultStack"/> <interceptor-ref name="authority"/> </interceptor-stack> </interceptors> <default-interceptor-ref name="mydefault"/>
不过 6.1 的例子 不能使用默认拦截器,因为 login 的 Action 不能被拦截,否则将永远不能登录
解决方法: 一旦在某个包下定义了上面的默认拦截器栈,在该包下的所有 Action 都会自动增加权限检查功能,对于那些不需要使用权限控制的 Action ,将它们定义在另一个包中,这个新的包中依然使用 Struts 2 原有的默认拦截器栈,将不会有权限控制功能。