编写Web应用程序,通常都会采用分层结构,界面层+业务逻辑层+数据库层,这时候在界面层,会碰到安全问题,即一些资源(Servlet, strutsAction)允许特定的角色调用,如提供一个AddUserction,这个Action只允许"admin"角色的人调用这个 Actoin。那么,通常代码是这么写的:
public ActionForward execute(ActionMapping mapping,
ActionForm form,
HttpServletRequest request,
HttpServletResponse response) throws Exception
{
UserForm user = (UserForm) form;
UserBean userBean = BeanFactory.getUserBean();
if(WFSecurity.check(request,"admin","superadmin")==null) return ;
userBean.add(user.getUserName(), user.getPassword(), user.getRole());
.....
}
Security.check的逻辑很简单,从HttpSession 取出User,判断User的角色是否为"admin",或者"supperadmin",如果是,则返回一个User对象,如果不是,则重定向到登陆页面,并返回null.代码如下:
public class WFSecurity {
public static User check(HttpServletRequest request,
HttpServletResponse response, String... expectedRoles) throws ServletException, IOException {
User user = (User) request.getSession().getAttribute("USER");
if (user == null) {
gotoLoginPage(request, response);
return null;
} else {
String role = user.getRole();
for (String r : expectedRoles) {
if (role.equals(r)) {
return user;
}
}
gotoLoginPage(request, response);
return null;
}
}
public static void gotoLoginPage(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
request.getRequestDispatcher("/login.jsp").forward(request, response);
}
}
不过,既然本文标题是《自己动手写代码,为web服务器增加更多的功能》,所以,在这要用些更有意思的方式来实现这个Web应用的很常见的需求,我们的目标是只需要用Annotation申明一下我们的Serlvet或者Acttion的某个方法所期望的角色,从而使得我们的servlet, action专注于调用业务逻辑。如下
//(注意:RolesAllowed是Java EE定义好的Annotation,在这用它主要是为了重用,你也可以定义自己的Annotation)
@RolesAllowed({"admin","superadmin"})
public ActionForward execute(ActionMapping mapping,
ActionForm form,
HttpServletRequest request,
HttpServletResponse response) throws Exception
{
UserForm user = (UserForm) form;
UserBean userBean = BeanFactory.getUserBean();
userBean.addUser(user.getUserName(), user.getPassword(), user.getRole());
.......
}
或者如果Action继承的是DispatchAction,你也可以为每个方法加上特定的Annotation来申明安全,如
//没有用RolesAllowed,说明允许任何人调用
public ActionForward query(ActionMapping mapping,
ActionForm form,
HttpServletRequest request,
HttpServletResponse response) throws Exception
@RolesAllowed({"admin","superadmin"})
public ActionForward query(ActionMapping mapping,
ActionForm form,
HttpServletRequest request,
HttpServletResponse response) throws Exception
在这我们就用Struts1.3作为例子,来说明如何实现这个功能,需要具备的知识有 了解Java反射,了解Annotation,以及了解 Struts如何处理请求并派发到Action中,当然,如果你不了解这些,你也可以通过本文学习这些知识,这就是为初学者准备的。
主要思路是在Struts 调用具体Action前(也可以适用于其他Web框架或者无框架编程),找到此Action申明的RolesAllowed, 然后取出RolesAllowed所允许的角色,最后将该角色列表于Sesssion 里面用户角色作比较,如果匹配,允许调用Action,如果不匹配,则告之用户访问了受权限保护的资源。
第一步:创建一个新类MyStrutsProcessor 继承 Struts 默认的RequestProcessor,并在Struts-config.xml里申明用此processor处理所有的请求
<controller processorClass="com.jsofa.common.MyStrutsProcessor"/>
第二步,需要重载RequestProcessor的processActionPerform方法,该方法默认情况下会调用Action.execute()方法,如前所述,我们需要进行角色判断
代码如下
protected ActionForward processActionPerform(HttpServletRequest request,
HttpServletResponse response,
Action action,
ActionForm form,
ActionMapping mapping)
throws IOException, ServletException {
//
User user = (User) request.getSession().getAttribute("USER");
//以下俩行代码主要是为本文第二部分《增加业务逻辑层功能》所加的代码
SessionContext ctx = SessionContextFactory.getSessionContext();
ctx.setUser(user);
//通过反射取出Action对应的角色列表
List<String> expectedRolesList = this.getExpectedRole(action);
if (expectedRolesList != null) {
if (user == null) {
//该Action受保护
return mapping.findForward("SECURITY_ISSUE");
}
String role = user.getRole();
if (!expectedRolesList.contains(role)) {
//该Action受保护
return mapping.findForward("SECURITY_ISSUE");
}
return super.processActionPerform(request, response, action, form, mapping);
} else {
// 如果列表为空,则表示允许所用调用
return super.processActionPerform(request, response, action, form, mapping);
}
}
getExpectedRole方法是通过反射取出Action所申明的角色列表
private List<String> getExpectedRole(Action action) {
.......
//找到execute方法
Method executeMethod = action.getClass().getMethod("execute", new Class[]{ActionMapping.class, ActionForm.class, HttpServletRequest.class, HttpServletResponse.class});
//找到该方法定义的RolesAllowed
RolesAllowed anno = (RolesAllowed) executeMethod.getAnnotation(RolesAllowed.class);
if (anno == null) {
// 没有设置角色列表,允许任何请求调用
return null;
} else {
String[] rolesList = anno.value();
// as matter of fact ,roles list can be cached (but it's hard to support hot deploy)
return Arrays.asList(rolesList);
}
........
}
如果对Java EE规范了解的人,就会发现,其实WebContainer支持通过RolesAllowed 来申明允许调用Serlvet的角色,但这并不是很灵活,所以我们才会提供上诉代码来增强Web服务器的灵活性,以上代码只是提供一个列子来说明我们程序员也可以实现并扩展Java EE规范。在下一章里,我们同样遵循Java EE规范,看看在没有EJB Container或者Spring或者其他框架的情况下,如何封装我们的业务逻辑(Jave EE推荐放在EJB的会话Bean中),以及如何优雅的处理事务,会话等常见业逻辑中碰到的问题