Анализ исходного кода процесса запуска Spring Security | Третья пуля новичка Nuggets

Java задняя часть
Анализ исходного кода процесса запуска Spring Security | Третья пуля новичка Nuggets

Не предавай свою жизнь, не предавай себя.

клин

Я говорил об этом в предыдущих двухПроцесс аутентификации SpringSecurityиПроцесс аутентификации SpringSecurity, сегодня третий выпуск, даSpringSecurityпоследние штрихи,SpringSecurityпроцесс запуска.

Как и многие фильмы, после того, как они стали популярными, их сиквелы часто являются ранними историями предыдущих, о чем я собираюсь рассказать в этом третьем выпуске.SpringSecurity启动流程Это также «ранняя история», которая поможет вам по-настоящему распознатьSpringSecurityобщая картина.

В предыдущей статье, вSpringSecurityКогда цепочка фильтров в , мы часто понимаем ее как понятие, то есть мы знаем только, что есть такая штука, а также знаем, для чего она используется, но не знаем, какая цепочка фильтров сделана оф., как его создать, когда.

Сегодняшняя задача - понятьSpringSecurityЧто делает для нас автоматическая конфигурация, как она создает цепочку фильтров и как добавляет нашу пользовательскую конфигурацию к конфигурации по умолчанию.

Желаю хорошего урожая (лайк и смотри, неограниченная мана).

1. 📚Включить веб-безопасность

Давайте сначала посмотрим, как мы обычно используемSpringSecurityиз.

мы используемSpringSecurityвсегда создавайте новыйSpringSecurityСвязанный класс конфигурации, используйте его для наследованияWebSecurityConfigurerAdapter, затем аннотировать@EnableWebSecurity, то мы можем переписатьWebSecurityConfigurerAdapterМетод внутри для завершения нашей собственной пользовательской конфигурации.

нравится:

@EnableWebSecurity
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {

    }

}

Мы уже знаем, что наследованиеWebSecurityConfigurerAdapterЧтобы переписать конфигурацию, что делает эта аннотация?

от его названия@EnableWebSecurityМы, вероятно, можем догадаться, что это тот, который автоматически настраивает нас.SpringSecurityиздобросердечные люди.

@Retention(value = java.lang.annotation.RetentionPolicy.RUNTIME)
@Target(value = { java.lang.annotation.ElementType.TYPE })
@Documented
@Import({ WebSecurityConfiguration.class,
        SpringWebMvcImportSelector.class,
        OAuth2ImportSelector.class })
@EnableGlobalAuthentication
@Configuration
public @interface EnableWebSecurity {

    /**
     * Controls debugging support for Spring Security. Default is false.
     * @return if true, enables debug support with Spring Security
     */
    boolean debug() default false;
}

эммм, я думаю, что у всех должны быть знания, связанные с аннотациями, хорошо, раз у вас есть знания, связанные с аннотациями, я буду говорить об этом напрямую.

это@EnableWebSecurityЕсть два места, которые являются более важными:

  • Один@ImportАннотация импортирует три класса, последние два из которыхSpringSecurityЧто-то сделано для совместимости, совместимоSpringMVC,совместимыйSpringSecurityOAuth2, мы в основном смотрим на первый класс, импорт этого класса представляет собой загрузку содержимого этого класса.

  • два это@EnableGlobalAuthenticationэта заметка,@EnableWebSecurityВы еще не разобрались. Вот еще один. Эта аннотация также загружает класс конфигурации -AuthenticationConfiguration, глядя на его имя, вы должны знать, к какому классу он относится, верно?AuthenticationManagerСвязанные классы конфигурации, мы можем поговорить об этом позже.

В итоге,@EnableWebSecurityМожно сказать, что это помогает нам автоматически загружать два класса конфигурации:WebSecurityConfigurationиAuthenticationConfiguration(@EnableGlobalAuthenticationаннотация загружает этот класс конфигурации).

