Use template method pattern to build permission filters

Keywords: Java Web Development Spring Session

Scene analysis

In web applications, almost all of our systems involve the processing of privileges. How to design privileges and control privileges is a problem we can not ignore in Web development. Although there are many excellent security processing frameworks like spring security and shiro in the open source world, their requirements are relatively simple. The implementation of these frameworks is still based on our servlet filters, so we should try to avoid using these relatively heavy frameworks and achieve our permission control needs only through our java. As we all know, spring can become the standard of java industry because native java is too cumbersome in some work, especially in Web development. If you use standard servlets, it must be to torture programmers. Can this change a little in the servlet 3.0 era? ? I think in a way. servlet 3.0 itself supports full annotation to avoid configuring web applications. When combined with beautiful design patterns, it sparks, and achieves perfect and efficient code.

Let's start with a brief introduction to the design patterns that will be used in the code:

Chain of Responsibility

> Responsibility chain mode, there are many objects with the same interface, each object holds a reference to the next object, which will form a chain, requests in this chain, until an object decides to process the request. However, the sender is not sure which object will handle the request in the end, so the responsibility chain model can be realized, and the system can be dynamically adjusted under the circumstance of concealing the client.

In this example, UML diagrams are used to illustrate the following:

The filter mechanism in our Servlet just uses the responsibility chain mode. http requests propagate on the server's filter chain. The main function of the interceptor is to intercept the customer's HttpServletRequest before HttpServletRequest reaches the Servlet. Check the HttpServletRequest as needed, or modify the HttpServletRequest. TtpServletRequest header and data. Before HttpServletResponse arrives at the client, intercept HttpServletResponse. Check HttpServletResponse as needed to modify HttpServletResponse header and data. Its execution logic is briefly described as follows:

Adapter mode

> The adapter pattern converts the interface of a class into another interface representation expected by the client, aiming at eliminating the class compatibility problem caused by interface mismatch.

The UML diagram shows the following:

From the above we know that to write a filter, you must implement the Filter interface.

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

In our filters, it's obvious that only one doFilter () method is needed, so we need to create an adapter between our interceptor implementation class and the Filter interface to clarify the role of our implementation class.

Template Method

> In an abstract class, there is a main method, and then define 1...n methods, which can be abstract or practical. Define a class, inherit the abstract class, rewrite the abstract method, and invoke the abstract class to realize the invocation of subclasses.

The UML diagram shows the following:

code implementation

Combining the three design modes, we can easily build a highly available and flexible privilege management system in the environment of servlet 3.0. Part of the code is shown below.

  1. BaseFilterAdapter.java Core Implementation Class

        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());
                }
            }
    
            /**
             * The main method of filtering can be overridden and rewritten
             * */
            @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)){
                    //Decorate the HttpRequest request object and pass it to the filter chain
                    filterChain.doFilter(new MyHttpServletRequestWrapper((HttpServletRequest) servletRequest), servletResponse);
                }else {
                    Map<String,Object> result = HttpResultFactory.builderFailtureResult(setErrorMessage());
                    sendToClient((HttpServletResponse) servletResponse, JSONObject.toJSONString(result));
                }
            }
    
            /**
             * Set the error message returned to the user if the condition is not met
             * */
            public abstract String setErrorMessage();
    
            /**
             * Setting filter conditions
             * */
            public abstract boolean filterCondition(HttpServletRequest request) ;
    
            @Override
            public void destroy() {
    
            }
        }
  2. UserLoginFilterAdapter.java Logon Rights Interceptor

        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
         * Logon Interceptor Intercepts Unlogged Users
         * Addresses that do not require login can be declared in web.xml
         */
        @WebFilter(
                filterName = "01_UserLoginFilterAdapter",
                urlPatterns = "*.do",
                initParams = {
                        @WebInitParam(name = "ignoreLoginPath",value = "/index/*.do")
                })
        public class UserLoginFilterAdapter extends BaseFilterAdapter {
            /**
             * Return user not logged in error
             * */
            @Override
            public String setErrorMessage() {
                return SysCodeEnums.NOT_LOGIN.getMessage();
            }
    
            @Override
            public boolean filterCondition(HttpServletRequest request1) {
                //* If the path accessed by the user does not need to be logged in or the user has logged in
                return  request1.getSession().getAttribute("user") != null;
            }
        }
  3. PrivilegeFilterAdapter.java privilege link interceptor

    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
     *
     * Permission URL interceptor
     */
    @WebFilter(
            filterName = "02_PrivilegeFilter",
            //Set of paths to intercept
            urlPatterns = {
                    "/users/**"
            },
            initParams = {
                    @WebInitParam(name = "ignoreLoginPath",value = "")
            })
    public class PrivilegeFilterAdapter extends BaseFilterAdapter {
        /**
         * If the user does not have access to the url and the url is not on the list ignored by the system,
         * Intercept this request and return the inadequate privilege error
         * */
        public String setErrorMessage() {
            return SysCodeEnums.USER_NOT_AUTH.getMessage();
        }
    
        /**
         * Setting permission filtering conditions
         * */
        public boolean filterCondition(HttpServletRequest request1) {
            HttpSession session = request1.getSession();
            return PathMatcher.canIgnore(request1.getServletPath(), (Set<String>) session.getAttribute("permissionSet"));
        }
    }
  4. PathMatcher.java url path parser

        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
         *
         * urlInterception Matching Method Set
         */
        public class PathMatcher {
            private static AntPathMatcher antPathMatcher = new AntPathMatcher();
            /**
             * Can matching URLs be ignored
             * @param url Current URL
             * @param ignorePath Negligible url sets
             * */
            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 Request Decorator (to prevent JS attacks)

    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);
        }
        // Here are unfiltered parameters
        private List<String> excludeNames = new ArrayList<String>() { // The attribute character is not escaped
            {
                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;
        }
    }

    Combining the above five codes, we can build a graceful lightweight privilege control system. When design patterns are combined with web development, we may no longer have to endure monotonous mvc. We may find pleasure in pursuing perfect design. Finally, I would like to thank my colleagues for their great support in my work.

Posted by goclimb on Sun, 14 Jul 2019 10:21:40 -0700