Apache Shiro的工作流程分析

jopen 10年前發布 | 90K 次閱讀 Shiro 安全相關 Apache Shiro

apache shiro非常易于使用,如果是在標準java web環境下使用,你沒有必要去了解內部的工作流程。如果需要在非標準java web環境使用,就必須深入到它的整個對象圖中去。我希望在clojure ring的環境中使用shiro,我還不知道行不行,我正在了解中。

本文基于shiro的web環境,用宏觀(也就是不精確)的角度去理解shiro的工作流程,先看shiro官方的一張圖。

Apache 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的方式來實現。

來自:http://my.oschina.net/jianglibo/blog/318426

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!