вWebSecurityConfigurationэто класс конфигурации, который помогает нам построить цепочку фильтров, иAuthenticationConfigurationвводит насAuthenticationManagerСвязанные класс конфигурации, мы в основном говорим о сегодняшнемWebSecurityConfiguration.

2. 📖Обзор исходного кода

Так как сказаноWebSecurityConfiguration, как обычно, сначала покажем вам исходный код, а неважное упростим:

@Configuration(proxyBeanMethods = false)
public class WebSecurityConfiguration implements ImportAware, BeanClassLoaderAware {
    private WebSecurity webSecurity;

    private Boolean debugEnabled;

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

    private ClassLoader beanClassLoader;

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

    
    @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();
    }


    @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);
        }

        webSecurityConfigurers.sort(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;
    }
    
}

Как показано в коде, сначалаWebSecurityConfigurationЭто класс конфигурации, который отмечен на классе@ConfigurationАннотация, функцию этой аннотации знают все, вот и ставлю все ленты в этот класс@BeanСоздайте экземпляр аннотированного компонента.

Два наиболее важных метода в этом классе:springSecurityFilterChainиsetFilterChainProxySecurityConfigurer.

springSecurityFilterChainударить по методу@BeanАннотация, любой может увидеть, что этот метод был созданspringSecurityFilterChain, но не волнуйтесь, мы не можем сначала посмотреть на этот метод, хотя он выше.

3. 📄SetFilterChainProxySecurityConfigurer

Сначала рассмотрим этот метод:setFilterChainProxySecurityConfigurer,Зачем?

Зачем?

потому что это@Autowiredаннотация, так что это большеspringSecurityFilterChainМетод выполняется первым, и из порядка загрузки системы нам нужно сначала посмотреть на него.

@AutowiredРоль здесь заключается в том, чтобы автоматически вводить два обязательных параметра для этого метода.Давайте сначала рассмотрим эти два параметра:

  • параметрobjectPostProcessorэто создатьWebSecurityПример вводится в него, так что вы можете сначала понять его.

  • параметрwebSecurityConfigurersЯвляетсяList, собственно всеWebSecurityConfigurerAdapterПодкласс , тогда, если мы определяем пользовательский класс конфигурации, мы фактически читаем нашу конфигурацию.

    На самом деле немного сложно понять, почему параметрSecurityConfigurer<Filter, WebSecurity>Этот тип может получитьWebSecurityConfigurerAdapterподкласс ?

    так какWebSecurityConfigurerAdapterДостигнутоWebSecurityConfigurer<WebSecurity>интерфейс, при этомWebSecurityConfigurer<WebSecurity>снова унаследовалSecurityConfigurer<Filter, T>, после уровня реализации и уровня отношений наследования,WebSecurityConfigurerAdapterнаконец сталSecurityConfigurerподкласс .

    в то время как параметрSecurityConfigurer<Filter, WebSecurity>Два общих параметра на самом деле играют роль фильтра, внимательно посмотрите на нашWebSecurityConfigurerAdapterРеализация и отношения наследования, вы можете найти в нашейWebSecurityConfigurerAdapterИменно этого типа.

хорошо, после разговора о параметрах, я думаю, мы можем посмотреть на код:

@Autowired(required = false)
public void setFilterChainProxySecurityConfigurer(
        ObjectPostProcessor<Object> objectPostProcessor,
        @Value("#{@autowiredWebSecurityConfigurersIgnoreParents.getWebSecurityConfigurers()}") List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers)
        throws Exception {

    // 创建一个webSecurity实例
    webSecurity = objectPostProcessor
            .postProcess(new WebSecurity(objectPostProcessor));
    if (debugEnabled != null) {
        webSecurity.debug(debugEnabled);
    }

    // 根据order排序
    webSecurityConfigurers.sort(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;
}

Согласно нашим комментариям, то, что делает этот код, можно разделить на несколько шагов:

  1. Экземпляр webSecurity создается и назначается переменным-членам.

  2. следующий заwebSecurityConfigurersпройти черезorderСортировать,orderпорядок загрузки.

  3. определить, одинаковые лиorderКласс конфигурации , если это произойдет, об ошибке будет сообщено напрямую.

  4. Сохраняем конфигурацию, вставляемwebSecurityв переменных-членах.

Вы можете напрямую понимать это как инициализацию переменных-членов и просто загружать конфигурацию нашего класса конфигурации, потому что все последующие операции инициализируются вокруг нее.webSecurityinstance и информацию о классе конфигурации, которую мы загрузили для этого.

Эти вещи можно разобрать и описать пошагово, но в таком случае написать статью действительно невозможно, да и сил расписывать подробно нет, я выбираю только эту основную нить с четкими следами. , если вы сможете понять порядок его загрузки после прочтения, это будет хорошо.

Точно так же, как вопросы интервью Spring будут спрашивать о порядке загрузки SpringBeans, SpringMVC спросит SpringMVC о текущем процессе запроса.

Если все понятно, то надо изучать исходный код, на начальном этапе нам нужно знать только один из его основных контекстов, при последующем использовании, если есть проблема, можно напрямую локализовать проблему, это уже очень просто Ну, обучение - это итеративный процесс.

4. 📃Цепочка фильтров SpringSecurity

После инициализации переменных и загрузки конфигурации мы начнем создавать цепочку фильтров, так что давайте сначалаsetFilterChainProxySecurityConfigurerЕсть причина, если мы не загрузим нашу пользовательскую конфигурацию, как мы узнаем, какие фильтры нужны, а какие нет, когда мы создаем цепочку фильтров.

@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();
    }

springSecurityFilterChainЛогика метода очень проста, если мы не загрузим пользовательский класс конфигурации, он загрузит для нас класс конфигурации по умолчанию, а затем вызовет этотbuildметод.

Увидев это знакомое название метода, вы должны знать, что это режим билдера, неважно, какой это режим, так как он вызывается, мы просто кликаем в нем.

public final O build() throws Exception {
        if (this.building.compareAndSet(false, true)) {
            this.object = doBuild();
            return this.object;
        }
        throw new AlreadyBuiltException("This object has already been built");
    }

build()путьwebSecurityродительский классAbstractSecurityBuilderметод в , который вызывает сноваdoBuild()метод.

@Override
protected final O doBuild() throws Exception {
    synchronized (configurers) {
        buildState = AbstractConfiguredSecurityBuilder.BuildState.INITIALIZING;
        
        // 空方法
        beforeInit();
        // 调用init方法
        init();

        buildState = AbstractConfiguredSecurityBuilder.BuildState.CONFIGURING;

        // 空方法
        beforeConfigure();
        // 调用configure方法
        configure();

        buildState = AbstractConfiguredSecurityBuilder.BuildState.BUILDING;

        // 调用performBuild
        O result = performBuild();

        buildState = AbstractConfiguredSecurityBuilder.BuildState.BUILT;

        return result;
    }
}

Вы можете видеть через мои аннотацииbeforeInit()иbeforeConfigure()пустые методы, Только действительно полезноinit(),configure()иperformBuild()метод.

Давайте сначала посмотримinit(),configure()метод.

private void init() throws Exception {
        Collection<SecurityConfigurer<O, B>> configurers = getConfigurers();

        for (SecurityConfigurer<O, B> configurer : configurers) {
            configurer.init((B) this);
        }

        for (SecurityConfigurer<O, B> configurer : configurersAddedInInitializing) {
            configurer.init((B) this);
        }
    }

private void configure() throws Exception {
        Collection<SecurityConfigurer<O, B>> configurers = getConfigurers();

        for (SecurityConfigurer<O, B> configurer : configurers) {
            configurer.configure((B) this);
        }
    }

Вы можете видеть в исходном коде, что мы сначала получаем информацию о нашем классе конфигурации, а затем вызываем сам класс конфигурации в цикле.init(),configure()метод.

