若依项目学习笔记13——综合问题
1. XSS攻击
XSS攻击(跨站脚本攻击)通常指的是通过利用网页开发时留下的漏洞,通过巧妙的方法注入恶意指令代码到网页,使用户加载并执行攻击者恶意制造的网页程序。这些恶意网页程序通常是JavaScript,但实际上也可以包括Java、 VBScript、ActiveX、 Flash 或者甚至是普通的HTML。攻击成功后,攻击者可能得到包括但不限于更高的权限(如执行一些操作)、私密网页内容、会话和cookie等各种内容
1.1 演示
我们首先把配置文件中xss的过滤开关给关闭,然后我们运行项目-参数设置-新增
然后修改前端页面,找到下图中红框部分,将其改成 <div v-html="form.configName"></div>
重新运行项目,可以看到界面发生了混乱,同时有恶意脚本的弹出
并且新增进去的信息是直接存入标签,而不是目标信息
1.2 过滤详解
配置文件中的配置很简单,大家自行查看;我们打开过滤配置 framework.config.FilterConfig
//获取配置文件中的三个参数
@Value("${xss.enabled}")
private String enabled;
@Value("${xss.excludes}")
private String excludes;
@Value("${xss.urlPatterns}")
private String urlPatterns;
@SuppressWarnings({ "rawtypes", "unchecked" })
@Bean
public FilterRegistrationBean xssFilterRegistration()
{
FilterRegistrationBean registration = new FilterRegistrationBean();
//配置请求类型
registration.setDispatcherTypes(DispatcherType.REQUEST);
//实例过滤器,见下
registration.setFilter(new XssFilter());
//配置匹配连接
registration.addUrlPatterns(StringUtils.split(urlPatterns, ","));
registration.setName("xssFilter");
//设置优先级
registration.setOrder(FilterRegistrationBean.HIGHEST_PRECEDENCE);
//初始化参数
Map<String, String> initParameters = new HashMap<String, String>();
initParameters.put("excludes", excludes);
initParameters.put("enabled", enabled);
registration.setInitParameters(initParameters);
return registration;
}
我们打开 common.filter.XssFilter
init()
: 初始化,获取两个参数并转换成list和boolean类型,方便获取和判断doFilter()
:放行,根据开关的boolean值对指定内容进行匹配和放行,不放行则对其进行过滤,详见XssHttpServletRequestWrapper
2. 防止重复提交
如果用户在提交表单时点击频率过快或者所提交的内容和数据库中数据有重复,这样容易造成系统数据混乱,这时就需要我们对这种操作进行过滤匹配了;我们只需在目标方法(如删除、新增等等)上添加注解 @RepeatSubmit
即可
我们继续打开过滤配置 framework.config.FilterConfig
@SuppressWarnings({ "rawtypes", "unchecked" })
@Bean
public FilterRegistrationBean someFilterRegistration()
{
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter(new RepeatableFilter());
/匹配的url,默认所有
registration.addUrlPatterns("/*");
registration.setName("repeatableFilter");
//优先级比xss低
registration.setOrder(FilterRegistrationBean.LOWEST_PRECEDENCE);
return registration;
}
接下来进入到 RepeatableFilter
当中
public class RepeatableFilter implements Filter
{
@Override
public void init(FilterConfig filterConfig) throws ServletException
{
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException
{
ServletRequest requestWrapper = null;
if (request instanceof HttpServletRequest && StringUtils.equalsAnyIgnoreCase(request.getContentType(),
MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_JSON_UTF8_VALUE))
{
requestWrapper = new RepeatedlyRequestWrapper((HttpServletRequest) request, response);
}
if (null == requestWrapper)
{
chain.doFilter(request, response);
}
else
{
chain.doFilter(requestWrapper, response);
}
}
@Override
public void destroy()
{
}
}
防止重复提交拦截器见 framework.interceptor.RepeatSubmitInterceptor
3. 全局异常处理器
一般项目中有异常是直接用try...catch来捕获处理,但是在项目开发中,可能会存在大量的异常,如业务一场、权限不足等等问题,还用这种方法的话会很麻烦和冗余;所以我们单独设置一个类来处理这些异常。全局异常就是对框架所有异常进行统一管理。把可能发生的异常throw给这个类来统一处理;我们可以先打开 framework.web.exception.GlobalExceptionHandler全局异常处理器,可以看到其中有两个注解
@RestControllerAdvice
:在类上添加,用于拦截异常并统一处理;大家看一下官方文档的介绍@ExceptionHandler
:在方法上添加,用于指定来处理的异常,可以指定多个;
在全局异常处理器类中定义了许多异常的类型处理,我们只需在全局异常处理器类上添加注解 @RestControllerAdvice
,在其中每个异常处理方法上添加注解 @ExceptionHandler
即可;例如触发了 system.service.impl.SysRoleServiceImpl 中的 checkRoleAllowed()
方法,抛出了 CustomException
异常,这时就会调用到我们的全局异常处理器类,找到对应带有注解 @ExceptionHandler(CustomException.class)
的方法,然后进行处理,并把结果返回给前端显示
4. 框架验证
框架验证就是验证要提交的表单是否符合提交规则,如必填是否为空,选框是否勾选等等
4.1 前端部分
我们使用的是Element提供的组件;打开前端,这里使用 rules 和 configName ,进行表单规则和提示信息的映射,具体内容大家自行看element的文档
可以看到是一一对应的
4.2 后端部分
找到 web.controller.system.SysConfigController.add ,可以看到参数列表部分有注解 @Validated
,作用是开启验证
我们点进去参数 SysConfig 中,里面封装了各种验证的属性,在各种方法上有注解 @NotBlank
和 @Size
,这些都是 Validated 的api,直接调用,也可以自定义
如果检查验证不通过的话,会抛出异常,然后被上面讲到的全局异常处理器所处理
@ExceptionHandler(MethodArgumentNotValidException.class)
public Object validExceptionHandler(MethodArgumentNotValidException e)
{
log.error(e.getMessage(), e);
String message = e.getBindingResult().getFieldError().getDefaultMessage();
return AjaxResult.error(message);
}
5. 日志配置
日志的配置在全局配置文件中,见下
# 日志配置
logging:
level:
# 配置日志的包等级
com.ruoyi: debug
# 配置sb的日志等级
org.springframework: warn
spring boot(以后简称sb,单纯简称)默认继承logback,,就在resources目录下有个logback.xml文件,大部分配置都有注释,大家自行查看;
5.1 日志的使用
- 在目标类中导入对应的包
- 获取日志对象
private static final Logger log = LoggerFactory.getLogger(AddressUtils.class);
- 操作
log.error("获取地理位置异常 {}", ip);
用户日志处理在 framework.manager.AsyncManager中,大家自行查看
6. 文件上传
本项目的文件上传包括用户头像上传和同志公告中的图片上传
6.1 前端部分
前端我们使用的依旧是element的上传组件,我们打开userAvatat.vue,找到上传的方法
可以看到调用了 api.system.user.js 中的 uploadAvatar()
方法
6.2 后端部分
根据这个接口所调用的url,我们打开 web.controller.system.SysProfileController 的 avatar()
方法
@Log(title = "用户头像", businessType = BusinessType.UPDATE)
@PostMapping("/avatar")
public AjaxResult avatar(@RequestParam("avatarfile") MultipartFile file) throws IOException
{
if (!file.isEmpty())
{
//从缓存中获取登录用户
LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
//调用上传方法,参数:路径(已预先定义好)、文件
String avatar = FileUploadUtils.upload(RuoYiConfig.getAvatarPath(), file);
if (userService.updateUserAvatar(loginUser.getUsername(), avatar))
{
//刷新缓存
AjaxResult ajax = AjaxResult.success();
ajax.put("imgUrl", avatar);
// 更新缓存用户头像
loginUser.getUser().setAvatar(avatar);
tokenService.setLoginUser(loginUser);
return ajax;
}
}
return AjaxResult.error("上传图片异常,请联系管理员");
}
主要看 upload()
方法,这里代码太长就不贴出来了,大家自行点进去看;主要包括如获取文件名称、对文件大小和类型进行校验以及对文件保存的路径名称进行设置
通知公告部分
上面是头像上传的,接下来介绍通知公告的;我们打开 web.controller.common.CommonController
@PostMapping("/common/upload")
public AjaxResult uploadFile(MultipartFile file) throws Exception
{
try
{
// 上传文件路径
String filePath = RuoYiConfig.getUploadPath();
// 上传并返回新文件名称
String fileName = FileUploadUtils.upload(filePath, file);
String url = serverConfig.getUrl() + fileName;
AjaxResult ajax = AjaxResult.success();
ajax.put("fileName", fileName);
ajax.put("url", url);
return ajax;
}
catch (Exception e)
{
return AjaxResult.error(e.getMessage());
}
}
可以看到,方法大致和头像上传是一致的,需要注意的是,他所上传的路径和头像上传的路径不同
房屋内
我们所上传的文件会保存在本地磁盘,我们可以在浏览器直接输入 localhost/dev-api/profile/文件路径/文件名称.文件类型
来访问,这个在 framework.config.ResourcesConfig 中进行了设置,系统也可以根据这个地址对文件进行访问
/** 本地文件上传路径 */
registry.addResourceHandler(Constants.RESOURCE_PREFIX + "/**").addResourceLocations("file:" + RuoYiConfig.getProfile() + "/");
同时还会把这个路径和我们本地磁盘存储该文件的路径进行映射,所以我们可以在浏览器直接输入文件在磁盘中的路径来访问,这里就不进行演示了
7. 文件下载
本系统还提供了下载功能,那如何下载呢?我们可以在浏览器地址栏输入 localhost/dev-api/common/download/resource?name=/profile/文件路径/文件名称.文件类型
来下载
7.1 文件下载方法详解
打开 web.controller.common.CommonController ,找到文件下载方法,具体作用见注释
@GetMapping("/common/download/resource")
public void resourceDownload(String resource, HttpServletRequest request, HttpServletResponse response)
throws Exception
{
try
{
if (!FileUtils.checkAllowDownload(resource))
{
throw new Exception(StringUtils.format("资源文件({})非法,不允许下载。 ", resource));
}
// 本地资源路径
String localPath = RuoYiConfig.getProfile();
// 数据库资源地址,将本机地址和文件地址进行拼接
String downloadPath = localPath + StringUtils.substringAfter(resource, Constants.RESOURCE_PREFIX);
// 下载名称,拿到最后一个 / 后面的字符串作文下载名
String downloadName = StringUtils.substringAfterLast(downloadPath, "/");
//文件流下载
response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
FileUtils.setAttachmentResponseHeader(response, downloadName);
FileUtils.writeBytes(downloadPath, response.getOutputStream());
}
catch (Exception e)
{
log.error("下载文件失败", e);
}
}
后端部分暂时就讲完啦,撒花~~~~接下来的学习笔记主要内容是关于前端的,嘿嘿嘿