shiro实现请求拦截并打印日志

传统方式实现HandlerInterceptor

这里代码不但实现了token的认证拦截,也在afterCompletion实现了接口请求日志打印

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.linlinjava.litemall.core.annotation.VisitorAccessible;
import org.linlinjava.litemall.core.enums.CommonCodeEnum;
import org.linlinjava.litemall.core.exception.ApplicationException;
import org.linlinjava.litemall.core.util.IpUtil;
import org.linlinjava.litemall.core.util.JWTVerifierUtil;
import org.linlinjava.litemall.db.domain.LitemallUser;
import org.linlinjava.litemall.db.domain.UserVo;
import org.linlinjava.litemall.db.service.LitemallUserService;
import org.linlinjava.litemall.wx.global.GlobalHolder;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.NamedThreadLocal;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Component
public class InitInterceptor implements HandlerInterceptor {

    private static final Log LOGGER = LogFactory.getLog(InitInterceptor.class);

    @Autowired
    private LitemallUserService userService;

    private NamedThreadLocal<Long> startTimeThreadLocal =
            new NamedThreadLocal<Long>("StopWatch-StartTime");


    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String ip = IpUtil.getIpAddr(request);
        LOGGER.info("ip:"+ ip +"request:"+ String.format("%s consume  millis", request.getRequestURI()));
        response.setHeader("Access-Control-Allow-Origin", request.getHeader("Origin"));
        response.setHeader("Access-Control-Allow-Credentials", "true");
        response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
        response.setHeader("Access-Control-Max-Age", "3600");
        response.setHeader("Access-Control-Allow-Headers", "Content-Type,Accept,X-Requested-With,token");

        long beginTime = System.currentTimeMillis();//1、开始时间
        startTimeThreadLocal.set(beginTime);
        if (request.getMethod().toUpperCase().equals("OPTIONS") || request.getServletPath().contains("ok") || request.getServletPath().contains("api/user/login")) {
            return true;
        }
        // 处理用户

        String token = request.getHeader("token");
        if (StringUtils.isNotEmpty(token)) {
            String userCode = "";
            try {
                userCode = (String) JWTVerifierUtil.verify(token).get(2);
                if (StringUtils.isEmpty(userCode)) {
                    throw new ApplicationException(CommonCodeEnum.NO_ACCESS_RIGHT);
                }
            } catch (Exception e) {
                throw new ApplicationException(CommonCodeEnum.NO_ACCESS_RIGHT);
            }

            LitemallUser userInfoEntity = userService.findById(Integer.valueOf(userCode));
            if (userInfoEntity == null) {
                throw new ApplicationException(CommonCodeEnum.NO_ACCESS_RIGHT);
            }

            UserVo userInfo = new UserVo();
            BeanUtils.copyProperties(userInfoEntity, userInfo);
            GlobalHolder.setCurrentLoginUser(userInfo);
        } else {
            HandlerMethod handlerMethod = (HandlerMethod) handler;
            VisitorAccessible annotation = handlerMethod.getMethodAnnotation(VisitorAccessible.class);
            if (annotation == null) {
                throw new ApplicationException(CommonCodeEnum.NO_ACCESS_RIGHT);
            }
        }
        return true;
    }

    /**
     * 在业务处理器处理请求执行完成后,生成视图之前执行的动作 可在modelAndView中加入数据,比如当前时间
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
    }

    /**
     * 在DispatcherServlet完全处理完请求后被调用,可用于清理资源等
     * 当有拦截器抛出异常时,会从当前拦截器往回执行所有的拦截器的afterCompletion() 处理写session工作
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        try {
            long endTime = System.currentTimeMillis();//2、结束时间
            long beginTime = startTimeThreadLocal.get();//得到线程绑定的局部变量(开始时间)
            long consumeTime = endTime - beginTime;//3、消耗的时间
            String ip = IpUtil.getIpAddr(request);
            LOGGER.info("ip:"+ ip +"request:"+ String.format("%s consume %d millis", request.getRequestURI(), consumeTime));
        } catch (Exception e) {
            e.printStackTrace();
        }
        GlobalHolder.removeCurrentLoginUser();

    }

}

shiro中AdviceFilter实现请求日志打印

AdviceFilter 提供了 AOP 的功能,其实现和 SpringMVC 中的 Interceptor 思想一样

public class MyAdviceFilter extends AdviceFilter {
    @Override
    protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
        System.out.println("====预处理/前置处理");
        return true;//返回 false 将中断后续拦截器链的执行
    }
    @Override
    protected void postHandle(ServletRequest request, ServletResponse response) throws Exception {
        System.out.println("====后处理/后置返回处理");
    }
    @Override
    public void afterCompletion(ServletRequest request, ServletResponse response, Exception exception) throws Exception {
        System.out.println("====完成处理/后置最终处理");
    }
}

preHandle:进行请求的预处理,然后根据返回值决定是否继续处理(true:继续过滤器链);可以通过它实现权限控制;
postHandle:执行完拦截器链之后正常返回后执行;
afterCompletion:不管最后有没有异常,afterCompletion 都会执行,完成如清理资源功能。

这里只要在afterCompletion处打印请求日志即可,代码遗留的问题在于如何打印post,put请求的body,先看看完整代码:

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.shiro.web.servlet.AdviceFilter;
import org.springframework.core.NamedThreadLocal;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;


/**
 * @author Cheertan
 * @date 2020-08-11
 */