Как упоминалось ранее, наш класс конфигурации наследуетсяWebSecurityConfigurerAdapterподкласс , в то время какWebSecurityConfigurerAdapterСноваSecurityConfigurerподклассы, всеSecurityConfigurerВсе подклассы должны быть реализованыinit(),configure()метод.

Итак, вотinit(),configure()На самом деле метод заключается в вызовеWebSecurityConfigurerAdapterпереписал самinit(),configure()метод.

вWebSecurityConfigurerAdapterсерединаconfigure()method - это пустой метод, поэтому нам просто нужно посмотреть наWebSecurityConfigurerAdapterсерединаinit()метод в порядке.

public void init(final WebSecurity web) throws Exception {
        final HttpSecurity http = getHttp();
        web.addSecurityFilterChainBuilder(http).postBuildAction(() -> {
            FilterSecurityInterceptor securityInterceptor = http
                    .getSharedObject(FilterSecurityInterceptor.class);
            web.securityInterceptor(securityInterceptor);
        });
    }

Его также можно разделить на два этапа:

  1. казненgetHttp()метод, где к инициализации добавляется множество фильтров.

  2. будетHttpSecurityположить вWebSecurity,будетFilterSecurityInterceptorположить вWebSecurity, о чем мы говорили в главе об аутентификацииFilterSecurityInterceptor.

Затем мы в основном смотрим на первый шагgetHttp()метод:

protected final HttpSecurity getHttp() throws Exception {
        http = new HttpSecurity(objectPostProcessor, authenticationBuilder,
                sharedObjects);
        if (!disableDefaults) {
            // @formatter:off
            http
                    .csrf().and()
                    .addFilter(new WebAsyncManagerIntegrationFilter())
                    .exceptionHandling().and()
                    .headers().and()
                    .sessionManagement().and()
                    .securityContext().and()
                    .requestCache().and()
                    .anonymous().and()
                    .servletApi().and()
                    .apply(new DefaultLoginPageConfigurer<>()).and()
                    .logout();
            // @formatter:on
            ClassLoader classLoader = this.context.getClassLoader();
            List<AbstractHttpConfigurer> defaultHttpConfigurers =
                    SpringFactoriesLoader.loadFactories(AbstractHttpConfigurer.class, classLoader);

            for (AbstractHttpConfigurer configurer : defaultHttpConfigurers) {
                http.apply(configurer);
            }
        }

        // 我们一般重写这个方法
        configure(http);
        return http;
    }

getHttp()внутри методаhttpКуча вызываемых методов - это все фильтры, первыйcsrf()Очевидно, это фильтр для предотвращения CSRF-атак, ниже есть еще много других, этоSpringSecurityТе фильтры, которые будут добавлены в цепочку фильтров по умолчанию.

Во-вторых, есть еще один ключевой момент — это предпоследняя строка кода, еще я добавил комментарий, мы вообще переписываем этот метод в нашем классе кастомной конфигурации, так что тут наша кастомная конфигурация вступает в силу.

Таким образом, в процессе инициализации этот метод сначала загружает свою собственную конфигурацию по умолчанию, а затем загружает нашу переписанную конфигурацию, так что комбинация этих двух становится конфигурацией по умолчанию, которую мы видим. (Если мы не перепишемconfigure(http)метод, он также будет иметь немного конфигурации по умолчанию, вы можете перейти к исходному коду, и вы все поймете. )

init(),configure()После того, как (пустой метод) заканчивается, он вызываетсяperformBuild()метод.

protected Filter performBuild() throws Exception {

    int chainSize = ignoredRequests.size() + securityFilterChainBuilders.size();

    List<SecurityFilterChain> securityFilterChains = new ArrayList<>(
            chainSize);

    for (RequestMatcher ignoredRequest : ignoredRequests) {
        securityFilterChains.add(new DefaultSecurityFilterChain(ignoredRequest));
    }

    // 调用securityFilterChainBuilder的build()方法
    for (SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder : securityFilterChainBuilders) {
        securityFilterChains.add(securityFilterChainBuilder.build());
    }

    FilterChainProxy filterChainProxy = new FilterChainProxy(securityFilterChains);

    if (httpFirewall != null) {
        filterChainProxy.setFirewall(httpFirewall);
    }

    filterChainProxy.afterPropertiesSet();

    Filter result = filterChainProxy;
    
    postBuildAction.run();
    return result;
    }

