当前位置: 首页 > news >正文

apache shiro的工作流程分析

本文基于shiro的web环境,用宏观(也就是不精确)的角度去理解shiro的工作流程,先看shiro官方的一张图。

和应用程序直接交互的对象是Subject,securitymanager为Subject服务。可以把Subject看成一个用户,你的所有的代码都由用户来执行。suject.execute(callable),这个callable里面就是你的代码。

一、shiro如何介入你的webapp

它是如何初始化的?servletContextListener。它是如何在每个http请求中介入的?ServletFilter.

<listener>
    <listener-class>org.apache.shiro.web.env.EnvironmentLoaderListener</listener-class>
</listener>


<filter>
    <filter-name>ShiroFilter</filter-name>
    <filter-class>org.apache.shiro.web.servlet.ShiroFilter</filter-class>
</filter>

<filter-mapping>
    <filter-name>ShiroFilter</filter-name>
    <url-pattern>/*</url-pattern>
    <dispatcher>REQUEST</dispatcher> 
    <dispatcher>FORWARD</dispatcher> 
    <dispatcher>INCLUDE</dispatcher> 
    <dispatcher>ERROR</dispatcher>
</filter-mapping>

EnvironmentLoaderListener会根据你在web.xml中的配置读取对应的配置文件(默认读取/WEB-INF/shiro.ini,或者classroot的对应文件),构建一个shiro环境,该环境保存在servletcontext中,所有的filter都可以获取。里面就包括一个单例的securityManager,该securityManager已经根据ini的内容进行了配置。

再看shiroFilter:

@Override
    public void init() throws Exception {
        WebEnvironment env = WebUtils.getRequiredWebEnvironment(getServletContext());

        setSecurityManager(env.getWebSecurityManager());

        FilterChainResolver resolver = env.getFilterChainResolver();
        if (resolver != null) {
            setFilterChainResolver(resolver);
        }
    }

这样filter里面就可以使用securityManager了。

下面的一段代码就是本文开头提到的Subject(用户)的创建了,因为是web环境所以每次请求都需要创建一个subject对象,在filter里面给你准备好,在你的servlet里面就可以直接使用了。

    final ServletRequest request = prepareServletRequest(servletRequest, servletResponse, chain);
            final ServletResponse response = prepareServletResponse(request, servletResponse, chain);

            final Subject subject = createSubject(request, response);

            //noinspection unchecked
            subject.execute(new Callable() {
                public Object call() throws Exception {
                    updateSessionLastAccessTime(request, response);
                    executeChain(request, response, chain);
                    return null;
                }
            });

看一下subject.execute的javadoc,写道:

Associates the specified Callable with this Subject instance and then executes it on the currently running thread.  If you want to execute the Callable on a different thread, it is better to use the associateWith(Callable)} method instead.

将callable(你的所有代码都在里面执行)和当前的subject实例相关联,并且在当前的thread中执行...

二、securityManage如何为subject服务

请注意看上面最后一段java代码,里面有一个createSubject(request,response)方法,也在filter里面,它的代码如下:

  protected WebSubject createSubject(ServletRequest request, ServletResponse response) {
        return new WebSubject.Builder(getSecurityManager(), request, response).buildWebSubject();
    }

看到没有?subject的构造用到了securityManager,所有shiro的魔法都被隐藏在securityManager里面了。接下来提一些问题,试着发现securityManager需要完成哪些工作。

  • 如果保证每次http请求得到同一个(确切说应该是一样的)subject?这里关系到session管理了吧。

  • 如何登陆用户,subject.login?这里关系到认证,授权,用户realm了吧。

  • ....

三、clojure-ring使用shiro的可行方案

clojure-ring SPEC中没有提供servlet环境,它的Adapters仅仅是封装了request和response,已经处于请求的最末端。所以shiro提供的servletfilter无法使用。来看看jetty-adapter的代码:

[handler options]
  (let [^Server s (create-server (dissoc options :configurator))
        ^QueuedThreadPool p (QueuedThreadPool. ^Integer (options :max-threads 50))]
    (.setMinThreads p (options :min-threads 8))
    (when-let [max-queued (:max-queued options)]
      (.setMaxQueued p max-queued))
    (when (:daemon? options false)
      (.setDaemon p true))
    (doto s
      (.setHandler (proxy-handler handler))
      (.setThreadPool p))
    (when-let [configurator (:configurator options)]
      (configurator s))
    (.start s)
    (when (:join? options true)
      (.join s))
    s))

其中.setHandler,是一个实现了jetty的AbstractHandler的handler.

(defn- proxy-handler
  "Returns an Jetty Handler implementation for the given Ring handler."
  [handler]
  (proxy [AbstractHandler] []
    (handle [_ ^Request base-request request response]
      (let [request-map  (servlet/build-request-map request)
            response-map (handler request-map)]
        (when response-map
          (servlet/update-servlet-response response response-map)
          (.setHandled base-request true))))))

ring结构和SPEC都非常简洁,上面的代码中将jetty server修改成servletcontext,然后通过一个servlet来实现这个adapter,就可以了。

代码如下:

 public void useServlet() throws Exception {
    Server server = new Server(8080);

    final ServletContextHandler servletContext = new ServletContextHandler(ServletContextHandler.SESSIONS);
    servletContext.setClassLoader(Thread.currentThread().getContextClassLoader());
    
    servletContext.addLifeCycleListener(new LifeCycle.Listener() {
      
      @Override
      public void lifeCycleStopping(LifeCycle arg0) {
      }
      
      @Override
      public void lifeCycleStopped(LifeCycle arg0) {
      }
      
      @Override
      public void lifeCycleStarting(LifeCycle arg0) {
      }
      
      @Override
      public void lifeCycleStarted(LifeCycle arg0) {
        servletContext.setContextPath("/");
        servletContext.addServlet(new ServletHolder(new HelloServlet()), "/*");
        servletContext.addServlet(new ServletHolder(new HelloServlet("Buongiorno Mondo")), "/it/*");
        servletContext.addServlet(new ServletHolder(new HelloServlet("Bonjour le Monde")), "/fr/*");
        servletContext.callContextInitialized(new EnvironmentLoaderListener(), new ServletContextEvent(servletContext.getServletContext()));
        servletContext.addFilter(ShiroFilter.class, "/*", EnumSet.of(DispatcherType.INCLUDE,DispatcherType.REQUEST,DispatcherType.FORWARD,DispatcherType.ERROR));
      }
      
      @Override
      public void lifeCycleFailure(LifeCycle arg0, Throwable arg1) {
      }
    });
    
    server.setHandler(servletContext);
    server.start();
    server.join();
  }

使用上面的代码,就完整的使用了shiro的web模块,但是上面的方法需要注意几个问题:

1、session和cookie和ring本身的机制不一样,需要特别处理。

2、这样个性化的adapter无法融入ring的生态圈,比如lein-ring

基于上面的考虑,不如不使用shiro的web模块,直接使用shiro-core,然后用ring-middleware的方式来实现。

转载于:https://www.cnblogs.com/yaomajor/p/6144947.html

相关文章:

  • margin和padding的区别
  • Linux date 用法
  • mysql引擎更改
  • jquery中DOM节点操作(一)
  • PowerDesigner CDM中取消默认不能存在同名主键的方法
  • linux 下java环境的配置
  • dubbo源码解析(二)
  • 在VMW 虚拟桌面环境下 VC总是提示 计算虚拟磁盘空间
  • RH413-RHEL6.4课程总结
  • 摄像机成像模型
  • mybatis 获取insert返回的主键
  • 15索引器
  • (七)理解angular中的module和injector,即依赖注入
  • zzz
  • JS中获取session中传过来的值对象
  • 0x05 Python数据分析,Anaconda八斩刀
  • css系列之关于字体的事
  • ES6 学习笔记(一)let,const和解构赋值
  • interface和setter,getter
  • Zepto.js源码学习之二
  • 从地狱到天堂,Node 回调向 async/await 转变
  • 大快搜索数据爬虫技术实例安装教学篇
  • 理解在java “”i=i++;”所发生的事情
  • 聊聊flink的TableFactory
  • 如何选择开源的机器学习框架?
  • 深度解析利用ES6进行Promise封装总结
  • 体验javascript之美-第五课 匿名函数自执行和闭包是一回事儿吗?
  • 我们雇佣了一只大猴子...
  • ​configparser --- 配置文件解析器​
  • #【QT 5 调试软件后,发布相关:软件生成exe文件 + 文件打包】
  • %3cscript放入php,跟bWAPP学WEB安全(PHP代码)--XSS跨站脚本攻击
  • (09)Hive——CTE 公共表达式
  • (C语言版)链表(三)——实现双向链表创建、删除、插入、释放内存等简单操作...
  • (Python) SOAP Web Service (HTTP POST)
  • (Redis使用系列) Springboot 使用redis实现接口Api限流 十
  • (二)linux使用docker容器运行mysql
  • (附源码)springboot优课在线教学系统 毕业设计 081251
  • (简单) HDU 2612 Find a way,BFS。
  • (力扣)循环队列的实现与详解(C语言)
  • (牛客腾讯思维编程题)编码编码分组打印下标题目分析
  • (幽默漫画)有个程序员老公,是怎样的体验?
  • (原創) 物件導向與老子思想 (OO)
  • ***汇编语言 实验16 编写包含多个功能子程序的中断例程
  • .NET 5种线程安全集合
  • .net framwork4.6操作MySQL报错Character set ‘utf8mb3‘ is not supported 解决方法
  • .net 使用ajax控件后如何调用前端脚本
  • .net解析传过来的xml_DOM4J解析XML文件
  • [2669]2-2 Time类的定义
  • [Android]使用Android打包Unity工程
  • [CentOs7]图形界面
  • [CQOI 2011]动态逆序对
  • [IE编程] 如何编程清除IE缓存
  • [node]Node.js 模块系统
  • [PHP]关联和操作MySQL数据库然后将数据库部署到ECS
  • [Python]—Linux Server 系统监控程序