上一篇聊了聊 Secutity 的基础 , 这一篇我们聊一聊 Securiy Filter , 基本上 Security 常见得功能都能通过 Filter 找到相关的痕迹 .
先来看看我们之前这么注册 Filter 的 >>>
AbstractAuthenticationProcessingFilter filter = new DatabaseAuthenticationFilter();
filter.setAuthenticationManager(authenticationManagerBean());
http.addFilterBefore(filter, UsernamePasswordAuthenticationFilter.class);
我们来追溯一下 Filter 怎么被加载进去的 :
Step First : 将 Filter 加载到 Security 体系中
// 添加到 HttpSecurity
C- HttpSecurity
- this.filters.add(filter) : 这里的 filters 仅仅是一个 List 集合
// 追溯代码可以看到这个集合会用于创建一个 DefaultSecurityFilterChain
protected DefaultSecurityFilterChain performBuild() throws Exception {
Collections.sort(this.filters, this.comparator);
return new DefaultSecurityFilterChain(this.requestMatcher, this.filters);
}
HttpSecurity 这个类我们后面会详细说说 , 这里了解到他其中维护了一个 Filter 集合即可 , 这个集合会被加载到 FilterChain 中
Step 2 : Filter Chain 的使用方式
// 成功标注断点后 , 可以追溯到整个的加载流程 :
// Step 1 : 要构建一个 springSecurityFilterChain
@Bean(name = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
public Filter springSecurityFilterChain()throws Exception {
boolean hasConfigurers = webSecurityConfigurers != null
&& !webSecurityConfigurers.isEmpty();
if (!hasConfigurers) {
WebSecurityConfigurerAdapter adapter = objectObjectPostProcessor.postProcess(new WebSecurityConfigurerAdapter() {});
webSecurity.apply(adapter);
}
return webSecurity.build();
}
// Step 2 : webSecurity.build() 执行构建
public final O build() throws Exception {
// 居然还可以看到 CAS 操作 , 这里应该是设置绑定状态
if (this.building.compareAndSet(false, true)) {
// 执行 Build
this.object = doBuild();
return this.object;
}
throw new AlreadyBuiltException("This object has already been built");
}
// Step 3 : AbstractConfiguredSecurityBuilder 中
protected final O doBuild() throws Exception {
synchronized (configurers) {
buildState = BuildState.INITIALIZING;
// 为子类挂载钩子
beforeInit();
init();
buildState = BuildState.CONFIGURING;
// 在调用每个SecurityConfigurer#configure(SecurityBuilder)方法之前调用。
// 子类可以在不需要使用SecurityConfigurer时 , 覆盖这个方法来挂载到生命周期中
beforeConfigure();
configure();
buildState = BuildState.BUILDING;
// 实际构建对象
O result = performBuild();
buildState = BuildState.BUILT;
return result;
}
}
// Step 4 : 至此反射获取到 Filters 链
@Override
protected DefaultSecurityFilterChain performBuild() throws Exception {
Collections.sort(filters, comparator);
return new DefaultSecurityFilterChain(requestMatcher, filters);
}
可以看到 , 最开始添加到 Filter 集合的 Filter ,最终会用于构建 springSecurityFilterChain , 那么 springSecurityFilterChain 又是干什么的呢?
Security 的 Filter 和 WebFilter 本质是一样的 , 只是为了实现 Seccurity 的功能
>>> 来看一下 FilterChain 的调用链 :
FilterChain 的核心是一个 VirtualFilterChain , 每个请求过来都会有一个VirtualFilterChain 生成
VirtualFilterChain 是 FilterChainProxy 的内部类.
// 补充 : VirtualFilterChain : 内部过滤器链实现,用于通过与请求匹配的额外内部过滤器列表传递请求
C- VirtualFilterChain
?- 每次运行的时候都会创建 , 来链表调用所有的 Filter
P- currentPosition : 当前运行 Filter 的下标
P- FirewalledRequest : 可用于拒绝潜在危险的请求和/或包装它们来控制它们的行为
P- List<Filter> additionalFilters : 包含所有的 Filter 对象
M- doFilter(ServletRequest request, ServletResponse response)
?- 这个方法会从2个维度来处理
1- currentPosition == size : 当执行最后一个的时候 , 先重置 FirewalledRequest , 再调用 originalChain
2- currentPosition != size : 在此之前依次执行 Filter 集合中的 doFilter
VirtualFilterChain 的创建流程 :
C- FilterChainProxy extends GenericFilterBean
?- GenericFilterBean 继承了 Filter 接口 , 其最终会由 SpringWeb 的 Filter 进行调用
M- doFilterInternal
- FirewalledRequest 的相关处理
- 创建了一个 VirtualFilterChain ,执行 Filter 链
// implements Filter
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
boolean clearContext = request.getAttribute(FILTER_APPLIED) == null;
if (clearContext) {
try {
request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
doFilterInternal(request, response, chain);
}finally {
SecurityContextHolder.clearContext();
request.removeAttribute(FILTER_APPLIED);
}
}else {
doFilterInternal(request, response, chain);
}
}
private void doFilterInternal(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
FirewalledRequest fwRequest = firewall.getFirewalledRequest((HttpServletRequest) request);
HttpServletResponse fwResponse = firewall.getFirewalledResponse((HttpServletResponse) response);
List<Filter> filters = getFilters(fwRequest);
if (filters == null || filters.size() == 0) {
// 日志略...
fwRequest.reset();
chain.doFilter(fwRequest, fwResponse);
return;
}
// 这里创建了一个内部类 VirtualFilterChain
VirtualFilterChain vfc = new VirtualFilterChain(fwRequest, chain, filters);
// 类似于链表的方式 , 依次来调用
vfc.doFilter(fwRequest, fwResponse);
}
// 扩展 : FirewalledRequest
C- FirewalledRequest :
?- 这是一个配合防火墙功能的Request 实现类 , 其通常配合 HttpFirewall 来实现
M- reset : 重置方法,该方法允许在请求离开安全过滤器链时由FilterChainProxy重置部分或全部状态
C- StrictHttpFirewall
M- getFirewalledRequest
- rejectForbiddenHttpMethod(request) : 拒绝禁止的 HttpMethod
- rejectedBlacklistedUrls(request) : 拒绝黑名单 URL
- return new FirewalledRequest(request) : 这里创建了一个 FirewalledRequest , 不过 reset 是空实现
可以看到 , 这里每次执行 doFilterInternal 时都会创建一个 VirtualFilterChain .
主要抽象类 AbstractAuthenticationProcessingFilter
C- AbstractAuthenticationProcessingFilter
- !requiresAuthentication(request, response) : 确定是否匹配该请求
?- 注意 ,我们构建 DatabaseAuthenticationFilter 的时候其实是传入了一个Matcher匹配器的
- requiresAuthenticationRequestMatcher.matches(request);
- 不匹配则继续执行 FilterChain
- 匹配后继续执行
- Authentication authResult = attemptAuthentication(request, response);
// 这个方法是需要实现类复写的 , 在实现类中我们做了下面的事情
- 将 Request 中的验证信息 (账户密码, 如果需要扩展 ,可以是更多信息 , Cookie , Header 等等) 取出
- 构建了一个 Token (DatabaseUserToken)
- 将 Token 放入 Details 中
- 通过 AuthenticationManager 调用 ProviderManager 完成认证
// 具体的认证方式我们后续在详细分析
我之前以为 Security 的方式是把 所有的 Filter 走一遍后再执行 Provider , 从这里看来他采用的是Filter 适配后就直接执行 Provider
C- WebAsyncManagerIntegrationFilter
?- 提供SecurityContext和Spring Web的webbasyncmanager之间的集成
?- SecurityContextCallableProcessingInterceptor#beforeConcurrentHandling 用于填充SecurityContext
C- 创建一个WebAsyncManager
?- 用于管理异步请求处理的中心类,主要用作SPI,通常不直接由应用程序类使用。
@Override
protected void doFilterInternal(HttpServletRequest request,HttpServletResponse response, FilterChain filterChain)throws ServletException, IOException {
// 创建一个WebAsyncManager
// 用于管理异步请求处理的中心类,主要用作SPI,通常不直接由应用程序类使用。
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
SecurityContextCallableProcessingInterceptor securityProcessingInterceptor = (SecurityContextCallableProcessingInterceptor) asyncManager
.getCallableInterceptor(CALLABLE_INTERCEPTOR_KEY);
// 如果没有 SecurityContextCallableProcessingInterceptor , 则创建一个注入 WebAsyncManager
if (securityProcessingInterceptor == null) {
asyncManager.registerCallableInterceptor(CALLABLE_INTERCEPTOR_KEY,new SecurityContextCallableProcessingInterceptor());
}
filterChain.doFilter(request, response);
}
注意 , SecurityContext 是整个认证的核心 , 拥有 SecurityContext 即表示认证成功
C- SecurityContextPersistenceFilter
?- 这是一个必选的Filter , 其目的是为了往 SecurityContextHolder 中插入一个 SecurityContext , SecurityContext 是最核心的认证容器
- SecurityContext contextBeforeChainExecution = repo.loadContext(holder);
?- 注意 , 这里会尝试获取Sesssion 的 Context , 用于验证
- SecurityContextHolder.setContext(contextBeforeChainExecution)
?- 设置一个 Context
- chain.doFilter(holder.getRequest(), holder.getResponse());
- finally 中会在所有filter 完成后 , 往 SecurityContextHolder 插入一个 contextAfterChainExecution
?- 注意前面是 contextBeforeChainExecution
// finally 代码一览
finally {
SecurityContext contextAfterChainExecution = SecurityContextHolder.getContext();
// 清除 Context
SecurityContextHolder.clearContext();
// 重新保存新得 Context
repo.saveContext(contextAfterChainExecution, holder.getRequest(),holder.getResponse());
request.removeAttribute(FILTER_APPLIED);
}
List<HeaderWriter> headerWriters
: 构造对象的时候会写入一个list
核心方法 : doFilterInternal
// eaderWriterResponse.writeHeaders() 是从 HeadersConfigurer 中获取的
C- HeadersConfigurer
M- private List<HeaderWriter> getHeaderWriters() {
List<HeaderWriter> writers = new ArrayList<>();
addIfNotNull(writers, contentTypeOptions.writer);
addIfNotNull(writers, xssProtection.writer);
addIfNotNull(writers, cacheControl.writer);
addIfNotNull(writers, hsts.writer);
addIfNotNull(writers, frameOptions.writer);
addIfNotNull(writers, hpkp.writer);
addIfNotNull(writers, contentSecurityPolicy.writer);
addIfNotNull(writers, referrerPolicy.writer);
addIfNotNull(writers, featurePolicy.writer);
writers.addAll(headerWriters);
return writers;
}
LogoutFilter 允许定制LogoutHandler , 这一点在构造函数里面就能看到
可以看到 , 默认使用 logout 地址作为拦截请求
public LogoutFilter(LogoutSuccessHandler logoutSuccessHandler,
LogoutHandler... handlers) {
this.handler = new CompositeLogoutHandler(handlers);
Assert.notNull(logoutSuccessHandler, "logoutSuccessHandler cannot be null");
this.logoutSuccessHandler = logoutSuccessHandler;
setFilterProcessesUrl("/logout");
}
// LogoutFilter doFilter 逻辑
M- doFilter
?- 只要的操作就是调用handler 执行 logout 逻辑 , 并且调用 LogoutSuccess 逻辑
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
if (requiresLogout(request, response)) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
// 执行 LogoutHandler 的实现类
this.handler.logout(request, response, auth);
// 执行 LogoutSuccessHandler 的实现类
logoutSuccessHandler.onLogoutSuccess(request, response, auth);
return;
}
chain.doFilter(request, response);
}
// PS : 这个 Filter 有一定局限性 , 无法处理多个 Handler , 可以考虑定制一个 Filter
// Handler 实现类我们后续再深入
之前了解到 , 为了使同步器令牌模式能够防止 CSRF 攻击,必须在 HTTP 请求中包含实际的 CSRF 令牌。这必须包含在浏览器不会自动包含在 HTTP 请求中的请求的一部分(即表单参数、 HTTP 头等)中。
Spring Security 的 CsrfFilter 将一个 CsrfToken 作为一个名为 _csrf 的 HttpServletRequest 公开属性
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
request.setAttribute(HttpServletResponse.class.getName(), response);
// 加载 CsrfToken
CsrfToken csrfToken = this.tokenRepository.loadToken(request);
final boolean missingToken = csrfToken == null;
if (missingToken) {
// 缺失则重新创建一个
csrfToken = this.tokenRepository.generateToken(request);
this.tokenRepository.saveToken(csrfToken, request, response);
}
request.setAttribute(CsrfToken.class.getName(), csrfToken);
request.setAttribute(csrfToken.getParameterName(), csrfToken);
if (!this.requireCsrfProtectionMatcher.matches(request)) {
filterChain.doFilter(request, response);
return;
}
// 跨域后比对实际Token
String actualToken = request.getHeader(csrfToken.getHeaderName());
if (actualToken == null) {
actualToken = request.getParameter(csrfToken.getParameterName());
}
if (!csrfToken.getToken().equals(actualToken)) {
if (missingToken) {
this.accessDeniedHandler.handle(request, response,new MissingCsrfTokenException(actualToken));
} else {
this.accessDeniedHandler.handle(request, response,new InvalidCsrfTokenException(csrfToken, actualToken));
}
return;
}
filterChain.doFilter(request, response);
}
如果缓存的请求与当前请求匹配,则负责重新构造已保存的请求
整个核心代码主要是 2句话:其中主要是封装了一个新得 wrappedSavedRequest
HttpServletRequest wrappedSavedRequest = requestCache.getMatchingRequest(
(HttpServletRequest) request, (HttpServletResponse) response);
chain.doFilter(wrappedSavedRequest == null ? request : wrappedSavedRequest,response);
SecurityContextHolderAwareRequestFilter 一个过滤器,它使用实现servlet API安全方法的请求包装器填充ServletRequest 简单点说 , 就是一个封装 Request 的 Filter , 封装的 HttpServletRequest 提供了很多额外的功能
SessionManagementFilter SessionManagementFilter 中提供了多个对象用于在用户已经认证后进行 Session 会话活动 ,** 激活会话固定保护机制或检查多个并发登录**
作用 : 处理过滤器链中抛出的任何AccessDeniedException和AuthenticationException。
如果检测到AuthenticationException,过滤器将启动authenticationEntryPoint。这允许通用地处理来自AbstractSecurityInterceptor的任何子类的身份验证失败。
sendStartAuthentication(request, response, chain,(AuthenticationException) exception);
如果检测到AccessDeniedException,筛选器将确定该用户是否是匿名用户。
sendStartAuthentication(request,response,chain,new InsufficientAuthenticationException(
messages.getMessage("ExceptionTranslationFilter.insufficientAuthentication","Full authentication is required to access this resource")));
(PS : 因为是链式结构 , 所以他作为最后一个 , 也是处在最外层的)
核心是通过一个 catch 来处理
try {
chain.doFilter(request, response);
}catch (Exception ex) {
Throwable[] causeChain = throwableAnalyzer.determineCauseChain(ex);
RuntimeException ase = (AuthenticationException) throwableAnalyzer.getFirstThrowableOfType(AuthenticationException.class, causeChain);
// 获取依次类型
if (ase == null) {
ase = (AccessDeniedException) throwableAnalyzer.getFirstThrowableOfType(
AccessDeniedException.class, causeChain);
}
if (ase != null) {
if (response.isCommitted()) {
throw new ServletException("Unable to handle the Spring Security Exception because the response is already committed.", ex);
}
// 专属处理 SpringSecurityException
handleSpringSecurityException(request, response, chain, ase);
}else {
if (ex instanceof ServletException) {
throw (ServletException) ex;
}else if (ex instanceof RuntimeException) {
throw (RuntimeException) ex;
}
throw new RuntimeException(ex);
}
}
如果应用程序没有抛出 AccessDeniedException 或 AuthenticationException,那么 ExceptionTranslationFilter 不会做任何事情。
谈到了 Filter , 肯定就要细聊 Filter 对应的业务 , 上面说了一些简单的 Filter 业务 , 这一段我们来说一说比较大的业务流程 :
我们在配置 Security 的时候 , 一般都会配置 Request Match 等参数 , 例如 :
http.authorizeRequests()
.antMatchers("/test/**").permitAll()
.antMatchers("/before/**").permitAll()
.antMatchers("/index").permitAll()
.antMatchers("/").permitAll()
.anyRequest().authenticated() //其它请求都需要校验才能访问
.and()
.formLogin()
.loginPage("/login") //定义登录的页面"/login",允许访问
.defaultSuccessUrl("/home") //登录成功后默认跳转到"list"
.successHandler(myAuthenticationSuccessHandler).failureHandler(myAuthenctiationFailureHandler).permitAll().and()
.logout() //默认的"/logout", 允许访问
.logoutSuccessUrl("/index")
.permitAll();
那么这些参数是怎么生效的呢 ?
Step End : 最终匹配对象
我们来根据整个业务流程逆推 , 其最终对象是一个 RequestMatcher 实现类
注意 , 我们其上的 antMatchers 类型会生成多种不同的实现类 :
.... 等等其他的就不详细说了
拿到实现类后 ,调用 实现类的matches 方法返回最终结果
public boolean matches(HttpServletRequest request) {
return requestMatcher.matches(request);
}
Step Start : 看看请求的起点
找到了最终的匹配点 , 后面就好说了 , 打个断点 , 整个调用链就清清楚楚了
// Step1 : FilterChainProxy
private void doFilterInternal(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
FirewalledRequest fwRequest = firewall.getFirewalledRequest((HttpServletRequest) request);
HttpServletResponse fwResponse = firewall.getFirewalledResponse((HttpServletResponse) response);
List<Filter> filters = getFilters(fwRequest);
//...... 省略
// 执行 Filter 链 , 如果没有相当于直接进去
if (filters == null || filters.size() == 0) {
chain.doFilter(fwRequest, fwResponse);
}
// Step2 : getFilters 过滤 Filter Chain
for (SecurityFilterChain chain : filterChains) {
// 如果地址匹配 , 则执行对象 Filter 链
if (chain.matches(request)) {
return chain.getFilters();
}
}
这里可以看到 , 如果没有被拦截成功的 ,最终应该就直接运行了 , 所以 Security 一切的起点都是 Filter
Security 通过 DelegatingFilterProxy 将 Security 融入到 WebFilter 的体系中 ,其主要流程为 :
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
// -> PIC51001 : delegate 对象结构
Filter delegateToUse = this.delegate;
if (delegateToUse == null) {
synchronized (this.delegateMonitor) {
delegateToUse = this.delegate;
if (delegateToUse == null) {
WebApplicationContext wac = findWebApplicationContext();
if (wac == null) {
throw new IllegalStateException("...");
}
delegateToUse = initDelegate(wac);
}
this.delegate = delegateToUse;
}
}
// Let the delegate perform the actual doFilter operation.
invokeDelegate(delegateToUse, request, response, filterChain);
}
protected void invokeDelegate(
Filter delegate, ServletRequest request, ServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
// 会调用到 FilterChainProxy , 正式进入 Security 体系
delegate.doFilter(request, response, filterChain);
}
PIC51001 : delegate 对象结构
继续补充 : delegate 的初始化 , 获取 FilterChain
protected Filter initDelegate(WebApplicationContext wac) throws ServletException {
String targetBeanName = getTargetBeanName();
// 这里的 TargetBeanName 为 springSecurityFilterChain
Filter delegate = wac.getBean(targetBeanName, Filter.class);
if (isTargetFilterLifecycle()) {
delegate.init(getFilterConfig());
}
return delegate;
}
Spring Security 的起点就是 Filter ,常用的功能都能通过 Filter 找到相关的痕迹 , 后续我们会继续分析更底层的东西, 来开枝散叶的看看底下经历了什么
Security 的 Filter 和 WebFilter 的本质一样 , Security 通过一个 DelegatingFilterProxy 将 SecurityFilterChain 集中到 Filter 体系中
FilterChain 的核心是一个 VirtualFilterChain , 每个请求过来都会有一个VirtualFilterChain 生成 ,其中会添加所有的 Filter 类
的 Filter 包括 :
Filter 会通过 matches 进行拦截 , 判断是否要执行 Filters 逻辑
阅读量:2014
点赞量:0
收藏量:0