本文基於shiro的web環境,用宏觀(也就是不精確)的角度去理解shiro的工做流程,先看shiro官方的一張圖。java
和應用程序直接交互的對象是Subject,securitymanager爲Subject服務。能夠把Subject當作一個用戶,你的全部的代碼都由用戶來執行。suject.execute(callable),這個callable裏面就是你的代碼。web
1、shiro如何介入你的webappapache
它是如何初始化的?servletContextListener。它是如何在每一個http請求中介入的?ServletFilter.cookie
<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的內容進行了配置。session
再看shiroFilter:app
@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了。webapp
下面的一段代碼就是本文開頭提到的Subject(用戶)的建立了,由於是web環境因此每次請求都須要建立一個subject對象,在filter裏面給你準備好,在你的servlet裏面就能夠直接使用了。ide
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,寫道:ui
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.this
將callable(你的全部代碼都在裏面執行)和當前的subject實例相關聯,而且在當前的thread中執行...
2、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了吧。
....
3、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模塊,可是上面的方法須要注意幾個問題:
一、session和cookie和ring自己的機制不同,須要特別處理。
二、這樣個性化的adapter沒法融入ring的生態圈,好比lein-ring
基於上面的考慮,不如不使用shiro的web模塊,直接使用shiro-core,而後用ring-middleware的方式來實現。