本文是《轻量级 Java Web 框架架构设计》的系列博文。
今天想和大家简单的分享一下,在 Smart 中是如何做到访问安全控制的。也就是说,当没有登录或 Session 过期时所做的操作,会自动退回到首页(例如:登录页面),以防止用户进行非法操作。
这件事情或许是每个具备安全性考虑的系统都需要的功能,我们可以分两种情况来处理用户的请求:
下面是具体的实现过程,您别忘了系好安全带,我们这就出发了!
第一步:在 Smart Framework 中定义一个认证异常类 AuthException
public class AuthException extends RuntimeException { public AuthException() { super(); } public AuthException(String message) { super(message); } public AuthException(String message, Throwable cause) { super(message, cause); } public AuthException(Throwable cause) { super(cause); } }
没啥内容,就一个普通的 RuntimeException 而已,其实就是想自定义一种异常类型,以区别于其他的异常,方便在框架中进行处理。
第二步:在 Smart Sample 中定义一个 AuthAspect,用于拦截 Action 的所有请求
@Bean @Aspect(pkg = "com.smart.sample.action") @Order(0) public class AuthAspect extends BaseAspect { @Override public boolean filter(Class<?> cls, Method method, Object[] params) { String className = cls.getSimpleName(); String methodName = method.getName(); return !( className.equals("UserAction") && (methodName.equals("login") || methodName.equals("logout") ) ); } @Override public void before(Class<?> cls, Method method, Object[] params) throws Exception { User user = DataContext.Session.get("user"); if (user == null) { throw new AuthException(); } } }
以上代码中需注意一下几点:
这样 AOP 框架(也就是 BaseAspect 类及其相关 Proxy Chain 等)就可以处理这个自定义异常了。
还记得 Order 注解吗?它曾经在单元测试中出现过,现在还可以用于定义 AOP 顺序。
友情提示:
第三步:在 BaseAspect 中将异常继续往上抛,抛给它的调用者
public abstract class BaseAspect implements Proxy { @Override public final void doProxy(ProxyChain proxyChain) throws Exception { ... begin(); try { if (filter(cls, method, params)) { before(cls, method, params); Object result = proxyChain.doProxyChain(); after(cls, method, params, result); } else { proxyChain.doProxyChain(); } } catch (Exception e) { error(cls, method, params, e); throw e; // 将异常继续往上抛,抛给它的调用者 } finally { end(); } } ... }
那么 Aspect 的调用者是谁呢?也就是说,谁用 Aspect 呢?至少 Action 是一个关键性用户。
那么 Action 又是谁来调用呢?当然就是 DispatchServlet 了。
于是,顺腾摸瓜,找到了调用的起源。
第四步:修改 DispatchServlet,处理 AuthException
@WebServlet("/*") public class DispatcherServlet extends HttpServlet { ... private void handleActionMethod(HttpServletRequest request, HttpServletResponse response, ActionBean actionBean, List<Object> paramList) { // 从 ActionBean 中获取 Action 相关属性 Class<?> actionClass = actionBean.getActionClass(); Method actionMethod = actionBean.getActionMethod(); // 从 BeanHelper 中创建 Action 实例 Object actionInstance = BeanHelper.getInstance().getBean(actionClass); // 调用 Action 方法 Object actionMethodResult; try { actionMethod.setAccessible(true); // 取消类型安全检测(可提高反射性能) actionMethodResult = actionMethod.invoke(actionInstance, paramList.toArray()); } catch (Exception e) { // 处理 Action 方法异常【★】 handleActionMethodException(request, response, e); // 直接返回 return; } // 处理 Action 方法返回值 handleActionMethodReturn(request, response, actionMethodResult); } private void handleActionMethodException(HttpServletRequest request, HttpServletResponse response, Exception e) { if (e.getCause() instanceof AuthException) { // 若为认证异常,则分两种情况进行处理 if (WebUtil.isAJAX(request)) { // 若为 AJAX 请求,则发送 403 错误 WebUtil.sendError(403, response); } else { // 否则重定向到首页 WebUtil.redirectRequest(request.getContextPath() + "/", response); } } else { // 若为其他异常,则记录错误日志 logger.error("调用 Action 方法出错!", e); } } ... }
大家可以看到以上代码的【★】处,也就是调用 Action 方法时的异常处理代码,请见下面的 handleActionMethodException 方法。逻辑如下:
以上代码中涉及到了几个关键的 WebUtil 方法,其实这些都是对 Servlet API 的一个简单的封装。代码片段如下:
public class WebUtil { ... // 重定向请求 public static void redirectRequest(String path, HttpServletResponse response) { try { response.sendRedirect(path); } catch (Exception e) { logger.error("重定向请求出错!", e); throw new RuntimeException(e); } } // 发送错误代码 public static void sendError(int code, HttpServletResponse response) { try { response.sendError(code); } catch (Exception e) { logger.error("发送错误代码出错!", e); throw new RuntimeException(e); } } // 判断是否为 AJAX 请求 public static boolean isAJAX(HttpServletRequest request) { return request.getHeader("X-Requested-With") != null; } }
以上可以看到,普通请求非常简单,直接 redirect 就行了。而对于 AJAX 请求回调问题,我们可使用 jQuery 来轻松实现。
第五步:使用 jQuery 来处理 AJAX 非法访问
/* 全局变量 */ var BASE = '/smart-sample'; // 应用 Context 名称(若为空字符串表示应用以 ROOT 来发布) ... $(function() { $.ajaxSetup({ cache: false, error: function(jqXHR, textStatus, errorThrown) { switch (jqXHR.status) { case 403: document.write(''); location.href = BASE + '/'; break; case 503: alert(errorThrown); break; } } }); ... }
首先,定义一个全局变量 BASE,代表应用的 Context 名称,用户可自行修改。
然后,进行 AJAX 全局设置(使用了 jQuery 的 $.ajaxSetup 方法),在 error 回调函数中处理 403 错误,故意清空页面内容,并返回到应用首页(效果与普通请求非法访问时相同)。
以上就是 Smart Framework 关于安全性控制的解决方案,能否使用更简单更高效的方式来实现?等候您的评论。