Spring cloud微服務安全實戰-3-9API安全機制之審計日志


首先說一下審計日志的處理。審計日志處理的位置,應該是在認證之后,授權之前。因為只有你在認證之后,你才能知道這個請求到底是誰發出來的,誰在做這個事情。在這個授權之前,這樣的話那些被拒絕掉的請求。在響應的時候你才可以把他記下來。 

日志一定要持久化,可以把它存到數據庫里,也可以把它寫到文件里。

怎么保證過濾器按照順序執行的,我們在做流控和認證的過濾器的時候,實際上,並沒有指定的順序。那么現在到底是流控先執行 還是認證先執行?

在流控的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

結束


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM