本文基于shiro的web环境,用宏观(也就是不精确)的角度去理解shiro的工作流程,先看shiro官方的一张图。
和应用程序直接交互的对象是Subject,securitymanager为Subject服务。可以把Subject看成一个用户,你的所有的代码都由用户来执行。suject.execute(callable),这个callable里面就是你的代码。
一、shiro如何介入你的webapp
它是如何初始化的?servletContextListener。它是如何在每个http请求中介入的?ServletFilter.
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
<
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:
|
1
2
3
4
5
6
7
8
9
10
11
|
@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里面就可以直接使用了。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
|
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里面,它的代码如下:
|
1
2
3
|
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,就可以了。
代码如下:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
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的方式来实现。
本文详细阐述了如何在Clojure-Ring环境中整合并利用Apache Shiro框架,通过解析Shiro的工作流程,重点介绍了Shiro如何与web应用交互、SecurityManager的角色及其在session管理、用户认证与授权方面的服务。最后,提出了一种在非标准JavaWeb环境如Clojure-Ring下使用Shiro的可行方案,并展示了具体的代码实现。

2171

被折叠的 条评论
为什么被折叠?



