public static ApplicationFilterChain createFilterChain(ServletRequest request,
Wrapper wrapper, Servlet servlet) { // If there is no servlet to execute, return null
if (servlet == null) return null; // Create and initialize a filter chain object
ApplicationFilterChain filterChain = null; if (request instanceof Request) { Request req = (Request) request; if (Globals.IS_SECURITY_ENABLED) { // Security: Do not recycle
filterChain = new ApplicationFilterChain();
} else {
filterChain = (ApplicationFilterChain) req.getFilterChain(); if (filterChain == null) {
filterChain = new ApplicationFilterChain();
req.setFilterChain(filterChain);
}
}
} else { // Request dispatcher in use
filterChain = new ApplicationFilterChain();
}
filterChain.setServlet(servlet);
filterChain.setServletSupportsAsync(wrapper.isAsyncSupported()); // Acquire the filter mappings for this Context
StandardContext context = (StandardContext) wrapper.getParent();
FilterMap filterMaps[] = context.findFilterMaps(); // If there are no filter mappings, we are done
if ((filterMaps == null) || (filterMaps.length == 0)) return filterChain; // Acquire the information we will need to match filter mappings
DispatcherType dispatcher =
(DispatcherType) request.getAttribute(Globals.DISPATCHER_TYPE_ATTR); String requestPath = null; Object attribute = request.getAttribute(Globals.DISPATCHER_REQUEST_PATH_ATTR); if (attribute != null){
requestPath = attribute.toString();
} String servletName = wrapper.getName(); // Add the relevant path-mapped filters to this filter chain //关键是这个Map , 其中就有 springSecurityFilterChain
for (FilterMap filterMap: filterMaps) { soccer.js.jrzb2.info if (!matchDispatcher(filterMap, dispatcher)) { continue;
} if (!matchFiltersURL(filterMap, requestPath)) continue; ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)
context.findFilterConfig(filterMap.getFilterName()); if (filterConfig == null) { // FIXME - log configuration problem
continue;
}
filterChain.addFilter(filterConfig);
} // Add filters that match on servlet name second
for (FilterMap filterMap: filterMaps) { if (!matchDispatcher(filterMap, dispatcher)) { continue;
} if (!matchFiltersServlet(filterMap, servletName)) continue; ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)
context.findFilterConfig(filterMap.getFilterName()); if (filterConfig == null) { // FIXME - log configuration problem
continue;
}
filterChain.addFilter(filterConfig);
} // Return the completed filter chain
return filterChain;
Spring Security 已经定义了一些 Filter, 不管实际应用中你用到了哪些, 它们应当保持如下顺序;
ChannelProcessingFilter, 如果你访问的 channel 错了, 那首先就会在 channel 之间进行跳转, 如 http 变为 https;
SecurityContextPersistenceFilter, 在一开始进行 request 的时候就可以在 SecurityContextHolder 中建立一个 SecurityContext, 然后在请求结束的时候, 任何对 SecurityContext 的改变都可以被 copy 到 HttpSession;
负责将 SecurityContextHolder 中的登陆用户信息 (默认保存在ThreadLocal) 加载到,SecurityContext
SecurityContextHolder.getContext()!!
ConcurrentSessionFilter, 因为它需要使用 SecurityContextHolder 的功能, 而且更新对应 session 的最后更新时间, 以及通过 SessionRegistry 获取当前的 SessionInformation 以检查当前的 session 是否已经过期, 过期则会调用 LogoutHandler;
认证处理机制, 如 UsernamePasswordAuthenticationFilter, CasAuthenticationFilter, BasicAuthenticationFilter 等, 以至于 SecurityContextHolder 可以被更新为包含一个有效的 Authentication 请求;
SecurityContextHolderAwareRequestFilter, 它将会把 HttpServletRequest 封装成一个继承自 HttpServletRequestWrapper 的 SecurityContextHolderAwareRequestWrapper, 同时使用 SecurityContext 实现了 HttpServletRequest 中与安全相关的方法;
JaasApiIntegrationFilter, 如果 SecurityContextHolder 中拥有的 Authentication 是一个 JaasAuthenticationToken, 那么该 Filter 将使用包含在 JaasAuthenticationToken 中的 Subject 继续执行 FilterChain;
RememberMeAuthenticationFilter, 如果之前的认证处理机制没有更新 SecurityContextHolder, 并且用户请求包含了一个 Remember-Me 对应的 cookie, 那么一个对应的 Authentication 将会设给 SecurityContextHolder;
AnonymousAuthenticationFilter, 如果之前的认证机制都没有更新 SecurityContextHolder 拥有的 Authentication, 那么一个 AnonymousAuthenticationToken 将会设给 SecurityContextHolder;
ExceptionTransactionFilter, 用于处理在 FilterChain 范围内抛出的 AccessDeniedException 和 AuthenticationException, 并把它们转换为对应的 Http 错误码返回或者对应的页面;
FilterSecurityInterceptor, 保护 Web URI, 并且在访问被拒绝时抛出异常;
The order that filters are defined in the chain is very important. Irrespective of which filters you are actually using, the order should be as follows:
ChannelProcessingFilter, because it might need to redirect to a different protocol
SecurityContextPersistenceFilter, so a SecurityContext can be set up in the SecurityContextHolder at the beginning of a web request, and any changes to the SecurityContext can be copied to the HttpSession when the web request ends (ready for use with the next web request)
so 没有经过
SecurityContextPersistenceFilter 是用不了 SecurityContextHolder的!!!
ConcurrentSessionFilter, because it uses the SecurityContextHolder functionality and needs to update the SessionRegistry to reflect ongoing requests from the principal
Authentication processing mechanisms -
UsernamePasswordAuthenticationFilter,
CasAuthenticationFilter,
BasicAuthenticationFilter etc - so that the SecurityContextHolder can be modified to contain a valid Authentication request token
The
SecurityContextHolderAwareRequestFilter, if you are using it to install a Spring Security aware HttpServletRequestWrapper into your servlet container
The
JaasApiIntegrationFilter, if a JaasAuthenticationToken is in the SecurityContextHolder this will process the FilterChain as the Subject in the JaasAuthenticationToken
RememberMeAuthenticationFilter, so that if no earlier authentication processing mechanism updated the SecurityContextHolder, and the request presents a cookie that enables remember-me services to take place, a suitable remembered Authentication object will be put there
AnonymousAuthenticationFilter, so that if no earlier authentication processing mechanism updated the SecurityContextHolder, an anonymous Authentication object will be put there
ExceptionTranslationFilter, to catch any Spring Security exceptions so that either an HTTP error response can be returned or an appropriate AuthenticationEntryPoint can be launched
FilterSecurityInterceptor, to protect web URIs and raise exceptions when access is denied
public class SecurityContextHolder { // ~ Static fields/initializers
// =====================================================================================
public static final nav.fovkj.com String MODE_THREADLOCAL = "MODE_THREADLOCAL"; public static final String MODE_INHERITABLETHREADLOCAL = "MODE_INHERITABLETHREADLOCAL"; public static final String MODE_GLOBAL = "MODE_GLOBAL"; public static final String SYSTEM_PROPERTY = "spring.security.strategy"; private static String strategyName = System.getProperty(SYSTEM_PROPERTY); private static SecurityContextHolderStrategy strategy; private static int initializeCount = 0;
...
}
可通过
spring.security.strategy 配置
MODE_THREADLOCAL: 缺省工作模式, 保存在ThreadLocal
MODE_INHERITABLETHREADLOCAL: 存储在线程中, 但子线程可以获取到父线程中的
MODE_GLOBAL: 全局策略, 所有线程都获取
主要配置部分
<http auto-config="false" access-denied-page="/login.htm" entry-point-ref="authenticationEntryPoint"> <intercept-url pattern="/images/*" filters="none"/> <intercept-url pattern="/style/*" filters="none"/> <intercept-url pattern="/login.htm*" filters="none"/> <intercept-url pattern="/login.jsp*" filters="none"/> <intercept-url pattern="/logout.jsp" filters="none"/> <intercept-url pattern="/index/map.sh*" filters="none"/> <intercept-url pattern="/map/map_topic/topicList.sh*" filters="none"/> <intercept-url pattern="/map/searchNearby.sh*" filters="none"/> <intercept-url pattern="/solr/*" filters="none"/> <intercept-url pattern="/mobileRest/*" filters="none"/> <intercept-url pattern="/**" access="ROLE_USER"/> <!-- 验证码过滤器, spring 安全过滤器链 有默认的过滤器而且还是有默认顺序位置的(position这个属性指定)--> <custom-filter ref="captchaAuthenticaionFilter" position="FORM_LOGIN_FILTER" /> <!--成功注销后跳转到的页面; --> <logout logout-success-url="/login.htm" /> <http-basic /> </http>
认证入口点, 在过滤链, 检验失败的时候, 引导到登陆页面,其配置.
<beans:bean id="authenticationEntryPoint" class="net.jk.auth.JKLoginUrlAuthenticationEntryPoint"> <beans:property name="loginFormUrl" value="/login.jsp" /> <beans:property name="ajaxLoginFormUrl" value="ajaxLogin.htm" /> </beans:bean>
见上:
细谈认证过程的第二步
> 如果未登陆,如 UsernamePasswordAuthenticationFilter, CasAuthenticationFilter..过滤器负责获取到的用户名和密码封装成一个实现了
Authentication接口的 UsernamePasswordAuthenticationToken;
<beans:bean id="captchaAuthenticaionFilter" class="org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter"> <!-- 注入认证管理--> <beans:property name="authenticationManager" ref="authenticationManager" /> <!-- 成功与失败的处理--> <beans:property name="authenticationFailureHandler" ref="authenticationFailureHandler" /> <beans:property name="authenticationSuccessHandler" ref="authenticationSuccessHandler" /> <beans:property name="filterProcessesUrl" value="/j_spring_security_check" /> </beans:bean>
见上:
细谈认证过程的第五步
>
AuthenticationManager并不认证,而是调用AuthenticationProvider的列表认证
<!-- 认证管理 它应该持有DetailsService,AuthenticationProvider,如果没有AuthenticationProvider 配置默认会实例DaoAuthenticationProvider --> <authentication-manager alias="authenticationManager"> <authentication-provider user-service-ref="userDetailsService" /> </authentication-manager>
AuthenticationProvider接口的public abstract Authentication authenticate(Authentication paramAuthentication) throws AuthenticationException;方法是真正在处理认证逻辑的
它持有一个UserDetailsService,用于加载用户信息, 简而言之: 改变认证 实现AuthenticationProvider; 改变信息来源 实现UserDetailsService
认证成功: 后会补充 Authentication详细的角色权限等 返回给AuthenticationManager
认证失败: 抛出AuthenticationException
未知: 返回null,这将交给下一个Provider认证
<authentication-provider>会默认实例化一个DaoAuthenticationProvider
在注解中
@Service("userDetailsService")public class LoadUserDetailsService implements UserDetailsService{
... //`UserDetailsService`,用于加载用户信息
@Override
public UserDetails loadUserByUsername(String loginid)
throws UsernameNotFoundException, DataAccessException { final List<GrantedAuthority> grantedAuthorities = new ArrayList<GrantedAuthority>();//这是角色规则
if(StringUtils.isBlank(loginid))throw new UsernameIsEmptyException("请输入用户名! ");
Bs_user user=bs_userDao.findByLoginId(loginid); soccer.qiu.zuq8.info if(user==null)throw new UsernameNotFoundException("UserDetailsService.usernameNotFound");
grantedAuthorities.add(new GrantedAuthorityImpl(AuthConstants.ROLE_USER));
List<String> permissionCode=bs_roleService.findPermissionCodeByAccount(loginid); if(permissionCode!=null && !permissionCode.isEmpty()){ for(String code:permissionCode){
grantedAuthorities.add(new GrantedAuthorityImpl(code));
}
}
... //返回详细信息, 最终认证会在默认的 DaoAuthenticationProvider
return (UserDetails) authuser;
}
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/70048957/viewspace-3107299/ ,如需转载,请注明出处,否则将追究法律责任。