天天看點

Spring Security Config : WebSecurityConfiguration Web 安全配置概述源代碼分析

概述

當我們使用注解

@EnableWebSecurity

啟用

Spring Security

時,其實導入了一個配置類

WebSecurityConfiguration

,如下所示:

@Import({ WebSecurityConfiguration.class,
		SpringWebMvcImportSelector.class,
		OAuth2ImportSelector.class })
@Configuration
public @interface EnableWebSecurity {
	boolean debug() default false;
}
           

該配置類

WebSecurityConfiguration

使用一個

WebSecurity

對象基于使用者指定的或者預設的安全配置,建立一個

FilterChainProxy bean

來對使用者請求進行安全過濾。這個

FilterChainProxy bean

的名稱為

springSecurityFilterChain

,它也是一個

Filter

,最終會被作為

Servlet

過濾器鍊中的一個

Filter

應用到

Servlet

容器中。

你可以認為

WebSecurityConfiguration

Spring Web

安全過濾器

springSecurityFilterChain

的提供方。至于該安全過濾器如何被擷取和使用,我們在其他文章中分析。

這裡的安全配置可能來自

XML

配置,也可能來自

Java

配置類。在基于

Springboot

web

應用中,通常基于

WebSecurityConfigurerAdapter

的某個子類,該子類由開發人員實作并帶上注解

@Configuration

,用于進行定制安全配置。

源代碼版本 Spring Security Config 5.1.2.RELEASE

源代碼分析

package org.springframework.security.config.annotation.web.configuration;

// 忽略 import 行


/**
* Spring Web Security 的配置類 : 
*  1. 使用一個 WebSecurity 對象基于安全配置建立一個 FilterChainProxy 對象來對使用者請求進行安全過濾。 
*  2. 也會暴露一些必要的 bean。
*  3. 如何定制 Spring security 的web 安全,也就是 WebSecurity 對象 ?
*     3.1 實作一個繼承自 WebSecurityConfigurerAdapter 的配置類 , 
*     3.2 或者 提供一個配置類,實作了接口 WebSecurityConfigurer
*    該配置類的配置會在使用 @EnableWebSecurity 時應用到系統。
*
* Uses a WebSecurity to create the FilterChainProxy that performs the web based security 
* for Spring Security. It then exports the necessary beans. Customizations can be made to 
* WebSecurity by extending WebSecurityConfigurerAdapter and exposing it as a Configuration 
* or implementing WebSecurityConfigurer and exposing it as a Configuration. This configuration 
* is imported when using EnableWebSecurity.
*
* @see EnableWebSecurity
* @see WebSecurity
*
* @author Rob Winch
* @author Keesun Baik
* @since 3.2
*/
@Configuration
public class WebSecurityConfiguration implements ImportAware, BeanClassLoaderAware {
    private WebSecurity webSecurity;

    // 是否啟用了調試模式,來自注解 @EnableWebSecurity 的屬性 debug,預設值 false
    private Boolean debugEnabled;

    private List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers;

    private ClassLoader beanClassLoader;

    @Autowired(required = false)
    private ObjectPostProcessor<Object> objectObjectPostProcessor;

    @Bean
    public static DelegatingApplicationListener delegatingApplicationListener() {
        return new DelegatingApplicationListener();
    }

