• 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的方式来实现。

  • 相关阅读:
    Java框架第二次课
    知识扩展1——最大似然估计
    知识扩展2——熵 ,KL散度,交叉熵,JS散度,Wasserstein 距离(EarthMover距离)
    知识扩展3——广义线性模型GLM
    3 逻辑回归
    朴素贝叶斯
    GAN 的后序
    mysql 多个字段字符串合并成一个字段
    qt离线下载
    “用户”,你凭什么说改框架?
  • 原文地址:https://www.cnblogs.com/yaomajor/p/6144947.html
Copyright © 2020-2023  润新知