审计日志
定义:谁,在什么时间,干了什么事。
位置:认证之后,授权之前。
这样就知道是谁在访问,拒绝掉的访问也能被记录。如果放在认证之前,那么就不知道是谁在访问;如果放在授权之后,就没办法记录被拒绝的访问。
存储:审计日志一定要持久化,记在数据库里或者是文件,放在内存会丢失。(输出到公司的日志服务)
怎么记:请求进来的时候记录一次,请求出去的时候,更新日志。
如果只在请求进来的时候记,那么请求的成功与否是不知道的。如果只在请求返回的时候记,那么如果一个请求把你的系统搞挂了,也没有记,是不知道谁搞挂的。
技术选择:过滤器 VS 拦截器 VS ControllerAdvice VS AOP
过滤器,不好分辨是请求过来执行的还是请求出去执行的; ControllerAdvice-做全局异常处理 ;AOP -不说了
就用拦截器,拦截器在过滤器之后执行。
限流过滤器用 @Order注解,控制在第一个执行
认证过滤器,排在老二位置:
再写个审计拦截器,就是过滤器之后执行了
实现
数据库
实体类:
/** * <p> * 审计日志 * </p> * * @author 李浩洋 * @since 2019-10-27 */ @Data public class AuditLog implements Serializable { private static final long serialVersionUID = 1L; private Long id; /** * http方法 */ private String method; /** * 请求路径 */ private String path; /** * http状态码 */ private Integer status; /** * 请求用户名 */ private String username; /** * 创建时间 */ private Date createTime; /** * 修改时间 */ private Date updateTime; }
审计拦截器:
package com.nb.security.interceptor; import com.nb.security.entity.AuditLog; import com.nb.security.entity.User; import com.nb.security.service.IAuditLogService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.lang.Nullable; import org.springframework.stereotype.Component; import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.util.Date; /** * 审计日志拦截器 * 拦截流程 * 流控 -- 认证 --审计 -- 授权 -- 业务 * 审计要在进入接口之前,insert 数据库(实际可能发送到专门的日志服务器),执行完后 update,过滤器不便于判断拦截之前、之后,故用拦截器 */ @Component public class AuditLogInterceptor extends HandlerInterceptorAdapter { @Autowired private IAuditLogService auditLogService; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { AuditLog log = new AuditLog(); log.setMethod(request.getMethod()); log.setPath(request.getRequestURI()); log.setCreateTime(new Date()); User user = (User) request.getAttribute("user"); if (user != null) { user.setUsername(user.getUsername()); } auditLogService.save(log); //将审计日志的id传给request,以便于请求处理完成后更新审计日志 request.setAttribute("auditLogId", log.getId()); return super.preHandle(request, response, handler); } /** * 请求处理成功失败,都更新审计日志 * * @param request * @param response * @param handler * @param ex * @throws Exception */ @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception { //审计日志id Long auditLogId = (Long) request.getAttribute("auditLogId"); AuditLog log = auditLogService.getById(auditLogId); log.setStatus(response.getStatus()); log.setUpdateTime(new Date()); auditLogService.updateById(log); super.afterCompletion(request, response, handler, ex); } }
拦截器配置:
@Configuration public class SecurityConfig implements WebMvcConfigurer { //审计日志 @Autowired private AuditLogInterceptor auditLogInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(auditLogInterceptor);//.addPathPatterns();//先add的先执行,默认所有请求都拦截 } }
用Postman来一个正确的请求:
数据库新增了一条数据
控制台可以看到执行顺序,就是想要的结果。
错误的访问:
数据库insert了两条数据,一个是我的请求 /users/12 ,另一个 /error 是SpringBoot抛出异常后,会跳到一个/error 的路径,
新建一个异常处理器:
再发一个失败的请求:
数据库就不会再有 /error 请求了。
代码:https://github.com/lhy1234/springcloud-security/tree/master/nb-user-api
+++++++++++++++++++++分割线++++++++++++++++++++++++++++++
小结
本篇说了什么是审计,审计在代码中的位置,以及用拦截器来实现审计