1、审计所在安全链路的位置,为什么


如图所示,审计应该做在认证之后,授权之前。因为只有在认证之后,我们在记录日志的时候,在知道请求是那个用户发过来的;做在授权之前,哪些请求被拒绝了,在响应的时候,也可以把它记录下来。如果放到授权之后 ,那么被拒绝的请求就不能记录了。
审计日志一定要持久化,方便我们对问题的追溯,可以把它放到数据库中,也可以写到磁盘中。实际工作中,一般会发送到公司统一的日志服务上,由日志服务来存储。
2、审计采用的组件,及安全链路顺序的保障
首先,我们来明确一下各组件在请求中的执行顺序,如下图,依次是 Filter -> Interceptor -> ControllerAdvice -> AOP -> Controller


对于Filter之间,我们可以使用@Order注解来确定执行顺序;对于Interceptor之间根据注册的先后顺序执行。这里我们的审计功能选择Filter和Interceptor都可以,根据自己的喜好即可。
3、实现审计功能
/**
* 审计日志
*
* @author caofanqi
* @date 2020/1/28 22:55
*/
@Data
@Entity
@Table(name = "audit_log")
@EntityListeners(value = AuditingEntityListener.class)
public class AuditLogDO {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String httpMethod;
private String path;
private Integer httpStatus;
@CreatedBy
private String username;
@CreatedDate
private LocalDateTime requestTime;
@LastModifiedDate
private LocalDateTime responseTime;
private String errorMessage;
}
/**
* 审计日志Repository
* @author caofanqi
* @date 2020/1/28 23:13
*/
public interface AuditLogRepository extends JpaRepositoryImplementation<AuditLogDO,Long> {
}
3.2、开启JPA审计功能配置
/**
* JPA相关配置
*
* @author caofanqi
* @date 2020/1/29 1:13
*/
@Configuration
@EnableJpaAuditing
public class JpaConfig {
/**
* 获取当前登陆用户
*/
@Bean
public AuditorAware<String> auditorAware() {
return () -> {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
UserDO user = (UserDO) request.getAttribute("user");
if (user != null) {
return Optional.of(user.getUsername());
} else {
return Optional.of("anonymous");
}
};
}
}
此处不懂的,可以去看我写的JPA文章: https://www.cnblogs.com/caofanqi/p/11996718.html
3.3、基于Filter实现审计功能 AuditLogFilter,流控过滤器设置@Order(1)、认证过滤器设置@Order(2)
/**
* 审计过滤器
*
* @author caofanqi
* @date 2020/1/29 0:08
*/
@Slf4j
@Order(3)
@Component
public class AuditLogFilter extends OncePerRequestFilter {
@Resource
private AuditLogRepository auditLogRepository;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
log.info("++++++3、审计++++++");
AuditLogDO auditLogDO = new AuditLogDO();
auditLogDO.setHttpMethod(request.getMethod());
auditLogDO.setPath(request.getRequestURI());
//放入持久化上下文中,供异常处理使用
auditLogRepository.save(auditLogDO);
request.setAttribute("auditLogId",auditLogDO.getId());
// 执行请求
filterChain.doFilter(request,response);
// 执行完成,从持久化上下文中获取,并记录响应信息
auditLogDO = auditLogRepository.findById(auditLogDO.getId()).get();
auditLogDO.setHttpStatus(response.getStatus());
auditLogRepository.save(auditLogDO);
}
}
3.4、异常处理ControllerAdvice
/**
*
* @param e 系统异常
* @return 系统异常及时间
*/
@ExceptionHandler
@ResponseStatus(code = HttpStatus.INTERNAL_SERVER_ERROR)
public Map<String,Object> exceptionHandler(Exception e){
/*
* 如果有异常的化,将审计日志取出,记录异常信息
*/
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
Long auditLogId = (Long) request.getAttribute("auditLogId");
AuditLogDO auditLogDO = auditLogRepository.findById(auditLogId).orElse(new AuditLogDO());
auditLogDO.setErrorMessage(e.getMessage());
auditLogRepository.save(auditLogDO);
Map<String, Object> info = Maps.newHashMap();
info.put("message", e.getMessage());
info.put("time", LocalDateTime.now());
return info;
}
3.5、启动项目,进行测试,访问http://127.0.0.1:9090/users/40,并填写正确的用户名密码

执行顺序如下

数据库审计日志表

准备一个有错误的方法
@DeleteMapping("/{id}")
public void delete(@PathVariable Long id){
int i = 1 / 0 ;
}
测试如下:

数据库审计日志表

3.6、如果想基于Interceptors来实现,做如下修改
3.6.1、AuditLogInterceptor拦截器
/**
* 基于Interceptor的审计拦截器 ,与AuditLogFilter同时只能使用一个
*
* @author caofanqi
* @date 2020/1/28 23:12
*/
@Slf4j
@Component
public class AuditLogInterceptor extends HandlerInterceptorAdapter {
@Resource
private AuditLogRepository auditLogRepository;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
log.info("++++++3、审计++++++");
AuditLogDO auditLogDO = new AuditLogDO();
auditLogDO.setHttpMethod(request.getMethod());
auditLogDO.setPath(request.getRequestURI());
auditLogRepository.save(auditLogDO);
request.setAttribute("auditLogId",auditLogDO.getId());
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,Exception ex){
Long auditLogId = (Long) request.getAttribute("auditLogId");
AuditLogDO auditLogDO = auditLogRepository.findById(auditLogId).orElse(new AuditLogDO());
auditLogDO.setHttpStatus(response.getStatus());
auditLogRepository.save(auditLogDO);
}
}
3.6.2、注册拦截器
/**
* web配置类
*
* @author caofanqi
* @date 2020/1/28 22:32
*/
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Resource
private AuditLogInterceptor auditLogInterceptor;
/**
* 注册拦截器
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(auditLogInterceptor);
}
}
3.6.3、进行3.5的测试效果相同
项目源码:https://github.com/caofanqi/study-security/tree/dev-auditing
来源:https://www.cnblogs.com/caofanqi/p/12239473.html