Spring-Security 运行流程
首先在web.xml中配置如上图所示的过滤器,但这其实不是spring-security包下的类,而是spring-web下面的一个类,从名字上可以看出,这个类是一个过滤器的代理,根据我查阅资料,这样做的原因是:spring-security的过滤器链中的每一个都必须被注入来自 Spring 应用程序上下文的其他 Bean. 但 Servlet 规范并没有使得 Servlet 过滤器上的依赖注入容易进行. DeletegatingFilterProxy 通过充当 Spring 应用程序中被配置为 Bean 的实际过滤器的 “挂名人物” 来解决这个问题.真正起作用的 Filter 是 Spring 上下文中的那些 Filter Bean. web.xml 中的代理依次调用这些Bean, 实现对Web资源的保护。可以参考我的另一篇博客:
通过调试发现,最终DelegatingFilterProxy中生成一个FilterChainProxy对象,且在这个对象中,包含了security的过滤器链,过滤器链有三组,第一组是针对登录页面的,第二组是登出页面,第三组是对任何请求都过滤,权限的控制也是在这组过滤器链中实现的。我的理解是,掌握第三组过滤器链的作用,就基本对spring-security有一个基本的使用上掌握了。
下面来详细看下这个过滤器链:
第一个过滤器:SecurityContextPersistenceFilter
主要作用是SecurityContext的组装,供后续的过滤器链来使用,如果用户已经登录,再次访问其他资源时,会根据sessionId在session中将以前保存的SecurityContext取出,SecurityContext保存有用户的登录信息,那么就不需要用户再次登录了,只需要验证该用户是否有权限访问该资源即可。
在SecurityContextPersistenceFilter的doFilter方法中: SecurityContext contextBeforeChainExecution = repo.loadContext(holder);
上面一行代码,则是取出SecurityContext,如果用户第一次访问,还没有,则返回null。
第二个过滤器:LogoutFilter
这个是spring-security的登出功能,当我们在spring-security配置文件的security:http节点,配置auto-config为true时(如下图),那么spring-security就会为我们自动加载logout过滤器。之后就可以使用spring-security的logout功能了,可以参考spring security logout(spring security登出示例),我翻译的一篇外文,是logout功能的使用范例。
第三个过滤器:UsernamePasswordAuthenticationFilter
见名知意,这个过滤器是用来验证用户的登录凭证的,可以通过这个过滤器来实现从数据库取出数据,来验证用户登录凭证是否正确。在我学习的过程中,发现刚开始应该只需要初步的了解这个过滤器的执行流程,就能基本的解决一些需求了.什么意思呢,就是我发现最后,其实这个过滤器,就是取出form表单中用户提交的登录信息,来和我们配置在配置文件中的用户信息,或者是通过数据库中取出来的用户信息来匹配,匹配成功,则通过。刚开始我学习的很迷茫,就是把问题搞复杂了,其实看清本质就好。不废话,直接说如何从数据库取用户信息。
上面这段springSecurity的配置文件很重要,先说有关从数据中库取用户登录凭证的。
既然是从数据库取用户信息,那么取数据的实现代码,肯定是要我们自己写的
上面就是这个实现了org.springframework.security.core.userdetails.UserDetailsService接口的实现类,就是通过实现这个类,我们根据登录用户的名称,取出用户信息,在封装到springSecurity要的User对象中,返回给springSecurity框架来使用,就可以了。再配置文件中,我们通过springSecurity的配置文件来注入我们的这个实现类。这样就实现了从数据库中,去取用户的登录信息。第四个过滤器:BasicAuthenticationFilter
org.springframework.security.web.authentication.www.BasicAuthenticationFilter,这个过滤器是当开启Basic验证方式的时候,才有用,关于basic验证方式,就是一种将用户的验证信息放在http请求头的中用户验证方式。如果请求头不是以Basic开头,则这个过滤器是不会起作用的
第五个过滤器:RequestCacheAwareFilter
org.springframework.security.web.savedrequest.RequestCacheAwareFilter这个filter的用途官方解释是:
用于用户登录成功后,重新恢复因为登录被打断的请求
这个解释也有几点需要说明
被打断的请求:简单点说就是出现了AuthenticationException、AccessDeniedException两类异常
重新恢复:既然能够恢复,那肯定请求信息被保存到cache中了
第六个过滤器:SecurityContextHolderAwareRequestFilter
org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter
从类名称可以猜出这个过滤器主要是包装请求对象request的,看源码
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
chain.doFilter(new SecurityContextHolderAwareRequestWrapper((HttpServletRequest) req, rolePrefix), res);
}
SecurityContextHolderAwareRequestWrapper类对request包装的目的主要是实现servlet api的一些接口方法isUserInRole、getRemoteUser。
这个过滤器看起来很简单。目的仅仅是实现java ee中servlet api一些接口方法。
一些应用中直接使用getRemoteUser方法、isUserInRole方法,在使用spring security时其实就是通过这个过滤器来实现的。
第七个过滤器:AnonymousAuthenticationFilter
对应的类路径为:org.springframework.security.web.authentication.AnonymousAuthenticationFilter.
AnonymousAuthenticationFilter过滤器是在UsernamePasswordAuthenticationFilter、BasicAuthenticationFilter、RememberMeAuthenticationFilter这些过滤器后面的,所以如果这三个过滤器都没有认证成功,则为当前的SecurityContext中添加一个经过匿名认证的token,但是通过servlet的getRemoteUser等方法是获取不到登录账号的。因为SecurityContextHolderAwareRequestFilter过滤器在AnonymousAuthenticationFilter前面。
//省略了日志部分
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
//applyAnonymousForThisRequest永远返回ture
if (applyAnonymousForThisRequest((HttpServletRequest) req)) {
//如果当前SecurityContext中没有认证实体
if (SecurityContextHolder.getContext().getAuthentication() == null) {
//产生一个匿名认证实体,并保存到SecurityContext中
SecurityContextHolder.getContext().setAuthentication(createAuthentication((HttpServletRequest) req));
} else {}
}
chain.doFilter(req, res);
}
protected Authentication createAuthentication(HttpServletRequest request) {
//产生匿名认证token,注意这里的key、userAttribute是通过解析标签注入的
AnonymousAuthenticationToken auth = new AnonymousAuthenticationToken(key, userAttribute.getPassword(),
userAttribute.getAuthorities());
auth.setDetails(authenticationDetailsSource.buildDetails(request));
return auth;
}
anonymous标签配置为。
<anonymous granted-authority="ROLE_ANONYMOUS" enabled="true" username="test"/>
这里username属性容易混淆,username默认为anonymousUser,实际上是注入到UserAttribute的password变量中的。
granted-authority属性注入到UserAttribute的authorities授权列表。
第十个过滤器:MyFilterSecurityInterceptor
这个过滤器是自定义来实现权限控制逻辑的,通过spring-security配置文件来注入到spring-security的过滤器链中的,这个过滤器需要继承org.springframework.security.access.intercept.AbstractSecurityInterceptor。
配置文件如下:
<security:http auto-config="true" access-denied-page="/403.jsp" >
<security:form-login login-page="/login.jsp"/>
<!-- 将自定义的实现了权限控制逻辑的过滤器加入到spring-security的过滤器链中 -->
<security:custom-filter before="FILTER_SECURITY_INTERCEPTOR" ref="myFilter"/>
</security:http>
<!-- 注入MyFilterSecurityInterceptor需要的相关bean -->
<bean id="myFilter" class="com.wr1ttenyu.security.MyFilterSecurityInterceptor">
<property name="authenticationManager" ref="authenticationManager" />
<property name="accessDecisionManager" ref="myAccessDecisionManagerBean" />
<property name="securityMetadataSource" ref="securityMetadataSource" />
</bean>
<!-- 将实现了用户信息获取的bean注入到authenticationManager中 -->
<security:authentication-manager alias="authenticationManager">
<security:authentication-provider user-service-ref="myUserDetailService">
</security:authentication-provider>
</security:authentication-manager>
<!-- 自定义的获取用户信息的实现类 -->
<bean id="myUserDetailService" class="com.wr1ttenyu.security.MyUserDetailService" />
<!-- 这里myAccessDecisionManagerBean是用来注入到AbstractSecurityInterceptor中accessDecisionManager属性的,
用来实现权限控制的具体逻辑 -->
<bean id="myAccessDecisionManagerBean" class="com.wr1ttenyu.security.MyAccessDecisionManagerBean" />
<!-- 定义实现了权限控制逻辑的过滤器bean -->
<bean id="securityMetadataSource" class="com.wr1ttenyu.security.MyInvocationSecurityMetadataSource" />
配置好之后,该过滤器就会被加入到过滤器链中,具体实现权限验证的代码如下:
public void invoke(FilterInvocation fi) throws IOException, ServletException {
// 通过调用父类AbstractSecurityInterceptor的beforeInvocation方法来实现
// 值得注意的是,在beforeInvocation实现权限控制的逻辑中,如果请求的资源路径在securityMetadataSource中没有的话,
// 那么就会默认为该资源无权限要求,只要请求就会通过。
InterceptorStatusToken token = super.beforeInvocation(fi);
try {
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
} finally {
super.afterInvocation(token, null);
}
}
以上内容主要参考:http://www.codeweblog.com站内关于spring-security源码分析的文章
未完待续。。。。