美文网首页Spring Cloud
五、API安全机制-审计(日志)

五、API安全机制-审计(日志)

作者: 紫荆秋雪_文 | 来源:发表于2020-05-13 00:22 被阅读0次

    源码下载

    一、API安全机制-审计(日志) API安全机制.png

    审计日志应该在认证处理之后,这样我们就知道谁在发送请求,响应也应该被记录,尤其是请求被拒绝的时候

    二、使用@Order注解来控制过滤器的执行顺序 过滤器顺气.png

    • 这个顺序是不符合安全机制的,所以要使用@Order来控制过滤器调用顺序
    /**
     * 限流过滤器
     */
    @Slf4j
    @Order(1)
    @Component
    public class RateLimitFilter extends OncePerRequestFilter {
    
        /**
         * 限制1秒只能通过1个请求
         */
        private RateLimiter rateLimiter = RateLimiter.create(1);
    
        protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
    
            logger.info("1-限流过滤器");
            if (this.rateLimiter.tryAcquire()) {
                filterChain.doFilter(request, response);
            }
            else {
                // 限流
                response.setStatus(HttpStatus.TOO_MANY_REQUESTS.value());
                response.getWriter().write("Too Many Request !!!");
                response.getWriter().flush();
                return;
            }
        }
    }
    
    /**
     * 请求认证过滤器
     */
    @Slf4j
    @Order(2)
    @Component
    public class AuthenticationFilter extends OncePerRequestFilter {
    
        @Autowired
        private IRavenUserRepository userRepository;
    
        protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
            logger.info("2-认证过滤器");
            String header = request.getHeader("Authorization");
    
            if (StringUtils.isNotBlank(header)) {
                String token64 = StringUtils.substringAfter(header, "Basic ");
                String token = new String(Base64Utils.decodeFromString(token64));
                String[] items = StringUtils.splitByWholeSeparatorPreserveAllTokens(token, ":");
    
                if (items.length != 2) {
                    log.info("用户身份认证错误!!!");
                    throw new RuntimeException("用户身份认证错误!!!");
                }
                String username = items[0];
                String password = items[1];
                RavenUser user = this.userRepository.findByName(username);
                if (user != null && user.getPassword().equals(password)) {
                    request.setAttribute("user", user);
                }
            }
            filterChain.doFilter(request, response);
        }
    }
    
    • 打印结果
    com.raven.security.user.web.filter.RateLimitFilter - 1-限流过滤器
    c.r.security.user.web.filter.AuthenticationFilter - 2-认证过滤器
    

    三、Filter、Interceptor、ControllerAdvice、AOP执行顺序 执行顺序.png

    • 定义日志拦截器来拦截请求URL和响应
    • RavenAuditLog
    /**
     * 审核日志
     */
    @Data
    @Entity
    @EntityListeners(AuditingEntityListener.class)
    @Table(name = "t_auditLog")
    public class RavenAuditLog {
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Long id;
    
        private String username;
    
        private String method;
    
        private String path;
    
        private Integer status;
    
        @Temporal(TemporalType.TIMESTAMP)
        @CreatedDate
        private Date createdTime;
    
        @Temporal(TemporalType.TIMESTAMP)
        @LastModifiedDate
        private Date modifyTime;
    }
    
    • RavenAuditLogInterceptor日志拦截器
    /**
     * 日志拦截器
     */
    @Component
    public class RavenAuditLogInterceptor extends HandlerInterceptorAdapter {
    
        @Autowired
        private IRavenAuditLogService auditLogService;
    
    
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    
            RavenAuditLog log = new RavenAuditLog();
            log.setMethod(request.getMethod());
            log.setPath(request.getRequestURI());
    
            RavenUser user = (RavenUser) request.getAttribute("user");
            if (user != null) {
                log.setUsername(user.getUsername());
            }
    
            // 保存日志
            this.auditLogService.save(log);
    
            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");
            RavenAuditLog auditLog = this.auditLogService.selectById(auditLogId);
            auditLog.setStatus(response.getStatus());
            // 保存日志
            this.auditLogService.save(auditLog);
        }
    }
    
    • RavenInterceptorConfig拦截器生效配置类
    /**
     * 拦截器配置类
     */
    @Configuration
    @EnableJpaAuditing
    public class RavenInterceptorConfig implements WebMvcConfigurer {
    
        @Autowired
        private RavenAuditLogInterceptor auditLogInterceptor;
    
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            /**
             * registry.addInterceptor(this.auditLogInterceptor).addPathPatterns("/user");
             * 拦截器指定拦截的url
             */
            registry.addInterceptor(this.auditLogInterceptor);
        }
    }
    
    • 数据日志记录 日志记录.png

      在请求url /users/2时,请求错误本应该只返回状态码500,但是确先返回状态码200,然后又跳转到/error返回状态码500,这样为分析错误会有很大难度,期望是请求/users/2时就直接返回状态码500

    • 解决方案,通过统一处理错误处理器来处理

    /**
     * 处理全局异常
     */
    @RestControllerAdvice
    public class ErrorHandler {
    
        @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
        @ExceptionHandler(Exception.class)
        public Map<String, Object>handle(Exception e) {
            HashMap<String, Object> map = new HashMap<>();
            map.put("message", e.getMessage());
            map.put("time", new Date().getTime());
            return map;
        }
    
    }
    

    相关文章

      网友评论

        本文标题:五、API安全机制-审计(日志)

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