跳至主要內容

项目开发扩展-⑤拦截器

holic-x...大约 7 分钟框架Springboot

项目开发扩展-⑤拦截器

拦截器

【1】应用场景

【2】拦截器应用

a.子系统标识校验

(1)实现思路说明

SID验证实现参考:

【1】借助拦截器实现,每扩展一个子系统需要自定义相应的拦截器,随后加载拦截器配置

【2】借助自定义注解实现(类似日志AOP),考虑通用性和灵活性:

​ https://www.cnblogs.com/doge-elder/p/12728478.html

​ 自定义AOP实现:doBefore(),如果要正常处理在doBefore方法中不要捕捉处理异常,将异常抛出交由全局异常拦截器进行处理,否则可能出现异常处理后程序还是继续访问Controller接口,与理想的情况存在偏差导致出错(此处与日志处的AOP定义处理不太一样)

image-20200526234543064

​ 参考链接:https://www.cnblogs.com/yhtboke/p/5749063.html

image-20200526233710046

​ 问题说明:针对get方式可正常获取参数,但针对post方式被拦截器拦截后参数无法正常传递到相应的controller层导致响应出错

(2)实现参考说明
自定义过滤器校验

​ 针对GET请求可以通过request.getParameter("xxx")获取指定的参数

​ 但是POST请求处理的时候会出现拦截器获取参数后Controller层却无法正常读取参数,借助过滤器将POST请求参数进行过滤处理

image-20200526160526844

​ 但针对文件上传的时候,借助过滤器转换处理MultipartFile会报相关异常还未解决,因此此处暂定凡是文件上传下载相关均用GET请求处理(包括多文件、多参数上传也可通过FORM-DATA实现传递),过滤器只针对POST请求的参数做转换

// 如果request请求为MultipartHttpServletRequest不关闭流,避免出现org.apache.tomcat.util.http.fileupload.FileUploadException: Stream closed异常

RequestWrapper:

package com.sz.framework.filter;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.*;

/**
 * @ClassName RequestWrapper
 * @Description 包装HttpServletRequest, 目的是让其输入流可重复读
 * @Author
 * @Date 2020/5/26 12:12
 * @Version
 **/
public class RequestWrapper extends HttpServletRequestWrapper {

    private static final Logger log = LoggerFactory.getLogger(RequestWrapper.class);

    private final String body;

    public RequestWrapper(HttpServletRequest request) {
        super(request);
        StringBuilder stringBuilder = new StringBuilder();
        BufferedReader bufferedReader = null;
        InputStream inputStream = null;
        try {
            inputStream = request.getInputStream();
            if (inputStream != null) {
                bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
                char[] charBuffer = new char[128];
                int bytesRead = -1;
                while ((bytesRead = bufferedReader.read(charBuffer)) > 0) {
                    stringBuilder.append(charBuffer, 0, bytesRead);
                }
            } else {
                stringBuilder.append("");
            }
        } catch (IOException ex) {

        } finally {
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (bufferedReader != null) {
                try {
                    bufferedReader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        body = stringBuilder.toString();
    }

    @Override
    public ServletInputStream getInputStream() throws IOException {
        final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body.getBytes());
        ServletInputStream servletInputStream = new ServletInputStream() {
            @Override
            public boolean isFinished() {
                return false;
            }

            @Override
            public boolean isReady() {
                return false;
            }

            @Override
            public void setReadListener(ReadListener readListener) {
            }

            @Override
            public int read() throws IOException {
                return byteArrayInputStream.read();
            }
        };
        return servletInputStream;

    }

    @Override
    public BufferedReader getReader() throws IOException {
        return new BufferedReader(new InputStreamReader(this.getInputStream()));
    }

    public String getBody() {
        return this.body;
    }
}

ReplaceStreamFilter:

package com.sz.framework.filter;

import com.sz.framework.utils.CommonUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;

/**
 * @ClassName ReplaceStreamFilter
 * @Description 请求参数过滤器
 * @Author
 * @Date 2020/5/26 12:25
 * @Version
 **/
public class ReplaceStreamFilter implements Filter {

    private static final Logger log = LoggerFactory.getLogger(ReplaceStreamFilter.class);

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        log.info(CommonUtil.getFormatLog("StreamFilter初始化"));
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        ServletRequest requestWrapper = null;
        // 处理POST请求参数
        if ("POST".equals(((HttpServletRequest) request).getMethod().toUpperCase())) {
            requestWrapper = new RequestWrapper((HttpServletRequest) request);
        }
        if (requestWrapper == null) {
            // 正常过滤请求
            chain.doFilter(request, response);
        } else {
            // 按照指定规则过滤请求
            chain.doFilter(requestWrapper, response);
        }
    }

    @Override
    public void destroy() {
        log.info(CommonUtil.getFormatLog("StreamFilter销毁"));
    }
}

FilterConfig:

// @Configuration 过滤器配置(此处暂时取消过滤器配置:避免文件上传出错)
public class FilterConfig {

