项目开发扩展-5.拦截器
项目开发扩展-拦截器
拦截器
【1】应用场景
【2】拦截器应用
a.子系统标识校验
(1)实现思路说明
SID验证实现参考:
【1】借助拦截器实现,每扩展一个子系统需要自定义相应的拦截器,随后加载拦截器配置
【2】借助自定义注解实现(类似日志AOP),考虑通用性和灵活性:
https://www.cnblogs.com/doge-elder/p/12728478.html
自定义AOP实现:doBefore(),如果要正常处理在doBefore方法中不要捕捉处理异常,将异常抛出交由全局异常拦截器进行处理,否则可能出现异常处理后程序还是继续访问Controller接口,与理想的情况存在偏差导致出错(此处与日志处的AOP定义处理不太一样)
参考链接:https://www.cnblogs.com/yhtboke/p/5749063.html
问题说明:针对get方式可正常获取参数,但针对post方式被拦截器拦截后参数无法正常传递到相应的controller层导致响应出错
(2)实现参考说明
自定义过滤器校验
针对GET请求可以通过request.getParameter("xxx")获取指定的参数
但是POST请求处理的时候会出现拦截器获取参数后Controller层却无法正常读取参数,借助过滤器将POST请求参数进行过滤处理
但针对文件上传的时候,借助过滤器转换处理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)操作
拦截过滤配置相关:(笔记整理)
springboot 拦截器统一处理post get 以及文件上传:将参数转化
https://blog.csdn.net/qq_36066039/article/details/106142541
Springboot设定跨域请求失败
考虑shiro拦截导致
参考链接:
https://www.cnblogs.com/shenxingping/p/11389287.html