使用模板方法模式来构建权限过滤器

原创
2017/03/30 00:18
阅读数 669

场景分析

在web应用中,我们的系统几乎全部都会涉及到权限的处理.如何设计权限,控制权限是我们在web开发中不可忽略的问题.虽然开源世界中有很多像 spring security,shiro等优秀的安全处理框架,但其在我们相对简单的需求里未免显得过于重量级.而这些框架内部的实现上,还是基于我们 servlet 的过滤器 (filter),故而我们应当尽量避免使用这些相对重量的框架,仅仅通过我们的java来实现我们权限控制的需求. 众所周知,之所以spring能成为java业界的标准是因为原生的java在某些工作上显得太过繁琐,特别是在web开发方面,如果使用标准的servlet,那一定是想折磨程序员.在 servlet 3.0时代这种情况是否能够改变一些?我想在某种程度上时可以的.servlet 3.0 本身支持全注解的方式来免配置web应用,当其与优美的设计模式结合到一起的时候,会擦出火花,成就完美且且高效的代码.

我们先简单的介绍一下代码中会用到的设计模式:

责任链模式(Chain of Responsibility)

责任链模式,有多个具有相同接口的对象,每个对象持有对下一个对象的引用,这样就会形成一条链,请求在这条链上传递,直到某一对象决定处理该请求。但是发出者并不清楚到底最终那个对象会处理该请求,所以,责任链模式可以实现,在隐瞒客户端的情况下,对系统进行动态的调整。

在本例中用UML图表示如下所示:

责任链模式

在我们的 servlet 中过滤器的机制恰好使用的是责任链模式,http请求在服务器的过滤器链上进行传播.拦截器主要作用是在HttpServletRequest 到达Servlet 之前,拦截客户的HttpServletRequest .根据需要检查HttpServletRequest ,也可以修改HttpServletRequest 头和数据.在HttpServletResponse 到达客户端之前,拦截HttpServletResponse.根据需要检查HttpServletResponse ,可以修改HttpServletResponse 头和数据。 其执行逻辑简要描述如下:

责任链模式

适配器模式 (Adapter)

适配器模式将某个类的接口转换成客户端期望的另一个接口表示,目的是消除由于接口不匹配所造成的类的兼容性问题。

用UML图表示如下所示:

适配器模式

通过上面我们知道,要想写一个过滤器,必须实现 Filter 接口

       public interface Filter {
       
           public void init(FilterConfig filterConfig) throws ServletException;
        
        
           public void doFilter(ServletRequest request, ServletResponse response,
                                FilterChain chain)
                   throws IOException, ServletException;
       
           public void destroy();
       }

而在我们的过滤器中,明显只需要一个doFilter()方法,故而我们需要在我们的拦截器实现类与Filter接口之间创建一个适配器,来更加明确我们的实现类的作用

模板方法模式(Template Method)

一个抽象类中,有一个主方法,再定义1...n个方法,可以是抽象的,也可以是实际的方法,定义一个类,继承该抽象类,重写抽象方法,通过调用抽象类,实现对子类的调用.

用UML图表示如下所示:

模板方法模式

代码实现