    /**
     * 注册过滤器
     *
     * @return FilterRegistrationBean
     */
    @Bean
    public FilterRegistrationBean someFilterRegistration() {
        FilterRegistrationBean registration = new FilterRegistrationBean();
        registration.setFilter(replaceStreamFilter());
        registration.addUrlPatterns("/*");
        registration.setName("streamFilter");
        return registration;
    }

    /**
     * 实例化StreamFilter
     *
     * @return Filter
     */
    @Bean(name = "replaceStreamFilter")
    public Filter replaceStreamFilter() {
        return new ReplaceStreamFilter();
    }

}
AOP配置SID校验
<1>自定义sid注解
<2>定义sid校验切面
<3>在controller层通过配置注解引用sid校验

SidAnno

@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SidAnno {

    // SID:子系统标签(标识)
    public String SID() default "";

    // 接口访问类型:公共接口、子系统自定义接口
    public AccessType ACCESS_TYPE() default AccessType.COMMON;

}

SidAspect

@Aspect
@Component
public class SidAspect {

    // 本地日志记录
    private static final Logger logger = LoggerFactory.getLogger(SidAspect.class);

    // 定义Controller切点
    // @Pointcut("execution (public * *Controller(..))") 通过方法签名定义切点
    // @Pointcut("execution (* com.sz.mip.*.*.controller..*.*(..))") 通过包名定义切点(凡是controller包下的类都会执行)
    // 配置指定注解为切入点(自定义:只有当配置了自定义注解的方法或者类才执行)
    @Pointcut("@annotation(com.sz.mip.annotation.SidAnno)")
    public void controllerAspect() {
    }

    // 前置通知
    @Before(value = "controllerAspect()")
    public void doBefore(JoinPoint joinPoint) throws Exception {
        handleSid(joinPoint, null, null);
    }

    /**
     * 是否存在注解,如果存在就获取
     */
    private SidAnno getAnnotation(JoinPoint joinPoint) throws Exception {
        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method method = methodSignature.getMethod();
        if (method != null) {
            return method.getAnnotation(SidAnno.class);
        }
        return null;
    }

    /**
     * 判断本次请求的数据类型是否为json
     */
    private boolean isJson(HttpServletRequest request) {
        if (request.getContentType() != null) {
            return request.getContentType().equals(MediaType.APPLICATION_JSON_VALUE) ||
                    request.getContentType().equals(MediaType.APPLICATION_JSON_UTF8_VALUE);
        }
        return false;
    }

    /**
     * validSid:校验注解指定的SID参数和request请求的参数是否一致
     **/
    public void validSid(SidAnno sidAnno, String sid) throws Exception {
        // 分别处理request、response请求的参数
        logger.info("[mip-framework]-校验子系统标识(SID)");
        // 校验指定SID
        if (StringUtils.isEmpty(sid)) {
            throw new AdminException("[mip-framework]-指定子系统标识不能为空");
        }
        // 校验是否为EOAS子系统标识
        if (!sid.equals(sidAnno.SID())) {
            throw new AdminException("[mip-framework]-当前指定子系统标识并非当前访问子系统限定标识,请确认后重新操作");
        }
    }

    private void handleSid(JoinPoint joinPoint, final Exception e, AjaxResult ajaxResult) throws Exception {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        // 获得注解
        SidAnno sidAnno = getAnnotation(joinPoint);
        if (sidAnno == null) {
            // 放行
            return;
        }
        if (StringUtils.isEmpty(sidAnno.SID())) {
            throw new AdminException("[mip-framework]-后台接口指定SID不能为空");
        }
        if (AccessType.CUSTOM.equals(sidAnno.ACCESS_TYPE())) {
            logger.info("[mip-framework]:子系统自定义接口访问SID校验");
            // 如果是子系统自定义接口访问,则进行子系统校验
            String sid = null;
            String requestMethod = request.getMethod();
            // 如果是get请求则正常通过request.getParameter()方法获取
            if ("GET".equals(requestMethod)) {
                logger.info("[GET请求拦截]");
                sid = request.getParameter("SID");
//          String param = request.getParameter("param");
            } else if ("POST".equals(requestMethod)) {
                logger.info("[POST请求拦截]");
                // 如果是post请求
                if (isJson(request)) {
                    // 获取处理后的JSON字符串,校验参数
                    String jsonParam = new RequestWrapper(request).getBody();
                    // log.info("[preHandle] json数据 : {}", jsonParam);
                    JSONObject jsonObject = JSONObject.parseObject(jsonParam);
                    sid = jsonObject.getString("SID");
                }
            }
            // 获取当前注解指定的SID,校验和request中请求的参数是否一致
            validSid(sidAnno, sid);
        } else if (AccessType.COMMON.equals(sidAnno.ACCESS_TYPE())) {
            // 如果是公共接口则进行额外校验
            logger.info("[mip-framework]:公共接口访问SID校验");
        } else if (AccessType.OTHER.equals(sidAnno.ACCESS_TYPE())) {
            // 其他接口访问类型扩展
        }

    }


