首先說一下審計日志的處理。審計日志處理的位置,應該是在認證之后,授權之前。因為只有你在認證之后,你才能知道這個請求到底是誰發出來的,誰在做這個事情。在這個授權之前,這樣的話那些被拒絕掉的請求。在響應的時候你才可以把他記下來。
日志一定要持久化,可以把它存到數據庫里,也可以把它寫到文件里。
怎么保證過濾器按照順序執行的,我們在做流控和認證的過濾器的時候,實際上,並沒有指定的順序。那么現在到底是流控先執行 還是認證先執行?
在流控的Filter RateLimitFilter先打印一個1
BasicAuthecationFilter:在認證的Filter里面 我們再打一個2
發送請求 發現先輸出了2 后輸出了1。說明先走的認證 再走的流控
注意事項:
輸出后,記得執行:filterChain.doFilter(request,response); 讓代碼繼續往下走。下面是我在IDEA里面的截圖
調整Filter的執行順序
認證在第二個 就寫個@Order(2)
重啟服務進行測試
審計日志-簡單實現
審計日志不能用filter來做了。因為審計日志我們要做兩個動作。第一個是在請求進來后,我們要做一個動作。然后在請求出去之后,要把這個日志更新一下,所以進和出都要做事。如果只在請求進來記錄 實際上不知道最終這個請求最終是成功了還是失敗了。這樣這個日志記錄不完整。
如果用Filter來做,它只有一個doFilterInternal這么個方法,它是標准的Filter方法的實現,並沒有明確的區分在請求進來執行還是請求出去的時候執行。
我們使用SpringBoot提供的Interceptor。
ControllerAdvice一般是用來做全局的異常處理。
新建審計日志類
AuditLog
指定createTime和modifyTime存成時間戳 。
注意:@Temporal和TemporalType.TIMESTAMP都是import javax.persistence.包下的。
加上@CreateDate和@LastModifiedDate這兩個注解,我們在使用Repository的save方法的時候,它會自動判斷你是創建還是修改並給這兩個日期賦值。
org.springframework.data.annotation.LastModifiedDate包下的
package com.imooc.security.log;
import lombok.Data;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import javax.persistence.*;
import java.util.Date;
@Entity
@Data
@EntityListeners(AuditingEntityListener.class)
public class AuditLog {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String method;
private String path;
private Integer status;
private String username;
@Temporal(TemporalType.TIMESTAMP)
@CreatedDate
private Date createdTime;
@Temporal(TemporalType.TIMESTAMP)
@LastModifiedDate
private Date modifyTime;
}
審計日志Repository
IDEA里面截圖
package com.imooc.security.log; import org.springframework.data.jpa.repository.JpaSpecificationExecutor; import org.springframework.data.repository.CrudRepository; public interface AuditLogRepository extends JpaSpecificationExecutor<AuditLog>, CrudRepository<AuditLog,Long> { }
創建Interceptor
繼承的是,HandlerInterceptorAdpater 處理之前和處理之后,主要是要實現這兩個方法。
這里只需要preHandler和afterCompetion這兩個方法即可。其他的方法刪除。
聲明成一個Spring組件,使用@Component注解。
注入AuditLogRepository
@Autowired
private AuditLogRepository auditLogRepository;
最終返回true、返回false 的請求就不會被執行。
因為下面要用,所以這里,設置id。
afterCompletion 不管成功還是失敗,都會去調用
、
package com.imooc.security.log; import com.imooc.security.user.User; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.util.Optional; public class AuditLogInterceptor extends HandlerInterceptorAdapter { @Autowired private AuditLogRepository auditLogRepository; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { AuditLog log=new AuditLog(); log.setMethod(request.getMethod()); log.setPath(request.getRequestURI()); User user=(User)request.getAttribute("user"); if(user!=null){ log.setUsername(user.getUsername()); } auditLogRepository.save(log); //下面要用 所以這里加一個attribute的屬性 request.setAttribute("auditLogId",log.getId()); return true; } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { Long auditLogId = (Long) request.getAttribute("auditLogId"); AuditLog log = auditLogRepository.findById(auditLogId).get(); log.setStatus(response.getStatus()); auditLogRepository.save(log); } }
讓攔截器作用,還需做一些配置
要實現接口WebMvcConfigurer
注入auditLogInterceptor,然后加入到Spring里面。 攔截去是根據添加的來執行的 先add的就先執行。
還可以指定攔截器,只針對某些請求有效。這里我們不配置的話就是針對所有的請求,都走這個攔截器。
JPA有一個自動支持審計的功能
這個是個總開關,把JPA的審計打開。@EnableJpaAuditing
然后在需要做審計的類上面加上注解@EntityListeners(AuditingEntityListener.class) 把兼容器注入進來。
自己遇到的錯誤
上面報了一堆的錯誤,
錯誤都不是很明顯,比較明顯的錯誤最下面的這個錯誤。
Caused by: org.hibernate.AnnotationException: No identifier specified for entity: com.imooc.security.log.AuditLog
百度搜索出來錯誤。Caused by: org.hibernate.AnnotationException: No identifier specified for entity
No identifier specified for entity:沒有給實體設置唯一的標識
實體類沒有唯一的標識,說明我們的這個@Id注解沒有起作用。@Id不是這個org.springframework.data.annotation.Id包下的。
@Id應該是這個包下的 javax.persistence
修改完成,順利啟動,。控制台也輸出了。創建表的sql語句
Hibernate: create table audit_log (id bigint not null auto_increment, created_time datetime, method varchar(255), modify_time datetime, path varchar(255), status integer, username varchar(255), primary key (id)) engine=InnoDB
數據庫內看到創建好的表
運行測試
輸出的日志里面,輸出了創建表的語句
訪問一個id為13的 再訪問id為12的。id為12的編號用戶在數據庫內是不存在的
創建時間和修改時間 都是JPA自動幫我們填上的
錯誤的請求 id為12的用戶在數據庫內是不存在的,先保存一個200的記錄,又保存了一個500的記錄。這是Spring默認的一個處理機制。有了異常后跳到一個error的路徑上。錯誤的頁面的狀態碼是500.這個跳轉 實際上是不利於我們分析問題的
處理配置
創建一個ErrorHandler
這里我們用到了 Controller的增強。Advice。 由這里來接管我們的錯誤處理,不再走Spring默認的處理、
一旦報錯就把狀態改成500內部服務器錯誤。
把錯誤信息和當前時間 返回回去。這樣我就有了一個公共的異常處理類。
package com.imooc.security.config; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseStatus; import java.util.Date; import java.util.HashMap; import java.util.Map; @ControllerAdvice public class ErrorHandler { @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) @ExceptionHandler(Exception.class) public Map<String,Object> handle(Exception ex){ Map<String,Object> info=new HashMap<>(); info.put("message",ex.getMessage()); info.put("item",new Date().getTime()); return info; } }
再次運行程序測試。
這次的信息。,是time和message。這是我們自己在異常類里面定義的兩個字段、。
這次把請求錯誤記錄了下來 並且一條記錄。
審計-username
審計的時候記錄當前用戶是誰。
創建人
最后修改人
單寫一個注解是不夠的,還需要一個Bean來幫它知道當前的用戶是誰。
用@Bean注解,然后返回一個AuditorAware的泛型 里面是String類型。然后里面寫一個匿名的實現。
這樣就拿到了一個AuditorAware。 日常開發中是把用戶信息存儲在Redis中的。這里為了簡單的實現。
這里方法會告訴JPA當前的用戶是誰。用戶是誰具體的邏輯要自己去實現。這里只是簡單的寫死了是jojo
然后過濾器的這段。拿到用戶的請求這段代碼就不再需要了。
直接save就可以了
save的時候看到類上有@CreateBy的注解
就會調用這個Bean類的getCurrentAuditor方法,。然后拿到返回來的String。把它設置到username上。
運行測試
username就是寫死的值。
以上就是JPA對審計的支持,。
MySql時間晚8個小時的問題
主要是鏈接字符串的時區的問題
https://blog.csdn.net/qq784515681/article/details/79658979