Этот метод в основном должен видеть, чтобы вызватьsecurityFilterChainBuilderизbuild()метод, этоsecurityFilterChainBuilderмы вinit()Тот, что добавлен в метод, так что здесьsecurityFilterChainBuilderНа самом деле этоHttpSecurity, так что здесь на самом деле звонитHttpSecurityизbulid()метод.

это снова мы,WebSecurityизbulid()Метод еще не закончен, давайте сначалаHttpSecurityизbulid()метод.

HttpSecurityизbulid()Процесс метода такой же, как и раньше, также первыйinit()потомconfigure()НаконецperformBuild()метод, стоит отметить, что вHttpSecurityизperformBuild()Внутри метода фильтры в цепочке фильтров сортируются:

@Override
    protected DefaultSecurityFilterChain performBuild() {
        filters.sort(comparator);
        return new DefaultSecurityFilterChain(requestMatcher, filters);
    }

HttpSecurityизbulid()После выполнения методаDefaultSecurityFilterChainВернуться кWebSecurityизperformBuil()метод,performBuil()метод преобразования его вFilterChainProxy,НаконецWebSecurityизperformBuil()Когда выполнение метода завершается, он возвращаетFilterввести, чтобы статьname="springSecurityFilterChain"изBean.

После вышеуказанных шагов,springSecurityFilterChainПосле выполнения метода создается наша цепочка фильтров,SpringSecurityВы также можете бежать.

постскриптум

Видя это, вы на самом деле очень терпеливы, но вы все еще можете чувствовать туман, потому чтоSpringSecurity(Семья Spring) Этот высокотехнологичный проект полон различных шаблонов проектирования и идей кодирования. Когда вы этого не понимаете, вы можете только сказать, что это такое. Когда вы понимаете это, вы должны поклоняться ему как искусству.

Эти вещи нелегко понять, но они относительно несвязаны и легко расширяются. Как строку кода, ее легко понять, но нелегко расширить. Удача и невезение зависят друг от друга.

И есть так много имен классов с похожими именами, различных абстракций наследования, это действительно не так просто понять, эта статья на самом деле хочет дать этоSpringSecurityПодхожу к концу, заставляю себя писать, я люблю, чтобы было начало и конец, эта часть действительно сложная, в следующих статьях напишу несколько практических и интересных и отдохну.

если ты правSpringSecurityЕсли вас интересует исходный код, вы можете следить за моей статьей, щелкнуть свой собственный исходный код, посмотреть, давай.

посколькуПредыдущий запрос документовПосле того, как я опубликовал его, я чувствую, что стало намного больше фолловеров фронтенда.У Наггетс действительно все еще много фолловеров фронтенда. Это нормально, хотя я мало пишу о фронтенде, может быть, однажды изменит мою карьеру, ха-ха.

Я тоже этого не скрываю.Вообще-то бэкенд сейчас пишу.Могу только сказать,что немного разбираюсь в фронтенде,но если вам скучно,то можете зайти и почитать мой статьи, ставь лайк и читай.Что ты делаешь?👍,может однажды я вдруг пойму какую-то статью и уговорю фронтенда бросить а бэкенда войти в индустрию,давай все.

Не предавай свою жизнь, не предавай себя.

Каждый ваш лайк, подборка и комментарий - отличное подтверждение моих знаний.Если есть какие-то ошибки или сомнения в тексте, или какой-то совет для меня, вы можете оставить сообщение под областью комментариев и обсудить вместе.

Я Ear, псевдолитературный программист, который всегда хотел выводить знания, увидимся в следующем выпуске.