Не предавай свою жизнь, не предавай себя.
клин
Я говорил об этом в предыдущих двухПроцесс аутентификации 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;
}
Согласно нашим комментариям, то, что делает этот код, можно разделить на несколько шагов:
-
Экземпляр webSecurity создается и назначается переменным-членам.
-
следующий за
webSecurityConfigurers
пройти черезorder
Сортировать,order
порядок загрузки. -
определить, одинаковые ли
order
Класс конфигурации , если это произойдет, об ошибке будет сообщено напрямую. -
Сохраняем конфигурацию, вставляем
webSecurity
в переменных-членах.
Вы можете напрямую понимать это как инициализацию переменных-членов и просто загружать конфигурацию нашего класса конфигурации, потому что все последующие операции инициализируются вокруг нее.webSecurity
instance и информацию о классе конфигурации, которую мы загрузили для этого.
Эти вещи можно разобрать и описать пошагово, но в таком случае написать статью действительно невозможно, да и сил расписывать подробно нет, я выбираю только эту основную нить с четкими следами. , если вы сможете понять порядок его загрузки после прочтения, это будет хорошо.
Точно так же, как вопросы интервью 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);
});
}
Его также можно разделить на два этапа:
-
казнен
getHttp()
метод, где к инициализации добавляется множество фильтров. -
будет
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, псевдолитературный программист, который всегда хотел выводить знания, увидимся в следующем выпуске.