访问安全控制解决方案

本文是《轻量级 Java Web 框架架构设计》的系列博文。 前端

今天想和你们简单的分享一下,在 Smart 中是如何作到访问安全控制的。也就是说,当没有登陆或 Session 过时时所作的操做,会自动退回到首页(例如:登陆页面),以防止用户进行非法操做。 java

这件事情或许是每一个具有安全性考虑的系统都须要的功能,咱们能够分两种状况来处理用户的请求: ajax

  1. 普通请求
  2. AJAX 请求

下面是具体的实现过程,您别忘了系好安全带,咱们这就出发了! 安全

第一步:在 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 的全部请求 ide

@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();
        }
    }
}

以上代码中需注意一下几点: 函数

  1. 拦截到 action 包,即拦截全部的 Action 类。
  2. 使用 Order 注解定义 AOP 拦截顺序,为 0 表示最早拦截。【新特性】
  3. 在 filter 方法中排除掉几个特殊的 Action 方法,例如:login 与 logout,由于它们是无需进行安全控制的。
  4. 在 before 方法中从 Session 中获取 User 数据(已在 login 时放入到 Session),若为空,说明 Session 已过时,则主动抛出 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 方法。逻辑以下:

  1. 首先判断该异常是否为 AuthException,若是是,则须要分两种状况来考虑。
  2. 若为 AJAX 请求,则向 Response 中发送 403 错误代码。后面的事情就交给前端的 AJAX 回调函数来作吧,请见下文。
  3. 不然(即为普通请求),重定向到首页,即重定向发送“/”请求。
  4. 若不是 AuthException,则输出错误日志。

以上代码中涉及到了几个关键的 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 关于安全性控制的解决方案,可否使用更简单更高效的方式来实现?等候您的评论。

相关文章
相关标签/搜索