结合使用三种设计模式,在servlet 3.0的环境下我们就可以轻易的打造一个高可用的,灵活的权限管理系统.部分展示代码如下.

  1. BaseFilterAdapter.java 核心实现类

      package com.br.antifroud.filter;
    
      import com.alibaba.fastjson.JSONObject;
      import com.br.antifroud.common.http.HttpResultFactory;
    
      import javax.servlet.*;
      import javax.servlet.http.HttpServletRequest;
      import javax.servlet.http.HttpServletResponse;
      import java.io.IOException;
      import java.io.PrintWriter;
      import java.util.HashSet;
      import java.util.Map;
      import java.util.Set;
    
      /**
       * [@author](https://my.oschina.net/arthor) Wang Weiwei <weiwei02@vip.qq.com / weiwei.wang@100credit.com>
       * [@version](https://my.oschina.net/u/931210) 1.0
       * [@sine](https://my.oschina.net/mysine) 17-3-23
       */
      public abstract class BaseFilterAdapter implements Filter {
          protected Set<String> ignorePath;
          public void sendToClient(HttpServletResponse resp, String s) throws IOException {
              HttpServletResponse response = resp;
              response.setCharacterEncoding("UTF-8");
              PrintWriter printWriter = response.getWriter();
              response.setContentType("text/json");
              printWriter.print(s);
              printWriter.flush();
          }
    
          @Override
          public void init(FilterConfig filterConfig) throws ServletException {
              ignorePath = new HashSet<>();
              String[] urls = filterConfig.getInitParameter("ignoreLoginPath").split(",");
              for (int i = 0; i < urls.length; i++) {
                  ignorePath.add(urls[i].trim());
              }
          }
    
          /**
           * 进行过滤的主方法,可以被覆盖重写
           * */
          @Override
          public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
              HttpServletRequest request1 = (HttpServletRequest) servletRequest;
              if (PathMatcher.canIgnore(request1.getServletPath(),ignorePath) ||
                      filterCondition(request1)){
                  //将HttpRequest请求对象装饰后,传到过滤器链
                  filterChain.doFilter(new MyHttpServletRequestWrapper((HttpServletRequest) servletRequest), servletResponse);
              }else {
                  Map<String,Object> result = HttpResultFactory.builderFailtureResult(setErrorMessage());
                  sendToClient((HttpServletResponse) servletResponse, JSONObject.toJSONString(result));
              }
          }
    
          /**
           * 设置如果不满足条件 就返回给用户的错误信息
           * */
          public abstract String setErrorMessage();
    
          /**
           * 设置过滤条件
           * */
          public abstract boolean filterCondition(HttpServletRequest request) ;
    
          @Override
          public void destroy() {
    
          }
      }
    
  2. UserLoginFilterAdapter.java 登录权限拦截器

      package com.br.antifroud.filter;
    
      import com.br.antifroud.base.enums.SysCodeEnums;
    
      import javax.servlet.annotation.WebFilter;
      import javax.servlet.annotation.WebInitParam;
      import javax.servlet.http.HttpServletRequest;
    
      /**
       * @author Wang Weiwei <weiwei02@vip.qq.com / weiwei.wang@100credit.com>
       * @version 1.0
       * @sine 17-3-23
       * 登录拦截器 拦截未登录的用户
       * 如不需登录的地址可在web.xml中声明
       */
      @WebFilter(
              filterName = "01_UserLoginFilterAdapter",
              urlPatterns = "*.do",
              initParams = {
                      @WebInitParam(name = "ignoreLoginPath",value = "/index/*.do")
              })
      public class UserLoginFilterAdapter extends BaseFilterAdapter {
          /**
           * 返回用户尚未登录错误
           * */
          @Override
          public String setErrorMessage() {
              return SysCodeEnums.NOT_LOGIN.getMessage();
          }
    
          @Override
          public boolean filterCondition(HttpServletRequest request1) {
              //* 如果用户访问的路径无需登录或用户已登录
              return  request1.getSession().getAttribute("user") != null;
          }
      }
    
  3. PrivilegeFilterAdapter.java 权限链接拦截器

    package com.br.antifroud.filter;

    import com.br.antifroud.base.enums.SysCodeEnums;

    import javax.servlet.annotation.WebFilter; import javax.servlet.annotation.WebInitParam; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; import java.util.Set;

    /**

    • @author Wang Weiwei <weiwei02@vip.qq.com / weiwei.wang@100credit.com>

    • @version 1.0

    • @sine 17-3-23

    • 权限url 拦截器 */ @WebFilter( filterName = "02_PrivilegeFilter", //要拦截的路径集合 urlPatterns = { "/users/" }, initParams = { @WebInitParam(name = "ignoreLoginPath",value = "") }) public class PrivilegeFilterAdapter extends BaseFilterAdapter { /

      • 如用户没有访问该url的权限,且该url不在系统忽略的名单中,
      • 则拦截本次请求并返回权限不足错误
      • */ public String setErrorMessage() { return SysCodeEnums.USER_NOT_AUTH.getMessage(); }

      /**

      • 设置权限过滤条件
      • */ public boolean filterCondition(HttpServletRequest request1) { HttpSession session = request1.getSession(); return PathMatcher.canIgnore(request1.getServletPath(), (Set<String>) session.getAttribute("permissionSet")); } }
  4. PathMatcher.java url路径解析器

      package com.br.antifroud.filter;
    
      import org.springframework.util.AntPathMatcher;
    
      import java.util.Set;
    
      /**
       * @author Wang Weiwei <weiwei02@vip.qq.com / weiwei.wang@100credit.com>
       * @version 1.0
       * @sine 17-3-23
       *
       * url拦截匹配方法集合
       */
      public class PathMatcher {
          private static AntPathMatcher antPathMatcher = new AntPathMatcher();
          /**
           * 匹配url是否可以忽略
           * @param url 当前url
           * @param ignorePath 可以忽略的url 集合
           * */
          public static boolean canIgnore(String url, Set<String> ignorePath) {
              for (String path : ignorePath){
                  if (antPathMatcher.match(path,url)){
                      return true;
                  }
              }
              return false;
          }
      }
    
  5. MyHttpServletRequestWrapper.java 请求装饰器(防止js攻击)

    package com.br.antifroud.filter;

    import com.br.antifroud.util.StringUtil;

    import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; import java.util.ArrayList; import java.util.List;

    /**

    • @author Wang Weiwei <weiwei02@vip.qq.com / weiwei.wang@100credit.com>

    • @version 1.0

    • @sine 17-3-23 */ public class MyHttpServletRequestWrapper extends HttpServletRequestWrapper { public MyHttpServletRequestWrapper(HttpServletRequest request) { super(request); } // 此处是不过滤的参数 private List<String> excludeNames = new ArrayList<String>() { // 该属性字符不转义 { add("operators"); add("oldPassword"); add("newpassword"); add("confirmpassword"); add("password"); add("params"); add("newPurchaseJson"); add("resources"); add("code"); add("veCode"); add("title"); add("content"); add("logics"); } };

      public String getHeader(String name) {

       return StringUtil.replaceXss(super.getHeader(name));
      

      }

      public String getParameter(String name) {

       if (excludeNames != null && excludeNames.contains(name)) {
           return super.getParameter(name);
       }
       return StringUtil.replaceXss(super.getParameter(name));
      

      }

      public String[] getParameterValues(String name) {

       if (excludeNames != null && excludeNames.contains(name)) {
           return super.getParameterValues(name);
       }
       String[] params = super.getParameterValues(name);
       if (params == null){
           params = new String[0];
       }
       for (int i = 0; i < params.length; i++) {
           params[i] = StringUtil.replaceXss(params[i]);
       }
      
       return params;
      

      } }

综合以上5份代码,便构建一个优美的轻量级权限控制系统.当设计模式与web开发结合起来,我们或许不用再忍受单调的mvc,我们或许可以从追求完美的设计中寻找乐趣. 最后,感谢我的同事在我工作中提供的大量支持.

展开阅读全文
打赏
2
58 收藏
分享
加载中
为为02博主

引用来自“ylmotol7”的评论

shio是可以在非web下使用的

嗯,shiro是可以的,但有时候也会显得太过重量级
2017/04/04 18:59
回复
举报
shio是可以在非web下使用的
2017/04/04 18:45
回复
举报
@为为02 idea里面什么插件?
2017/04/01 19:51
回复
举报
为为02博主

引用来自“程序设计的艺术”的评论

Uml用的什么插件

intellij idea
2017/03/31 13:37
回复
举报
Uml用的什么插件
2017/03/31 13:26
回复
举报
更多评论
打赏
5 评论
58 收藏
2
分享
返回顶部
顶部