Spring Security 官方文档分析

Spring Security 源码分析

【基于官方文档】链接

1、Architecture

This section discusses Spring Security’s high-level architecture within Servlet based applications. We build on this high-level understanding within the Authentication, Authorization, and Protection Against Exploits sections of the reference.

​ 本章讨论基于 servlet 的应用中 spring security 架构。基于认证,授权。

2、 A Review of Filters

Spring Security’s Servlet support is based on Servlet Filters, so it is helpful to look at the role of Filters generally first. The following image shows the typical layering of the handlers for a single HTTP request.

spring security 基于 servlet 过滤器链,下图展示了处理单个 http 请求处理的经典分层。

在这里插入图片描述

The client sends a request to the application, and the container creates a FilterChain, which contains the Filter instances and Servlet that should process the HttpServletRequest, based on the path of the request URI. In a Spring MVC application, the Servlet is an instance of DispatcherServlet. At most, one Servlet can handle a single HttpServletRequest and HttpServletResponse. However, more than one Filter can be used to:

客户端向应用发送请求,servlet容器创建 FilterChain过滤器链,过滤器链中包含过滤器实例及servlet,应当根据请求的 URI 处理 `HttpServletRequest。一个“Servlet”可以处理一个“HttpServletRequest”和“HttpServletResponse”,但可以使用多个过滤器。

  • Prevent downstream Filter instances or the Servlet from being invoked. In this case, the Filter typically writes the HttpServletResponse.

  • Modify the HttpServletRequest or HttpServletResponse used by the downstream Filter instances and the Servlet.

The power of the Filter comes from the FilterChain that is passed into it.

下游的 HttpServletRequest 或者 HttpServletResponse可以被过滤器链中的过滤器修改,举例如下。

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
	// do something before the rest of the application
    chain.doFilter(request, response); // invoke the rest of the application
    // do something after the rest of the application
}

3、DelegatingFilterProxy

Spring provides a Filter implementation named DelegatingFilterProxy that allows bridging between the Servlet container’s lifecycle and Spring’s ApplicationContext. The Servlet container allows registering Filter instances by using its own standards, but it is not aware of Spring-defined Beans. You can register DelegatingFilterProxy through the standard Servlet container mechanisms but delegate all the work to a Spring Bean that implements Filter.

spring 提供了 DelegatingFilterProxy 的过滤器实现,用于将 servlet容器与 spring 的 ApplicationContext桥接起来。Servlet 容器运气用齐自己的标准注册过滤器实例,但是其并不知道 spring 的定义。你可以通过标准的Servlet容器机制注册 DelegatingFilterProxy ,但将所有工作委托给实现 Filter 的 Spring Bean。

Here is a picture of how DelegatingFilterProxy fits into the Filter instances and the FilterChain.

在这里插入图片描述

3.1 Source Analysis

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
	// Lazily get Filter that was registered as a Spring Bean
	// For the example in DelegatingFilterProxy delegate is an instance of Bean Filter0
	Filter delegate = getFilterBean(someBeanName);
	// delegate work to the Spring Bean
	delegate.doFilter(request, response);
}

3.2 DelegatingFilterProxy Bean Register In Springboot

通过 AbstractFilterRegistrationBeanDelegatingFilterProxy注入 servlet.

3.2.1 SecurityFilterAutoConfiguration

spring boot 下的注入

// springSecurityFilterChain	
private static final String DEFAULT_FILTER_NAME = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME;

	@Bean
	@ConditionalOnBean(name = DEFAULT_FILTER_NAME)
	public DelegatingFilterProxyRegistrationBean securityFilterChainRegistration(
			SecurityProperties securityProperties) {
    // 详情见 下述 3.3.2
		DelegatingFilterProxyRegistrationBean registration = new DelegatingFilterProxyRegistrationBean(
				DEFAULT_FILTER_NAME);
		registration.setOrder(securityProperties.getFilter().getOrder());
		registration.setDispatcherTypes(getDispatcherTypes(securityProperties));
		return registration;
	}

3.2.2 DelegatingFilterProxyRegistrationBean

	@Override
	public DelegatingFilterProxy getFilter() {
		return new DelegatingFilterProxy(this.targetBeanName, getWebApplicationContext()) {

			@Override
			protected void initFilterBean() throws ServletException {
				// Don't initialize filter bean on init()
			}
		};
	}

3.2.3 AbstractFilterRegistrationBean

调用上述 getFilter() 方法 注入 servletContext 中

@Override
protected Dynamic addRegistration(String description, ServletContext servletContext) {
   Filter filter = getFilter();
   return servletContext.addFilter(getOrDeduceName(filter), filter);
}

4、FilterChainProxy

Spring Security’s Servlet support is contained within FilterChainProxy. FilterChainProxy is a special Filter provided by Spring Security that allows delegating to many Filter instances through SecurityFilterChain. Since FilterChainProxy is a Bean, it is typically wrapped in a DelegatingFilterProxy.

spring security servlet 支持包含在 FilterChainProxy中, FilterChainProxy是 spring security 提供的特殊的 Filter, 支持代理多个 FilterFilterChainProxy是一个 bean,其被包装在 DelegatingFilterProxy 内部。

在这里插入图片描述

4.1 FilterChainProxy Bean Definition In Spring

For Example in WebSecurityConfiguration

springSecurityFilterChain注入 spring 容器中

public static final String DEFAULT_FILTER_NAME = “springSecurityFilterChain”;

/**
 * Creates the Spring Security Filter Chain
 * @return the {@link Filter} that represents the security filter chain
 * @throws Exception
 */
@Bean(name = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
public Filter springSecurityFilterChain() throws Exception {
   boolean hasFilterChain = !this.securityFilterChains.isEmpty();
   if (!hasFilterChain) {
      this.webSecurity.addSecurityFilterChainBuilder(() -> {
         this.httpSecurity.authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated());
         this.httpSecurity.formLogin(Customizer.withDefaults());
         this.httpSecurity.httpBasic(Customizer.withDefaults());
         return this.httpSecurity.build();
      });
   }
   for (SecurityFilterChain securityFilterChain : this.securityFilterChains) {
      this.webSecurity.addSecurityFilterChainBuilder(() -> securityFilterChain);
   }
   for (WebSecurityCustomizer customizer : this.webSecurityCustomizers) {
      customizer.customize(this.webSecurity);
   }
   return this.webSecurity.build();
}

5、SecurityFilterChain

SecurityFilterChain is used by FilterChainProxy to determine which Spring Security Filter instances should be invoked for the current request.

FilterChainProxy将决定当前请求调用哪个 Filter实例.

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Dq0C8kD2-1677572639308)(/Users/qianyuhua/Documents/mine/painnote/source-code/spring-security/picture/securityfilterchain.png)]

The Security Filters in SecurityFilterChain are typically Beans, but they are registered with FilterChainProxy instead of DelegatingFilterProxy. FilterChainProxy provides a number of advantages to registering directly with the Servlet container or DelegatingFilterProxy. First, it provides a starting point for all of Spring Security’s Servlet support. For that reason, if you try to troubleshoot Spring Security’s Servlet support, adding a debug point in FilterChainProxy is a great place to start.

Security Filters 是由 FilterChainProxy 注册的,并不是 DelegatingFilterProxy

Second, since FilterChainProxy is central to Spring Security usage, it can perform tasks that are not viewed as optional. For example, it clears out the SecurityContext to avoid memory leaks. It also applies Spring Security’s HttpFirewall to protect applications against certain types of attacks.

In addition, it provides more flexibility in determining when a SecurityFilterChain should be invoked. In a Servlet container, Filter instances are invoked based upon the URL alone. However, FilterChainProxy can determine invocation based upon anything in the HttpServletRequest by using the RequestMatcher interface.

FilterChainProxy 中可以通过 RequestMatcher 决定调用哪一条 SecurityFilterChain

在这里插入图片描述

In the Multiple SecurityFilterChain figure, FilterChainProxy decides which SecurityFilterChain should be used. Only the first SecurityFilterChain that matches is invoked. If a URL of /api/messages/ is requested, it first matches on the SecurityFilterChain0 pattern of /api/**, so only SecurityFilterChain0 is invoked, even though it also matches on SecurityFilterChainn. If a URL of /messages/ is requested, it does not match on the SecurityFilterChain0 pattern of /api/**, so FilterChainProxy continues trying each SecurityFilterChain. Assuming that no other SecurityFilterChain instances match, SecurityFilterChainn is invoked.

Notice that SecurityFilterChain0 has only three security Filter instances configured. However, SecurityFilterChainn has four security Filter instanes configured. It is important to note that each SecurityFilterChain can be unique and can be configured in isolation. In fact, a SecurityFilterChain might have zero security Filter instances if the application wants Spring Security to ignore certain requests.

5.1 FilterChainProxy

	// 过滤器执行方法,FilterChainProxy 本质也是一个过滤器链
	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
        throws IOException, ServletException {
    // 判断是否需要清除上下文环境
    boolean clearContext = request.getAttribute(FILTER_APPLIED) == null;
    if (!clearContext) {
        // 不需要清除上下文环境,直接执行核心过滤器
        doFilterInternal(request, response, chain);
        return;
    }
    try {
        // 标记已应用过滤器
        request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
        // 执行核心过滤器
        doFilterInternal(request, response, chain);
    } catch (Exception ex) {
        // 处理异常情况
        // 获取异常堆栈信息
        Throwable[] causeChain = this.throwableAnalyzer.determineCauseChain(ex);
        // 查找是否存在RequestRejectedException异常
        Throwable requestRejectedException = this.throwableAnalyzer
                .getFirstThrowableOfType(RequestRejectedException.class, causeChain);
        if (!(requestRejectedException instanceof RequestRejectedException)) {
            // 如果没有RequestRejectedException异常则抛出原异常
            throw ex;
        }
        // 如果存在RequestRejectedException异常则使用处理器进行处理
        this.requestRejectedHandler.handle((HttpServletRequest) request, (HttpServletResponse) response,
                (RequestRejectedException) requestRejectedException);
    } finally {
        // 清除上下文环境
        this.securityContextHolderStrategy.clearContext();
        // 移除已应用过滤器的标记
        request.removeAttribute(FILTER_APPLIED);
    }
}

	private void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain)
			throws IOException, ServletException {
    // 获取防火墙信息
		FirewalledRequest firewallRequest = this.firewall.getFirewalledRequest((HttpServletRequest) request);
		HttpServletResponse firewallResponse = this.firewall.getFirewalledResponse((HttpServletResponse) response);
    // 通过防火墙信息,找到过滤器,执行,为空不进行安全处理,直接将请求传递给下一个过滤器。
		List<Filter> filters = getFilters(firewallRequest);
		if (filters == null || filters.size() == 0) {
			if (logger.isTraceEnabled()) {
				logger.trace(LogMessage.of(() -> "No security for " + requestLine(firewallRequest)));
			}
			firewallRequest.reset();
			this.filterChainDecorator.decorate(chain).doFilter(firewallRequest, firewallResponse);
			return;
		}
		if (logger.isDebugEnabled()) {
			logger.debug(LogMessage.of(() -> "Securing " + requestLine(firewallRequest)));
		}
    //如果过滤器列表不为空,则将请求交给FilterChainDecorator对象来执行安全过滤器。这里使用了一个名为reset的局部FilterChain对象,用于在安全过滤器链中执行完毕后重置路径过滤器。在reset对象中,将请求和响应对象传递给chain,然后在chain对象中执行安全过滤器。
		FilterChain reset = (req, res) -> {
			if (logger.isDebugEnabled()) {
				logger.debug(LogMessage.of(() -> "Secured " + requestLine(firewallRequest)));
			}
			// Deactivate path stripping as we exit the security filter chain
			firewallRequest.reset();
			chain.doFilter(req, res);
		};
		this.filterChainDecorator.decorate(reset, filters).doFilter(firewallRequest, firewallResponse);
	}

/**
 * Internal {@code FilterChain} implementation that is used to pass a request through
 * the additional internal list of filters which match the request.
 */
private static final class VirtualFilterChain implements FilterChain {
		... 
   // 循环链表,属于过滤器链的所有过滤器依次执行
   @Override
   public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
     // 过滤器位置已经达到了过滤器链的末尾,即 currentPosition == size,那么直接调用原始过滤器链的 doFilter 方法处理请求。 
     if (this.currentPosition == this.size) {
         // Servlet 里的 Filter,
         this.originalChain.doFilter(request, response);
         return;
      }
     // 获取下一个需要执行的过滤器,然后调用该过滤器的 doFilter 方法
      this.currentPosition++;
      Filter nextFilter = this.additionalFilters.get(this.currentPosition - 1);
      if (logger.isTraceEnabled()) {
         String name = nextFilter.getClass().getSimpleName();
         logger.trace(LogMessage.format("Invoking %s (%d/%d)", name, this.currentPosition, this.size));
      }
      // 调用
      nextFilter.doFilter(request, response, this);
   }

}

6. Handling Security Exceptions

The ExceptionTranslationFilter allows translation of AccessDeniedException and AuthenticationException into HTTP responses.

ExceptionTranslationFilter 可以把 AccessDeniedException and AuthenticationException 转换成 http 响应数据

ExceptionTranslationFilter is inserted into the FilterChainProxy as one of the Security Filters.

The following image shows the relationship of ExceptionTranslationFilter to other components:

在这里插入图片描述

  • number 1 First, the ExceptionTranslationFilter invokes FilterChain.doFilter(request, response) to invoke the rest of the application.

  • number 2 If the user is not authenticated or it is an AuthenticationException, then Start Authentication.

    如果用户没有经过身份验证或者它是一个’ AuthenticationException ',那么启动身份验证。

    • The SecurityContextHolder is cleared out.
    • The HttpServletRequest is saved so that it can be used to replay the original request once authentication is successful.
  • The AuthenticationEntryPoint is used to request credentials from the client. For example, it might redirect to a log in page or send a WWW-Authenticate header.

    • number 3 Otherwise, if it is an AccessDeniedException, then Access Denied. The AccessDeniedHandler is invoked to handle access denied. 否则,如果它是’ AccessDeniedException ‘,则Access Denied。调用’ AccessDeniedHandler '来处理被拒绝的访问。

6.1 ExceptionTranslationFilter

private void handleSpringSecurityException(HttpServletRequest request, HttpServletResponse response, FilterChain chain, RuntimeException exception) throws IOException, ServletException {
    // 如果用户没有经过身份验证或者它是一个' AuthenticationException ',那么启动身份验证。
    if (exception instanceof AuthenticationException) {
        this.logger.debug("Authentication exception occurred; redirecting to authentication entry point", exception);
        this.sendStartAuthentication(request, response, chain, (AuthenticationException)exception);
      // 如果它是' AccessDeniedException ',则*Access Denied*。调用' AccessDeniedHandler '来处理被拒绝的访问。
    } else if (exception instanceof AccessDeniedException) {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        if (!this.authenticationTrustResolver.isAnonymous(authentication) && !this.authenticationTrustResolver.isRememberMe(authentication)) {
            this.logger.debug("Access is denied (user is not anonymous); delegating to AccessDeniedHandler", exception);
            this.accessDeniedHandler.handle(request, response, (AccessDeniedException)exception);
        } else {
            this.logger.debug("Access is denied (user is " + (this.authenticationTrustResolver.isAnonymous(authentication) ? "anonymous" : "not fully authenticated") + "); redirecting to authentication entry point", exception);
            this.sendStartAuthentication(request, response, chain, new InsufficientAuthenticationException(this.messages.getMessage("ExceptionTranslationFilter.insufficientAuthentication", "Full authentication is required to access this resource")));
        }
    }
}

7. Saving Requests Between Authentication

As illustrated in Handling Security Exceptions, when a request has no authentication and is for a resource that requires authentication, there is a need to save the request for the authenticated resource to re-request after authentication is successful. In Spring Security this is done by saving the HttpServletRequest using a RequestCache implementation.

当请求没有身份验证,并且是针对需要身份验证的资源时,需要保存请求,以便经过身份验证的资源在身份验证成功后重新请求。

7.1. RequestCache

The HttpServletRequest is saved in the RequestCache. When the user successfully authenticates, the RequestCache is used to replay the original request. The RequestCacheAwareFilter is what uses the RequestCache to save the HttpServletRequest.

By default, an HttpSessionRequestCache is used. The code below demonstrates how to customize the RequestCache implementation that is used to check the HttpSession for a saved request if the parameter named continue is present.

@Bean
DefaultSecurityFilterChain springSecurity(HttpSecurity http) throws Exception {
	HttpSessionRequestCache requestCache = new HttpSessionRequestCache();
	requestCache.setMatchingRequestParameterName("continue");
	http
		// ...
		.requestCache((cache) -> cache
			.requestCache(requestCache)
		);
	return http.build();
}

7.2 Prevent the Request From Being Saved

There are a number of reasons you may want to not store the user’s unauthenticated request in the session. You may want to offload that storage onto the user’s browser or store it in a database. Or you may want to shut off this feature since you always want to redirect the user to the home page instead of the page they tried to visit before login.

To do that, you can use the NullRequestCache implementation.

@Bean
SecurityFilterChain springSecurity(HttpSecurity http) throws Exception {
    RequestCache nullRequestCache = new NullRequestCache();
    http
        // ...
        .requestCache((cache) -> cache
            .requestCache(nullRequestCache)
        );
    return http.build();
}

7.3 Prevent the Request From Being Saved

There are a number of reasons you may want to not store the user’s unauthenticated request in the session. You may want to offload that storage onto the user’s browser or store it in a database. Or you may want to shut off this feature since you always want to redirect the user to the home page instead of the page they tried to visit before login.

@Bean
SecurityFilterChain springSecurity(HttpSecurity http) throws Exception {
    RequestCache nullRequestCache = new NullRequestCache();
    http
        // ...
        .requestCache((cache) -> cache
            .requestCache(nullRequestCache)
        );
    return http.build();
}

8.RequestCacheAwareFilter

The RequestCacheAwareFilter uses the RequestCache to save the HttpServletRequest.

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值