    // 定義一個bean,是表達式處理器,預設為一個 DefaultWebSecurityExpressionHandler,
    //  僅在 bean springSecurityFilterChain 執行個體化之後才能執行個體化
    @Bean
    @DependsOn(AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
    public SecurityExpressionHandler<FilterInvocation> webSecurityExpressionHandler() {
        return webSecurity.getExpressionHandler();
    }

    /**
     * Creates the Spring Security Filter Chain 
     * 定義 Spring Security Filter Chain  , 名字為 springSecurityFilterChain
     * @return
     * @throws Exception
     */
    @Bean(name = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
    public Filter springSecurityFilterChain() throws Exception {
        boolean hasConfigurers = webSecurityConfigurers != null
                && !webSecurityConfigurers.isEmpty();
        if (!hasConfigurers) {
            // 如果沒有 webSecurityConfigurer, 則提供一個卻省的
            WebSecurityConfigurerAdapter adapter = objectObjectPostProcessor
                    .postProcess(new WebSecurityConfigurerAdapter() {
                    });
            webSecurity.apply(adapter);
        }
        
        // 根據配置 webSecurityConfigurers或者預設 WebSecurityConfigurerAdapter 建構 
        // Filter FilterChainProxy 并傳回之,這是最終加入到Servlet容器的Filter chain
        // 中的一個 Filter, 但實際上,它的内部也維護了一個自己的安全相關的 Filter chain。
        return webSecurity.build();
    }

     // 定義一個bean,是web調用權限評估器,用于判斷一個使用者是否可以通路某個URL,
    // 對于 JSP tag 支援必要。 僅在bean springSecurityFilterChain 被定義時才生效。
    /**
     * Creates the WebInvocationPrivilegeEvaluator that is necessary for the JSP
     * tag support.
     * @return the WebInvocationPrivilegeEvaluator
     * @throws Exception
     */
    @Bean
    @DependsOn(AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
    public WebInvocationPrivilegeEvaluator privilegeEvaluator() throws Exception {
        return webSecurity.getPrivilegeEvaluator();
    }

    /**
     * Sets the <SecurityConfigurer<FilterChainProxy, WebSecurityBuilder>
     * instances used to create the web configuration.
     *
     * @param objectPostProcessor the ObjectPostProcessor used to create a
     * WebSecurity instance
     * @param webSecurityConfigurers the
     * <SecurityConfigurer<FilterChainProxy, WebSecurityBuilder> instances used to
     * create the web configuration 用于建立web configuration的SecurityConfigurer執行個體,
     * 注意該參數通過@Value(...)方式注入,對應的bean autowiredWebSecurityConfigurersIgnoreParents
     * 也在該類中定義
     * @throws Exception
     */
    @Autowired(required = false)
    public void setFilterChainProxySecurityConfigurer(
            ObjectPostProcessor<Object> objectPostProcessor,
            @Value("#{@autowiredWebSecurityConfigurersIgnoreParents.getWebSecurityConfigurers()}") 
            List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers)
            throws Exception {
        webSecurity = objectPostProcessor
                .postProcess(new WebSecurity(objectPostProcessor));
        if (debugEnabled != null) {
            webSecurity.debug(debugEnabled);
        }

        Collections.sort(webSecurityConfigurers, AnnotationAwareOrderComparator.INSTANCE);

        Integer previousOrder = null;
        Object previousConfig = null;
        for (SecurityConfigurer<Filter, WebSecurity> config : webSecurityConfigurers) {
            Integer order = AnnotationAwareOrderComparator.lookupOrder(config);
            if (previousOrder != null && previousOrder.equals(order)) {
                throw new IllegalStateException(
                        "@Order on WebSecurityConfigurers must be unique. Order of "
                                + order + " was already used on " + previousConfig + ", so it cannot be used on "
                                + config + " too.");
            }
            previousOrder = order;
            previousConfig = config;
        }
        for (SecurityConfigurer<Filter, WebSecurity> webSecurityConfigurer : webSecurityConfigurers) {
            webSecurity.apply(webSecurityConfigurer);
        }
        this.webSecurityConfigurers = webSecurityConfigurers;
    }

	// 定義一個bean,類型為AutowiredWebSecurityConfigurersIgnoreParents,其作用為從Spring容器中
	// 擷取所有類型為WebSecurityConfigurer的bean,這些bean就是要應用的安全配置原料
    @Bean
    public AutowiredWebSecurityConfigurersIgnoreParents autowiredWebSecurityConfigurersIgnoreParents(
            ConfigurableListableBeanFactory beanFactory) {
        return new AutowiredWebSecurityConfigurersIgnoreParents(beanFactory);
    }

    /**
     * A custom verision of the Spring provided AnnotationAwareOrderComparator that uses
     * AnnotationUtils#findAnnotation(Class, Class) to look on super class
     * instances for the Order annotation.
     *
     * @author Rob Winch
     * @since 3.2
     */
    private static class AnnotationAwareOrderComparator extends OrderComparator {
        private static final AnnotationAwareOrderComparator INSTANCE = new AnnotationAwareOrderComparator();

        @Override
        protected int getOrder(Object obj) {
            return lookupOrder(obj);
        }

        private static int lookupOrder(Object obj) {
            if (obj instanceof Ordered) {
                return ((Ordered) obj).getOrder();
            }
            if (obj != null) {
                Class<?> clazz = (obj instanceof Class ? (Class<?>) obj : obj.getClass());
                Order order = AnnotationUtils.findAnnotation(clazz, Order.class);
                if (order != null) {
                    return order.value();
                }
            }
            return Ordered.LOWEST_PRECEDENCE;
        }
    }

    /*
     * 擷取導入該配置bean的配置bean上的注解中繼資料并設定到該配置bean
     *  這裡主要是為了擷取注解 @EnableWebSecurity 的屬性 debugEnabled
     *
     * @see org.springframework.context.annotation.ImportAware#setImportMetadata(org.
     * springframework.core.type.AnnotationMetadata)
     */
    public void setImportMetadata(AnnotationMetadata importMetadata) {
        Map<String, Object> enableWebSecurityAttrMap = importMetadata
                .getAnnotationAttributes(EnableWebSecurity.class.getName());
        AnnotationAttributes enableWebSecurityAttrs = AnnotationAttributes
                .fromMap(enableWebSecurityAttrMap);
        debugEnabled = enableWebSecurityAttrs.getBoolean("debug");
        if (webSecurity != null) {
            webSecurity.debug(debugEnabled);
        }
    }

    /*
     *
     *
     * @see
     * org.springframework.beans.factory.BeanClassLoaderAware#setBeanClassLoader(java.
     * lang.ClassLoader)
     */
    public void setBeanClassLoader(ClassLoader classLoader) {
        this.beanClassLoader = classLoader;
    }
}

           

AutowiredWebSecurityConfigurersIgnoreParents

這是一個工具類,從目前

bean

容器中擷取所有的

WebSecurityConfigurer bean

。這些

WebSecurityConfigurer

通常是由開發人員實作的配置類,并且繼承自

WebSecurityConfigurerAdapter

上面引用到的類

AutowiredWebSecurityConfigurersIgnoreParents

:

/**
 * 一個工具類,從目前bean容器中擷取所有的WebSecurityConfigurer執行個體。
 *
 * @author Rob Winch
 *
 */
final class AutowiredWebSecurityConfigurersIgnoreParents {

	private final ConfigurableListableBeanFactory beanFactory;

	public AutowiredWebSecurityConfigurersIgnoreParents(
			ConfigurableListableBeanFactory beanFactory) {
		Assert.notNull(beanFactory, "beanFactory cannot be null");
		this.beanFactory = beanFactory;
	}

	@SuppressWarnings({ "rawtypes", "unchecked" })
	public List<SecurityConfigurer<Filter, WebSecurity>> getWebSecurityConfigurers() {
		List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers = 
			new ArrayList<SecurityConfigurer<Filter, WebSecurity>>();
		Map<String, WebSecurityConfigurer> beansOfType = beanFactory
				.getBeansOfType(WebSecurityConfigurer.class);
		for (Entry<String, WebSecurityConfigurer> entry : beansOfType.entrySet()) {
			webSecurityConfigurers.add(entry.getValue());
		}
		return webSecurityConfigurers;
	}
}
           

繼續閱讀