審計日志
定義:誰,在什么時間,干了什么事。
位置:認證之后,授權之前。
這樣就知道是誰在訪問,拒絕掉的訪問也能被記錄。如果放在認證之前,那么就不知道是誰在訪問;如果放在授權之后,就沒辦法記錄被拒絕的訪問。
存儲:審計日志一定要持久化,記在數據庫里或者是文件,放在內存會丟失。(輸出到公司的日志服務)
怎么記:請求進來的時候記錄一次,請求出去的時候,更新日志。
如果只在請求進來的時候記,那么請求的成功與否是不知道的。如果只在請求返回的時候記,那么如果一個請求把你的系統搞掛了,也沒有記,是不知道誰搞掛的。
技術選擇:過濾器 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
+++++++++++++++++++++分割線++++++++++++++++++++++++++++++
小結
本篇說了什么是審計,審計在代碼中的位置,以及用攔截器來實現審計