Shiro支持Servlet3.0的异步请求

Servlet3.0开始支持Request的异步处理,所谓异步处理就是服务端在收到请求之后,并不是直接开始调用业务代码开始存取数据等耗时操作,而是交给后端的线程池来处理,这样请求接收线程就可以继续接收新进来的请求。等线程池处理完之后,通过AsyncContext回调来返回数据给客户端。
异步请求使用场景:

  • 文件上传等耗时请求,可以采用异步处理,不至于因为同时多个人在上传文件而耗光web容器的线程,影响其它快速请求
  • 高并发服务器,对于高并发服务器,异步化必然选择,不但请求进来异步处理。在业务线程池调用其它服务的时候也需要异步,防止耗时操作耗光线程数,我们借用一张图来看一下:


    转载自拿铁咖啡公众号

Spring MVC异步支持

Spring MVC中已经对异步请求做了封装,只要在controller的方法中返回一个Callable或者DeferredResult就可以了,比如下面的例子:

@RestController
public class AsyncRequestController {

    @GetMapping("/async")
    public Callable<UserDto> doAsync(){
        return ()->{
            Thread.sleep(5000);
            return (UserDto)SecurityUtils.getSubject().getPrincipal();
        };
    }
}

对于异步请求的线程池初始化,可以在重写WebMvcConfigurationSupportconfigureAsyncSupport()方法。

@Configuration
public class WebConfiguration extends WebMvcConfigurationSupport{
    @Override
    protected void configureAsyncSupport(AsyncSupportConfigurer configurer) {
         //设置一个3个线程的线程池来处理异步请求
        configurer.setTaskExecutor(new ConcurrentTaskExecutor(Executors.newFixedThreadPool(3)));
        //异步请求处理超时为30秒
        configurer.setDefaultTimeout(30000);
    }
}

Shiro针对异步请求的配置

还是以上次的shiro实现jwt认证授权的项目为例(传送门:https://www.jianshu.com/p/0b1131be7ace),如果我们按照原来的配置,请求上面的异步controller会出现下面的错误:

2018-09-24 00:26:31.659 [ERROR][http-nio-8080-exec-2]:o.a.c.c.C.[.[localhost].[/].[dispatcherServlet] [log:182] Servlet.service() for servlet [dispatcherServlet] threw exception
org.apache.shiro.UnavailableSecurityManagerException: No SecurityManager accessible to the calling code, either bound to the org.apache.shiro.util.ThreadContext or as a vm static singleton.  This is an invalid application configuration.
    at org.apache.shiro.SecurityUtils.getSecurityManager(SecurityUtils.java:123)
    at org.apache.shiro.subject.Subject$Builder.<init>(Subject.java:626)
    at org.apache.shiro.SecurityUtils.getSubject(SecurityUtils.java:56)
    at org.apache.shiro.web.servlet.ShiroHttpServletRequest.getSubject(ShiroHttpServletRequest.java:89)
    at org.apache.shiro.web.servlet.ShiroHttpServletRequest.getSubjectPrincipal(ShiroHttpServletRequest.java:94)
    at org.apache.shiro.web.servlet.ShiroHttpServletRequest.getUserPrincipal(ShiroHttpServletRequest.java:112)
    at org.springframework.web.servlet.FrameworkServlet.getUsernameForRequest(FrameworkServlet.java:1093)
    at org.springframework.web.servlet.FrameworkServlet.publishRequestHandledEvent(FrameworkServlet.java:1078)
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1005)
    at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:866)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:635)
    at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:851)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:742)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at org.apache.catalina.core.ApplicationDispatcher.invoke(ApplicationDispatcher.java:728)
    at org.apache.catalina.core.ApplicationDispatcher.doDispatch(ApplicationDispatcher.java:649)
    at org.apache.catalina.core.ApplicationDispatcher.dispatch(ApplicationDispatcher.java:612)
    at org.apache.catalina.core.AsyncContextImpl$AsyncRunnable.run(AsyncContextImpl.java:567)
    at org.apache.catalina.core.AsyncContextImpl.doInternalDispatch(AsyncContextImpl.java:353)

上面错误的原因是我们的shiro filter没有对AsyncContext中的Request没有做拦截,造成SecurityManager为空。解决这个问题,只要在注册shiro Filter的地方做如下配置:

@Bean
    public FilterRegistrationBean<Filter> filterRegistrationBean(SecurityManager securityManager,UserService userService) throws Exception{
        FilterRegistrationBean<Filter> filterRegistration = new FilterRegistrationBean<Filter>();
        filterRegistration.setFilter((Filter)shiroFilter(securityManager, userService).getObject());
        filterRegistration.addInitParameter("targetFilterLifecycle", "true");
        filterRegistration.setAsyncSupported(true);
        filterRegistration.setEnabled(true);
        //这里添加一下对DispatcherType.ASYNC的支持就可以了
        filterRegistration.setDispatcherTypes(DispatcherType.REQUEST,DispatcherType.ASYNC);

        return filterRegistration;
    }

Filter配置
在SpringMVC的实现中,对于Filter在进入异步请求之前会过一遍,异步请求之后又会过一遍。但是对于shiro这种鉴权的Filter,其实第二遍是没有必要的。所以我们需要在Filter中针对第二次过Filter的情况跳过。实现方式就是第一次进Filter的时候在request的Attribute中加一个属性,这样第二次进来的时候就会发现这个属性不为空,直接跳过,这也是servlet中OncePerRequestFilter的实现逻辑。以JwtAuthFilter为例:

public class JwtAuthFilter extends AuthenticatingFilter {
    @Override
    protected void postHandle(ServletRequest request, ServletResponse response){
        //设置一个标记位
        request.setAttribute("jwtShiroFilter.FILTERED", true);
    }

    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        //判断是不是第二次进入,是则直接返回
        Boolean afterFiltered = (Boolean)(request.getAttribute("jwtShiroFilter.FILTERED"));
        if( BooleanUtils.isTrue(afterFiltered))
            return true;
        ...
    }
}

同样的RolesFilter也添加类似的逻辑,具体请看源代码:https://github.com/chilexun/springboot-demo/tree/master/shiro-jwt-demo

[参考资料]
异步化,高并发大杀器 作者: 拿铁咖啡

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 201,681评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,710评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,623评论 0 334
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,202评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,232评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,368评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,795评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,461评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,647评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,476评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,525评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,226评论 3 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,785评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,857评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,090评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,647评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,215评论 2 341

推荐阅读更多精彩内容