美文网首页
API 安全机制 | 审计日志

API 安全机制 | 审计日志

作者: 乌鲁木齐001号程序员 | 来源:发表于2020-07-12 16:26 被阅读0次

    审计日志

    • 审计在认证之后,授权之前;
    • 在认证之后,才知道是谁;
    • 在授权之前,才能记录下被授权机制拒绝的请求;
    • 审计日志一定要持久化,数据库或文件中,一般会直接发到统一的的日志服务上,由日志服务去记日志;
    • 审计日志要做两次,一次是在进入的时候,一次是在出去的时候;

    Spring 的拦截机制

    Spring 的拦截机制.png
    • Filter 不是 Spring 的机制,而是 Servlet 规范中定义的;
    • ControllerAdvice 一般做全局的异常处理;

    Filter 和 Intercepor 的区别

    • Filter 对请求进入业务逻辑之前和之后的拦截是在一个方法 doFilter 中完成的,如果下游的业务方法在执行的时候出现异常,在 Filter 中是拦截不到的;
    • Interceptor 对请求进入业务逻辑之前和之后的拦截是在多个方法 preHandle、postHandle、afterCompletion 中完成的,如果在下游的业务方法在执行的时候出现异常,可以在 aflterCompletion 中拦截到;
    • 所有的 Filter 都在 Interceptor 之前执行;

    审计日志 | 实现思路

    • 先确定审计对象,也就是表结构,因为这里的方案是将审计日志记录在数据库中;
    • 再写一个拦截器,在请求的前后,向审计日志保存在数据库中;
    • 完了把拦截器添加到 Spring 的上下文中;
    • 在配置类上启用 @EnableJpaAuditing 注解,在审计类上启用 @CreatedBy 注解,并创建一个为 @CreatedBy 服务的 Bean,用来获取当前修改数据的人;
    • 通过 @RestControllerAdvice 标注的类,解决在业务方法执行异常的时候,会重定向到 /error,从而导致 2 条审计记录的产生;

    审计类

    package com.lixinlei.security.api.entity;
    
    
    import java.util.Date;
    
    import javax.persistence.Entity;
    import javax.persistence.EntityListeners;
    import javax.persistence.GeneratedValue;
    import javax.persistence.GenerationType;
    import javax.persistence.Id;
    import javax.persistence.Temporal;
    import javax.persistence.TemporalType;
    
    import org.springframework.data.annotation.CreatedBy;
    import org.springframework.data.annotation.CreatedDate;
    import org.springframework.data.annotation.LastModifiedDate;
    import org.springframework.data.jpa.domain.support.AuditingEntityListener;
    
    import lombok.Data;
    
    @Entity
    @Data
    @EntityListeners(AuditingEntityListener.class)
    public class AuditLog {
    
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Long id;
    
        @Temporal(TemporalType.TIMESTAMP)
        @CreatedDate
        private Date createdTime;
    
        @Temporal(TemporalType.TIMESTAMP)
        @LastModifiedDate
        private Date modifyTime;
    
        @CreatedBy
        private String username;
    
        private String method;
    
        private String path;
    
        private Integer status;
    
    }
    

    审计日志拦截器

    package com.lixinlei.security.api.interceptor;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    import com.lixinlei.security.api.entity.AuditLog;
    import com.lixinlei.security.api.dao.AuditLogRepository;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
    
    @Component
    public class AuditLogInterceptor extends HandlerInterceptorAdapter {
    
        @Autowired
        private AuditLogRepository auditLogRepository;
    
        /**
         * 在业务方法执行之前执行
         * postHandle 是在业务方法执行成功之后执行
         * @param request
         * @param response
         * @param handler
         * @return 如果返回 false,就可以拒绝请求的执行,直接返回
         * @throws Exception
         */
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
                throws Exception {
    
            System.out.println(3);
    
            AuditLog log = new AuditLog();
            log.setMethod(request.getMethod());
            log.setPath(request.getRequestURI());
    
            // 保存日志
            auditLogRepository.save(log);
    
            request.setAttribute("auditLogId", log.getId());
            return true;
        }
    
        /**
         * 业务方法处理成功与否都会执行
         * @param request
         * @param response
         * @param handler
         * @param ex 如果业务方法处理成功,就不会有这个异常
         * @throws Exception
         */
        @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();
    
            // 就算业务方法执行失败,这里的 status 还是 200,然后会跳到一个 /error 的路径,这个的 status 是 500
            log.setStatus(response.getStatus());
    
            // 更新日志
            auditLogRepository.save(log);
        }
    
    } 
    

    添加拦截器到 Spring 中

    package com.lixinlei.security.api.config;
    
    import java.util.Optional;
    
    import com.lixinlei.security.api.interceptor.AuditLogInterceptor;
    import com.lixinlei.security.api.vo.UserInfo;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.data.domain.AuditorAware;
    import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
    import org.springframework.web.context.request.RequestContextHolder;
    import org.springframework.web.context.request.ServletRequestAttributes;
    import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
    import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
    
    @Configuration
    // 这个注解和 @EntityListeners(AuditingEntityListener.class) 没有的话,@CreatedBy 就不知道把谁注入到其标注的属性中
    @EnableJpaAuditing
    public class SecurityConfig implements WebMvcConfigurer {
    
        @Autowired
        private AuditLogInterceptor auditLogInterceptor;
    
        /**
         * 先添加的拦截器先生效
         * @param registry
         */
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            registry.addInterceptor(auditLogInterceptor);
        }
    
        /**
         * 这个 Bean 是用来让 @CreatedBy 知道是谁修改了数据
         * @CreatedBy 标注的属性的类型是 String,这里 AuditorAware 的泛型就用 String
         * @return
         */
        @Bean
        public AuditorAware<String> auditorAware() {
            return new AuditorAware<String>() {
                @Override
                public Optional<String> getCurrentAuditor() {
                    ServletRequestAttributes servletRequestAttributes
                            = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
                    UserInfo info = (UserInfo)servletRequestAttributes.getRequest().getSession().getAttribute("user");
                    String username = null;
                    if(info != null) {
                        username = info.getUsername();
                    }
                    return Optional.ofNullable(username);
                }
            };
        }
    
    }
    

    @RestControllerAdvice

    package com.lixinlei.security.api.controller.advice;
    
    import java.util.Date;
    import java.util.HashMap;
    import java.util.Map;
    
    import org.springframework.http.HttpStatus;
    import org.springframework.web.bind.annotation.ExceptionHandler;
    import org.springframework.web.bind.annotation.ResponseStatus;
    import org.springframework.web.bind.annotation.RestControllerAdvice;
    
    import lombok.extern.slf4j.Slf4j;
    
    @RestControllerAdvice
    @Slf4j
    public class ErrorHandler {
    
        @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
        @ExceptionHandler(Exception.class)
        public Map<String, Object> handle(Exception ex){
            log.error("system error", ex);
            Map<String, Object> info = new HashMap<>();
            info.put("message", ex.getMessage());
            info.put("time", new Date().getTime());
            return info;
        }
    
    }
    

    相关文章

      网友评论

          本文标题:API 安全机制 | 审计日志

          本文链接:https://www.haomeiwen.com/subject/gubgcktx.html