一、实现安全管控
在手写mvc框架得基础上(只完成了请求分发和自定义spring框架),定义注解@Security(有value属性,接收String数组),该注解用于添加在Controller类或者Handler方法上,实现安全管控。
一、思路
定义注解@Security,使用到Controller类上,代表其中的所有Handler方法都要进行安全管控;使用到Handler方法上,代表该方法需要进行安全管控。考虑了四种实现方法:
1.依照自定义spring框架中实现transactional的方式,我们可以使用代理,检查到有Controller类使用了@Security就进行动态代理,在代理类中进行安全检查;
2.使用过滤器Filter,但是过滤器只能拦截请求,无法详细到指定哪些方法处理;
3.使用拦截器Interceptor,很明显这个需要我们自己使用AOP(动态代理)实现;
4.我们映射handler的时候,有存储相应的关系,我们也做类似处理即可,创建一个请求与安全管控相应的关系map,在servlet doPost中添加上相应处理逻辑即可,简单快捷。
二、代码
我们在handlerMapping初始化的时候,读取其中的Security注解,获取其中的用户名,保存到securityMap中。
//securityMapping
private Map<String,Object> securityMap=new HashMap<String,Object>();
private void initHandlerMapping() {
if(ioc.isEmpty()) {
return;}
for(Map.Entry<String,Object> entry: ioc.entrySet()) {
// 获取ioc中当前遍历的对象的class类型
Class<?> aClass = entry.getValue().getClass();
if(!aClass.isAnnotationPresent(LagouController.class)) {
continue;}
boolean isAllSecurityMethod=false;
String[] SecutiryUser={
};
if(aClass.isAnnotationPresent(LagouSecurity.class)) {
isAllSecurityMethod=true;
LagouSecurity annotation = aClass.getAnnotation(LagouSecurity.class);
SecutiryUser = annotation.value();
}
String baseUrl = "";
if(aClass.isAnnotationPresent(LagouRequestMapping.class)) {
LagouRequestMapping annotation = aClass.getAnnotation(LagouRequestMapping.class);
baseUrl = annotation.value(); // 等同于/demo
}
// 获取方法
Method[] methods = aClass.getMethods();
for (int i = 0; i < methods.length; i++) {
Method method = methods[i];
// 方法没有标识LagouRequestMapping,就不处理
if(!method.isAnnotationPresent(LagouRequestMapping.class)) {
continue;}
// 如果标识,就处理
LagouRequestMapping annotation = method.getAnnotation(LagouRequestMapping.class);
String methodUrl = annotation.value(); // /query
String url = baseUrl + methodUrl; // 计算出来的url /demo/query
if(isAllSecurityMethod){
securityMap.put(url,SecutiryUser);
}else{
if(method.isAnnotationPresent(LagouSecurity.class)) {
LagouSecurity Mannotation = method.getAnnotation(LagouSecurity.class);
SecutiryUser = Mannotation.value();
securityMap.put(url,SecutiryUser);
}
}
// 把method所有信息及url封装为一个Handler
Handler handler = new Handler(entry.getValue(),method, Pattern.compile(url));
// 计算方法的参数位置信息 // query(HttpServletRequest request, HttpServletResponse response,String name)
Parameter[] parameters = method.getParameters();
for (int j = 0; j < parameters.length; j++) {
Parameter parameter = parameters[j];
if(parameter.getType() == HttpServletRequest.class || parameter.getType() == HttpServletResponse.class) {
// 如果是request和response对象,那么参数名称写HttpServletRequest和HttpServletResponse
handler.getParamIndexMapping().put(parameter.getType().getSimpleName(),j);
}else{
handler.getParamIndexMapping().put(parameter.getName(),j); // <name,2>
}
}
// 建立url和method之间的映射关系(map缓存起来)
handlerMapping.add(handler);
}
}
}
执行doPost的时候,检查我们的securityMap中是否存在相应的数据,若是存在就取检查请求中是否携带username参数,并检查是否属于我们允许的用户。
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 处理请求:根据url,找到对应的Method方法,进行调用
// 获取uri
// String requestURI = req.getRequestURI();
// Method method = handlerMapping.get(requestURI);// 获取到一个反射的方法
// 反射调用,需要传入对象,需要传入参数,此处无法完成调用,没有把对象缓存起来,也没有参数!!!!改造initHandlerMapping();
// method.invoke() //
// 根据uri获取到能够处理当前请求的hanlder(从handlermapping中(list))
Handler handler = getHandler(req);
if(handler == null) {
resp.getWriter().write("404 not found");
return;
}
String[] usernams= (String[]) securityMap.get( req.getRequestURI());
if(usernams!=null&& usernams.length>=1){
String username=req.getParameter("username");
if(username==null||username.equals("")){
resp.getWriter().write("username is empty");
return;
}
if(!Arrays.asList(usernams).contains(username)){
resp.getWriter().write("username"+username+"cannot access");
return;
}
}
// 参数绑定
// 获取所有参数类型数组,这个数组的长度就是我们最后要传入的args数组的长度
Class<?>[] parameterTypes = handler.getMethod().getParameterTypes();
// 根据上述数组长度创建一个新的数组(参数数组,是要传入反射调用的)
Object[] paraValues = new Object[parameterTypes.length];
// 以下就是为了向参数数组中塞值,而且还得保证参数的顺序和方法中形参顺序一致
Map<String, String[]> parameterMap = req.getParameterMap();
// 遍历request中所有参数 (填充除了request,response之外的参数)
for(Map.Entry<String,String[]> param: parameterMap.entrySet()) {
// name=1&name=2 name [1,2]
String value = StringUtils.join(param.getValue(), ","); // 如同 1,2
// 如果参数和方法中的参数匹配上了,填充数据
if(!handler.getParamIndexMapping().containsKey(param.getKey())) {
continue;}
// 方法形参确实有该参数,找到它的索引位置,对应的把参数值放入paraValues
Integer index = handler.getParamIndexMapping().get(param.getKey());//name在第 2 个位置
paraValues[index] = val