每次处理请求之前,需要先验证一下,当前用户是否具备此次请求的处理的权限。根据前面我们的定义(详见6.1),当前用户允许/支持的权限,存在session{loginAuth{auths}}中。
    如何得知此次请求所需要的权限?
    * 自定义注解
    * 注解可以作用在controller的方法上
    * 注解中指定要想让当前方法处理请求,所需要的权限范围
    这个权限认证需要在请求之前执行—-AOP编程中切面(Tomcat中Filter)
    因为当我们当前访问controller的方法时,会有mvc框架调用,所以这个切面需要由mvc框架提供和调用。

    image.png①业务程序员根据业务创建权限验证的切面对象 ,这个切面对象由框架调用 ,所以需要让框架知道有这么一个(类)切面 —- xml配置
    image.png
    mvc.xml

    1. <?xml version="1.0" encoding="UTF-8"?>
    2. <mvc>
    3. <!--人为规定使用<mvc-interceptor class="util.AuthInterceptor">
    4. 告诉框架在处理请求执行/在调用controller方法之前,有一个切面(拦截器)对象需要执行
    5. 切面不一定在所有的请求前处理
    6. 人为规定两个子标签
    7. <include></include>指定在哪些请求前处理
    8. <exclude></exclude>指定不在哪些请求前处理
    9. 如果两个子标签都不存在,表示所有请求前都要处理
    10. -->
    11. <mvc-interceptor class="util.AuthInterceptor">
    12. <include>/test1,/test2,/test3</include>
    13. </mvc-interceptor>
    14. <mvc-interceptor class="util.TestInterceptor">
    15. <exclude>/test1,/test2</exclude>
    16. </mvc-interceptor>
    17. </mvc>

    DispatcherServlet
    image.png

    private List<InterceptorInfo> interceptorInfoList=new ArrayList<>();
    
        //读取xml中<mvc-interceptor>信息
        private void parseMvcInterceptorElement(Document document) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
            //按照标签的嵌套规则,找到需要的所有mapping标记
            List<Element> mvcInterceptorElement = (List<Element>) document.selectNodes("mvc/mvc-interceptor");
            for (Element element : mvcInterceptorElement) {
                /**
                 * 获取标签指定的属性值,标签中记载着请求与响应对应关系
                 * 此时(初始化)获得这个对应关系是为了后面请求的时候用
                 * 所以需要存储起来
                 */
                String className = element.attributeValue("class");
    
                //反射创建目标对象
                Object interceptor= Class.forName(className).newInstance();
                InterceptorInfo interceptorInfo=new InterceptorInfo();
                interceptorInfo.setInterceptor(interceptor);
    
                Element includeEle=(Element) element.selectSingleNode("include");
                Element excludeEle=(Element) element.selectSingleNode("exclude");
                if(includeEle!=null){
                    String includeStr=includeEle.getText();
                    interceptorInfo.setIncludeUrl(includeStr);
                }
                if(excludeEle!=null){
                    String excludeStr=excludeEle.getText();
                    interceptorInfo.setExcludeUrl(excludeStr);
                }
                interceptorInfoList.add(interceptorInfo);
    
            }
        }
    

    InterceptorInfo

    package myweb;
    
    import java.util.Arrays;
    import java.util.HashSet;
    import java.util.List;
    import java.util.Set;
    
    /**
     * 存储拦截器相关信息
     */
    public class InterceptorInfo {
        private Object interceptor;
        private Set<String> includeUrl=new HashSet<>();
        private Set<String> excludeUrl=new HashSet<>();
    
        public InterceptorInfo() {}
    
        public InterceptorInfo(Object interceptor, Set<String> includeUrl, Set<String> excludeUrl) {
            this.interceptor = interceptor;
            this.includeUrl = includeUrl;
            this.excludeUrl = excludeUrl;
        }
    
        public Object getInterceptor() {
            return interceptor;
        }
    
        public void setInterceptor(Object interceptor) {
            this.interceptor = interceptor;
        }
    
        public Set<String> getIncludeUrl() {
            return includeUrl;
        }
    
        public void setIncludeUrl(String includeUrl) {
            if(includeUrl!=null&&!"".equals(includeUrl)){
                String[] ss=includeUrl.split(",");
                List<String> list= Arrays.asList(ss);
                this.includeUrl.addAll(list);
            }
        }
    
        public Set<String> getExcludeUrl() {
            return excludeUrl;
        }
    
        public void setExcludeUrl(String excludeUrl) {
            if(excludeUrl!=null&&!"".equals(excludeUrl)){
                String[] ss=excludeUrl.split(",");
                List<String> list= Arrays.asList(ss);
                this.excludeUrl.addAll(list);
            }
        }
    }
    

    ②框架知道要调用某些切面,就一定能调用么 ?
    需要保证业务程序员编写(提供)的切面对象,符合框架的调用规则 —-接口
    MvcInterceptor

    package myweb;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.lang.reflect.Method;
    
    public interface MvcInterceptor {
        //编写请求处理之前需要切面执行的操作
        //根据业务处,需要继续处理请求,返回true;终止此次请求处理,返回false
        public boolean prevHandle(HttpServletRequest request, HttpServletResponse response, Method method);
    
        //编写请求处理之后需要切面执行的操作
        public void postHandle(HttpServletRequest request, HttpServletResponse response);
    }
    

    ③调用切面,根据切面的处理结果,调用controller方法 —-框架增加AOP机制
    Chain(之前封装的框架里面一部分方法转移到了这个类中)

    package myweb;
    
    import myweb.annotation.RequestParam;
    import org.apache.commons.fileupload.FileItem;
    import org.apache.commons.fileupload.FileUploadException;
    import org.apache.commons.fileupload.disk.DiskFileItemFactory;
    import org.apache.commons.fileupload.servlet.ServletFileUpload;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import javax.servlet.http.HttpSession;
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.UnsupportedEncodingException;
    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    import java.lang.reflect.Parameter;
    import java.util.*;
    
    /**
     * 链对象,负责管理和调用 切面对象+目标controller对象
     */
    public class Chain {
        private List<InterceptorInfo> aspects;//链要执行的切面
        private MappingInfo info;//装载请求对应的Controller , Method
    
        private HttpServletRequest request ;
        private HttpServletResponse response ;
    
        private String multipartEncoding ;
    
        public Chain(List<InterceptorInfo> aspects, MappingInfo info, HttpServletRequest req, HttpServletResponse resp, String multipartEncoding) {
            this.aspects = aspects;
            this.info = info;
            this.request = req;
            this.response = resp;
            this.multipartEncoding = multipartEncoding;
        }
    
        public Object execute() throws InvocationTargetException, IllegalAccessException, InstantiationException {
            //先执行切面
            if(aspects.size() > 0){
                InterceptorInfo interceptorInfo= aspects.remove(0);
                //判断此次请求是否需要执行当前的拦截器
                String path= info.getPath();//此次请求
                Set<String> includeUrl=interceptorInfo.getIncludeUrl();
                Set<String> excludeUrl=interceptorInfo.getExcludeUrl();
                int flag = -1 ;//表示执行与否的状态 -1不执行,1执行 , 0 抛异常
    
                if(includeUrl.size() == 0 && excludeUrl.size() ==0){
                    flag = 1 ;
                }
    
                if(includeUrl.contains(path)){
                    //需要执行
                    flag = 1 ;
                }
                if(excludeUrl.contains(path)){
                    //是一个排除在外的请求,不执行拦截器
                    if(flag == 1){
                        //之前include中已经包含这个请求,结果exclude还包含
                        flag = 0 ;
                        throw new RuntimeException("混乱的拦截器配置,你是不是搞事情") ;
                    }
                    flag = -1 ;
                }else{
                    //不需要排除在外,要执行拦截器
                    if(excludeUrl.size() > 0) {
                        flag = 1;
                    }
                }
    
                if (flag==-1){
                    //当前拦截器不执行,继续看看下一个拦截器是否执行
                    return this.execute();
                }else {
                    //当前拦截器需要执行
                    MvcInterceptor interceptor = (MvcInterceptor) interceptorInfo.getInterceptor();
                    boolean f= interceptor.prevHandle(request,response, info.getMethod());
                    if(f==false){
                        //终止调用
                        return null;
                    }else {
                        //继续调用
                        Object result= this.execute();//调用下一个拦截器/切面 等价于 chain.doFilter()
                        interceptor.postHandle(request,response);
                        return result;
                    }
                }
            }else {
                //证明已经没有拦截器需要执行,那就执行目标
    
                //获得参数
                Map<String, Object> paramMap = receiveParams(request);
                //处理参数
                Object[] paramValues = handleParam(paramMap, info, request, response);
                //调用mappinginfo中的对象的方法,传递参数
                Object result = info.getMethod().invoke(info.getController(), paramValues);
                return result;
            }
    
        }
    
        /*
        接受请求传递的参数,将参数处理后装入map
        map.key就是请求传递参数key
        map.value可能是一个String[] 也可以是一个文件[]--Object表示
         */
        private Map<String, Object> receiveParams(HttpServletRequest request) {
            Map<String, Object> paramMap = new HashMap<>();
            //参数有两种可能(普通请求、文件上传请求)
            //普通请求按照文件上传方式处理,会抛出异常(选择这种为突破口)
            //文件上传请求按照普通请求处理会得不到参数
            try {
                //假设是文件上传方式传递参数
                DiskFileItemFactory factory = new DiskFileItemFactory();
                ServletFileUpload upload = new ServletFileUpload(factory);
                List<FileItem> fis = upload.parseRequest(request);//如果不是文件上传请求会抛出异常
                for (FileItem fileItem : fis) {
                    //拿到了一个参数
                    if (fileItem.isFormField()) {
                        //是一个普通参数
                        String key = fileItem.getFieldName();
                        String value = fileItem.getString(multipartEncoding);
    
                        String[] values = (String[]) paramMap.get(key);
                        if (values == null) {
                            values = new String[]{value};
                        } else {
                            values = Arrays.copyOf(values, values.length + 1);
                            values[values.length - 1] = value;
                        }
                        paramMap.put(key, values);
                    } else {
                        //是一个文件参数
                        String key = fileItem.getFieldName();
                        String fileName = fileItem.getName();//文件名
                        long size = fileItem.getSize();//文件大小
                        String contentType = fileItem.getContentType();//文件类型
                        InputStream is = fileItem.getInputStream();//文件内容
                        MultipartFile multipartFile = new MultipartFile(fileName, size, contentType, is);
                        MultipartFile[] mf = (MultipartFile[]) paramMap.get(key);
                        if (mf == null) {
                            mf = new MultipartFile[]{multipartFile};
                        } else {
                            mf = Arrays.copyOf(mf, mf.length + 1);
                            mf[mf.length - 1] = multipartFile;
                        }
                        paramMap.put(key, mf);
                    }
                }
            } catch (FileUploadException | UnsupportedEncodingException e) {
                //出现异常表示不是文件上传请求方式,按照普通请求方式处理
                Enumeration<String> names = request.getParameterNames();
                while (names.hasMoreElements()) {
                    String name = names.nextElement();
                    String[] values = request.getParameterValues(name);
                    paramMap.put(name, values);
                }
    
            } catch (IOException ioException) {
                ioException.printStackTrace();
            }
            return paramMap;
        }
    
        /*
        按照请求映射关系要调用的那个目标方法,获得目标方法的参数列表
        根据参数列表获得所需要的参数,并将参数组装到Object[]中
         */
        private Object[] handleParam(Map<String, Object> paramMap, MappingInfo info, HttpServletRequest req, HttpServletResponse resp) throws IllegalAccessException, InstantiationException, InvocationTargetException {
            Method method = info.getMethod();
            Parameter[] parameters = method.getParameters();
            Object[] paramValues = new Object[parameters.length];
            int i = 0;
            for (Parameter parameter : parameters) {
                RequestParam rp = parameter.getAnnotation(RequestParam.class);
                Class paramType = parameter.getType();
                if (rp != null) {
                    //注解的value就是key,可以用来获取实参
                    String key = rp.value();
                    Object value = paramMap.get(key);
                    if (value == null) {
                        //当前所需参数为空
                        i++;
                        continue;
                    }
                    //如果参数值存在,起初只有2种表现形成 String[] , MulitpartFile[]
                    //将此次需要的参数value,转换成方法中定义的需要参数类型
                    paramValues[i++] = castType(value, paramType);
                } else {
                    //没有@RequestParam注解,可能是request、response、session、domain(组装参数)
                    if (paramType == HttpServletRequest.class) {
                        paramValues[i++] = req;
                    } else if (paramType == HttpServletResponse.class) {
                        paramValues[i++] = resp;
                    } else if (paramType == HttpSession.class) {
                        paramValues[i++] = req.getSession();
                    } else {
                        Object paramObject = paramType.newInstance();
                        Method[] methods = paramType.getMethods();
                        //通过反射获得对象中的属性名,根据属性名找到与之同名的参数,为属性赋值
                        //为了更好的符合Java封装的特性,不建议通过反射找属性,建议找set方法
                        for (Method m : methods) {
                            String mname = m.getName();
                            if (mname.startsWith("set")) {
                                String key = mname.substring(3);
                                key = key.substring(0, 1).toLowerCase() + key.substring(1);
                                Object value = paramMap.get(key);
                                if (value == null) {
                                    continue;
                                }
                                Class domainParamType = m.getParameterTypes()[0];
                                Object obj = castType(value, domainParamType);
                                m.invoke(paramObject, obj);
                            }
                        }
                        //循环结束找到了所有set方法,赋值结束,将对象保存
                        paramValues[i++] = paramObject;
                    }
                }
            }
            return paramValues;
        }
    
        /*
        将原始类型的参数数据,转换成目标方法所需要的类型
        原始类型:String[],MultipartFile[]
        目标类型:int、long、double、String、Integer、MultipartFile以及这些类型的数组形式
         */
        private Object castType(Object value, Class paramType) {
            if (paramType == String.class) {
                String str = ((String[]) value)[0];
                return str;
            }
            if (paramType == int.class || paramType == Integer.class) {
                String str = ((String[]) value)[0];
                int num = Integer.parseInt(str);
                return num;
            }
            if (paramType == long.class || paramType == Long.class) {
                String str = ((String[]) value)[0];
                long num = Long.parseLong(str);
                return num;
            }
            if (paramType == double.class || paramType == Double.class) {
                String str = ((String[]) value)[0];
                double num = Double.parseDouble(str);
                return num;
            }
            if (paramType == String[].class) {
                return value;
            }
            if (paramType == int[].class) {
                String[] str = (String[]) value;
                int[] n = new int[str.length];
                for (int j = 0; j < str.length; j++) {
                    n[j] = Integer.parseInt(str[j]);
                }
                return n;
            }
            if (paramType == Integer[].class) {
                String[] str = (String[]) value;
                Integer[] nums = new Integer[str.length];
                for (int j = 0; j < str.length; j++) {
                    nums[j] = Integer.valueOf(str[j]);
                }
                return nums;
            }
            if (paramType == MultipartFile.class) {
                return ((MultipartFile[]) value)[0];
            }
            if (paramType == MultipartFile[].class) {
                return value;
            }
            return null;
        }
    
    }
    

    框架中处理动态资源的方法改为

    //处理动态资源请求
        private void handleDynamicResource(MappingInfo info, HttpServletRequest request, HttpServletResponse response) throws IllegalAccessException, InvocationTargetException, InstantiationException, IOException, ServletException {
            //需要带切面处理
            //使用责任链模式,实现切面与目标的调用
            List<InterceptorInfo> cloneList=new ArrayList<>();
            cloneList.addAll(interceptorInfoList);
            Chain chain=new Chain(cloneList,info,request,response,multipartEncoding);
            Object result=chain.execute();
    
            //根据result返回值实现响应
            handleResponse(result, info.getMethod(), request, response);
        }
    

    ④完善权限验证
    业务程序提供一个注解,负责在controller的方法上指定对应的权限范围