    // 配置后置返回通知,使用在方法aspect()上注册的切入点
    @AfterReturning(value = "controllerAspect()", returning = "ajaxResult")
    public void afterReturn(JoinPoint joinPoint, AjaxResult ajaxResult) {

    }

    // 异常通知:用于拦截记录异常日志
    @AfterThrowing(pointcut = "controllerAspect()", throwing = "e")
    public void doAfterThrowing(JoinPoint joinPoint, Exception e) {
    }

}

Controller

	@SidAnno(SID = "xxx")
    @PostMapping("/methodName")
    public AjaxResult methodName(@RequestBody JSONObject jsonObject) {
        return AjaxResultUtil.success();
    }

b.数据访问权限

数据访问权限说明

如何在应用系统中实现数据权限的控制功能:https://www.cnblogs.com/wuhuacong/p/3664204.html

若依后台管理系统代码分析:https://www.cnblogs.com/zhzJAVA11/p/9994654.html

MyBatis分页拦截器实现数据访问权限拦截控制:

https://blog.csdn.net/weixin_34239592/article/details/92060263?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-4.nonecase&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-4.nonecase

通用条件筛选过滤条件设定:

https://www.cnblogs.com/leoxie2011/archive/2012/03/20/2408542.html

通用权限管理系统组件回答用户的常用问题:操作权限、用户角色、数据权限的解决方法:https://www.cnblogs.com/jirigala/p/3432164.html

https://www.cnblogs.com/jirigala/p/3432164.html

基于SpringAOP实现数据权限控制

https://blog.csdn.net/jaune161/article/details/78984490?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-3.nonecase&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-3.nonecase

【3】常见问题

springboot拦截器的实现

https://blog.csdn.net/wmq_fly/article/details/82634287

https://blog.csdn.net/weixin_42401867/article/details/87364719

拦截器中获取request或者get请求的参数(处理不同)

https://blog.csdn.net/weixin_44560245/article/details/90700720

敏感数据脱敏处理参考:

https://blog.csdn.net/qq_26365837/article/details/89926487?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.nonecase&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.nonecase

https://www.jianshu.com/p/d54a970c251a(数据库建表添加字段属性:是否要进行脱敏处理)

https://blog.csdn.net/f1576813783/article/details/77253233

springboot设定拦截器不生效(结合springboot版本设定)

参考链接:https://blog.csdn.net/u012862619/article/details/81557779

拦截器不生效除却配置的原因,检查拦截路径是否正确:

WebMvcConfigurer、WebMvcConfigurationSupport两种配置实现方式

拦截器实现参考:

https://blog.csdn.net/wmq_fly/article/details/82634287

根据ServletRequestAttributes 获取前端请求方法名、参数、路径等信息(获取到的是表单数据)

https://blog.csdn.net/m0_37635053/article/details/103969075

获取参数列表:获取到的是URL后面拼接的参数
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
Map<String, String[]> map = request.getParameterMap();
String params = JSONObject.toJSONString(map);
log.info("[请求参数]"+params);

Springboot:获取request参数:

通常对安全性有要求的接口都会对请求参数做一些签名验证,一般把验签的逻辑统一放到过滤器或拦截器里,这样就不用每个接口都去重复编写验签的逻辑

https://blog.csdn.net/weixin_44560245/article/details/90700720

初定数据导入导出均用GET、POST用于正常功能交互(CRUD)操作

image-20200526123159504

拦截过滤配置相关:(笔记整理)

springboot 拦截器统一处理post get 以及文件上传:将参数转化

https://blog.csdn.net/qq_36066039/article/details/106142541

Springboot设定跨域请求失败

考虑shiro拦截导致

参考链接:

https://www.cnblogs.com/shenxingping/p/11389287.html

评论
  • 按正序
  • 按倒序
  • 按热度
Powered by Waline v3.1.3