每次处理请求之前,需要先验证一下,当前用户是否具备此次请求的处理的权限。根据前面我们的定义(详见6.1),当前用户允许/支持的权限,存在session{loginAuth{auths}}中。
如何得知此次请求所需要的权限?
* 自定义注解
* 注解可以作用在controller的方法上
* 注解中指定要想让当前方法处理请求,所需要的权限范围
这个权限认证需要在请求之前执行—-AOP编程中切面(Tomcat中Filter)
因为当我们当前访问controller的方法时,会有mvc框架调用,所以这个切面需要由mvc框架提供和调用。
①业务程序员根据业务创建权限验证的切面对象
,这个切面对象由框架调用
,所以需要让框架知道有这么一个(类)切面
—- xml配置
mvc.xml
<?xml version="1.0" encoding="UTF-8"?>
<mvc>
<!--人为规定使用<mvc-interceptor class="util.AuthInterceptor">
告诉框架在处理请求执行/在调用controller方法之前,有一个切面(拦截器)对象需要执行
切面不一定在所有的请求前处理
人为规定两个子标签
<include></include>指定在哪些请求前处理
<exclude></exclude>指定不在哪些请求前处理
如果两个子标签都不存在,表示所有请求前都要处理
-->
<mvc-interceptor class="util.AuthInterceptor">
<include>/test1,/test2,/test3</include>
</mvc-interceptor>
<mvc-interceptor class="util.TestInterceptor">
<exclude>/test1,/test2</exclude>
</mvc-interceptor>
</mvc>
DispatcherServlet
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的方法上指定对应的权限范围