public class MyAdviceFilter extends AdviceFilter {
    private static final Log LOGGER = LogFactory.getLog(MyAdviceFilter.class);
    private NamedThreadLocal<Long> startTimeThreadLocal =
            new NamedThreadLocal<Long>("StopWatch-StartTime");
    @Override
    protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
        return true;
        //返回 false 将中断后续拦截器链的执行
    }
    @Override
    protected void postHandle(ServletRequest request, ServletResponse response) throws Exception {

    }
    @Override
    public void afterCompletion(ServletRequest request, ServletResponse response, Exception exception) throws Exception {
        try {
            long endTime = System.currentTimeMillis();
            long consumeTime = endTime;
            String ip = (request.getRemoteHost());
            if (request instanceof HttpServletRequest) {
                String url = ((HttpServletRequest)request).getRequestURL().toString();
                String queryString = ((HttpServletRequest)request).getQueryString();
                //String body = IOUtils.toString(((HttpServletRequest)request).getReader());
                System.out.println(((HttpServletRequest)request).getMethod());
                if (((HttpServletRequest)request).getMethod().equalsIgnoreCase("GET")) {
                    LOGGER.info("ip:" + ip + " request: " + url + "?" + queryString);
                } else {
                    String body = request.getInputStream().toString();
   //body并不能打印出来,待解决,本打算用body = request.getReader().lines().collect(Collectors.joining(System.lineSeparator()));
   //但是会报错
                    LOGGER.info("ip:" + ip + " request: " + url+" body: "+body);
                }
            }

        } catch (Exception e) {
            e.printStackTrace();
        }

    }
}

在shiro配置类里加载自定义的MyAdviceFilter
关键就这段代码:

 Map<String, Filter> filters = new LinkedHashMap<String, Filter>();
        filters.put("myAdviceFilter", myAdviceFilter());
        shiroFilterFactoryBean.setFilters(filters);
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.linlinjava.litemall.admin.shiro.AdminAuthorizingRealm;
import org.linlinjava.litemall.admin.shiro.AdminWebSessionManager;
import org.linlinjava.litemall.admin.shiro.MyAdviceFilter;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;

import javax.servlet.Filter;
import java.util.LinkedHashMap;
import java.util.Map;

@Configuration
public class ShiroConfig {

    @Bean
    public Realm realm() {
        return new AdminAuthorizingRealm();
    }

    @Bean
    public ShiroLogFilter shiroLogFilter(){
        return new ShiroLogFilter();
    }
    @Bean
    public MyAdviceFilter myAdviceFilter() {
        return new MyAdviceFilter();
    }

    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);

        Map<String, Filter> filters = new LinkedHashMap<String, Filter>();
        filters.put("myAdviceFilter", myAdviceFilter());
        shiroFilterFactoryBean.setFilters(filters);


        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
        filterChainDefinitionMap.put("/admin/auth/login", "anon");
        filterChainDefinitionMap.put("/admin/oss/upload", "anon");
        filterChainDefinitionMap.put("/admin/auth/401", "anon");
        filterChainDefinitionMap.put("/admin/auth/index", "anon");
        filterChainDefinitionMap.put("/admin/auth/403", "anon");
        filterChainDefinitionMap.put("/admin/index/index", "anon");

        filterChainDefinitionMap.put("/admin/**", "authc");
        shiroFilterFactoryBean.setLoginUrl("/admin/auth/401");
        shiroFilterFactoryBean.setSuccessUrl("/admin/auth/index");
        shiroFilterFactoryBean.setUnauthorizedUrl("/admin/auth/403");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);


        return shiroFilterFactoryBean;
    }


    @Bean
    public SessionManager sessionManager() {

        return new AdminWebSessionManager();
    }

    @Bean
    public DefaultWebSecurityManager defaultWebSecurityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(realm());
        securityManager.setSessionManager(sessionManager());
        return securityManager;
    }

    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor =
                new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }

    @Bean
    @DependsOn("lifecycleBeanPostProcessor")
    public static DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator creator = new DefaultAdvisorAutoProxyCreator();
        creator.setProxyTargetClass(true);
        return creator;
    }
}

遗留问题

在MyAdviceFilter中使用 String body = request.getReader().lines().collect(Collectors.joining(System.lineSeparator()));

ava.lang.IllegalStateException: getReader() has already been called for this request

shiro教程

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值