Предисловие
Популярные общие рамки авторизации теперь включают Shiro of Apache и Spring Family Spring Security. Когда дело доходит до сегодняшней аутентификации микросервиса, нам нужно использовать нашу структуру авторизации для создания наших собственных услуг аутентификации. Сегодня премьер -министром был премьер -министром.
Spring Security в основном реализует аутентификацию (аутентификацию, решение, кто вы?) И контроль доступа (контроль доступа, то есть что вам разрешено делать?, Также известный как разрешение). Spring Security отделяет аутентификацию от архитектуры авторизации и предоставляет точки расширения.
Основные объекты
Основной код находится под пакетом Spring-Security Core. Чтобы понять весеннюю безопасность, вам нужно обратить внимание на основные объекты внутри.
SecurityContextholder, SecurityContext и Authentication
SecurityContextholder - это контейнер для хранения для SecurityContext. Threadlocal Storage используется по умолчанию, что означает, что методы SecurityContext доступны в том же потоке.
SecurityContext в основном хранит основную информацию приложения и представлен аутентификацией в Spring Security.
Получите принципал:
Object Princient = SecurityContextholder.getContext (). GetAuthentication (). GetPrincipal (); if (Принципиальный экземпляр userDetails) {String username = ((userDetails)В Spring Security вы можете взглянуть на определение аутентификации:
Аутентификация общедоступного интерфейса расширяет основную, сериализуемая {collection <? расширяет wreatedAuthority> getAuthorities (); / *** Обычно пароль*/ объект getCredentials (); /*** сохраняет дополнительную информацию о запросе аутентификации. Это может быть IP * адрес, серийный номер сертификата и т. Д. */ Object getDetails (); /*** Используется для определения того, является ли он аутентификацией. Если вы войдите в систему с именем пользователя и паролем, это обычно имя пользователя*/ Object getPrincipal (); / *** Аутентируется ли он*/ boolean isauthenticated (); void setauthatecented (логический Isauthenticated) бросает нелегальный каргментирование;}В практических приложениях обычно используется usernamepasswortauthenticationtoken:
Public Abstract Class AbstractAuthenticationToken Реалитирует аутентификацию, CredentialsScontainer {} открытый класс usernamepassWortAuthentication extravends AbstractAuthenticationToken {}Обычный процесс аутентификации обычно похож на следующем: создать usernamepasswordAuthenticationToken, а затем передать его аутентификации Manager для аутентификации (подробно описано позже). Если аутентификация будет передана, информация о аутентификации будет сохранена через SecurityContextholder.
UsernamepasswordAuthenticationtoken authenticationToken = new UsernamePassWordAuthenticationToken (loginvm.getUsername (), loginvm.getPassword ()); аутентификация аутентификации = this.
Userdetails и userdetailsservice
UserDetails - это ключевой интерфейс в Spring Security, который используется для представления принципала.
Общественный интерфейс userDetails Extends Serializable { / *** Информация о авторизации пользователя может быть понята как роль* / collection <? расширяет wreatedAuthority> getAuthorities (); / ** * Пароль пользователя * * @return the пароль */ string getPassword (); / *** Имя пользователя**/ string getUsername (); логический isaccountnonexpired (); логический isaccountnonlocked (); логический iscredentialsnonexpired (); Boolean Isenabled ();}UserDetails предоставляет необходимую информацию, необходимую для аутентификации. В фактическом использовании вы можете реализовать пользовательские работы самостоятельно и добавлять дополнительную информацию, такую как электронная почта, мобильная и другая информация.
В аутентификации принципал обычно является именем пользователя. Мы можем получить userdetails через принципал через userdetailsservice:
Общедоступный интерфейс userdetailsservice {userdetails lokuserbyusername (string username) выбрасывает usernamenotfoundexception;}UeldedAuthority
Как упомянуто в пользовательских сетях, предоставлена) может быть понят как роль, такая как Role_administrator или ROLE_HR_SUPERVISOR.
краткое содержание
Сертификация аутентификации
Аутентификация Manager
Аутентификация в основном достигается через интерфейс AuthenticationManager, который содержит только один метод:
Аутентификация публичного интерфейса Manage {аутентификация аутентификации (аутентификация аутентификации) выбрасывает AuthenticationException;}Метод Authenticate () в основном делает три вещи:
Authentication Exception - это исключение времени выполнения, которое обычно обрабатывается приложением общим образом. Код пользователя обычно не должен быть пойман и обрабатывается конкретно.
Реализация по умолчанию AuthenticationManager является ProviderManager, который делегирует набор экземпляров AuthenticationProvider для реализации аутентификации.
AuthenticationProvider и AuthenticationManager аналогичны аутентификации, оба содержат аутентификацию, но он имеет дополнительную поддержку метода, позволяющую запросить, поддерживает ли вызывающий абонент заданный тип аутентификации:
Аутентификация публичного интерфейса provider {аутентификация аутентификации (аутентификация аутентификации) выбрасывает AuthenticationException; логические поддержки (класс <?> аутентификация);}ProviderManager содержит набор аутентификационных провидеров. При выполнении аутентификации он пересекает поставщиков, а затем вызывает поддержку. Если поддерживается, он выполняет метод аутентификации, который пересекает текущего поставщика. Если поставщик успешно аутентифицируется, сломайте.
Общественная аутентификация аутентификации (аутентификация аутентификации) выбрасывает аутентификацию Exception {class <? расширяет аутентификацию> totest = Authentication.getClass (); AuthenticationException LastException = null; Результат аутентификации = null; Boolean Debug = logger.isdebugenabled (); для (AuthenticationProvider Provider: getProviders ()) {if (! Provider.supports (totest)) {продолжить; } if (debug) {logger.debug ("попытка аутентификации с использованием" + provider.getClass (). getName ()); } try {result = Provider.AuthentIcate (аутентификация); if (result! = null) {copyDetails (аутентификация, результат); перерыв; }} catch (accountStatusException e) {prepareException (e, аутентификация); // sec-546: избегайте опроса дополнительных поставщиков, если сбой ауты обусловлен // недопустимый статус учетной записи E; } catch (InternalAuthenticationServiceException e) {PrepareException (e, аутентификация); бросить E; } catch (AuthenticationException e) {astaxception = e; }} if (result == null && parent! = null) {// разрешить родителю попробовать. try {result = parent.authenticate (аутентификация); } catch (providernotfoundexception e) {// Игнорируйте, как мы будем бросать ниже, если никакого другого исключения не произошло до // вызовы родителей и родителя // может бросить providernotfound, даже если поставщик у ребенка уже // обрабатывал запрос} catch (AuthenticationException e) {LastException = E; }} if (result! = null) {if (erasecredentialSafterAuthentication && (result instanceOf creditentScontainer)) {// Аутентификация завершена. Удалить учетные данные и другие секретные данные // из результата аутентификации (((((учетные средства). } eventPublisher.publishauthenticationsUccess (result); результат возврата; } // родитель был нулевым или не аутентифицировал (или бросил исключение). if (lastexception == null) {astaxception = new providernotfoundexception (messages.getMessage ("ProviderManager.providerNOTFound", новый объект [] {totest.getName ()}, "Нет аутентификации provider, найденного для {0}")); } PrepareException (LastException, Authentication); Бросьте по -прежнему; }Как видно из вышеуказанного кода, поставщик Manager имеет дополнительного родителя. Если родитель не пуст, родитель. Аутикация (аутентификация) вызывается
Аутентификация Provider
AuthenticationProvider имеет много реализаций. Тот, который вас больше всего обеспокоен, обычно, это DaoauthenticationProvider, унаследованный от AbstractUserDetailSauthenticationProvider. Ядро состоит в том, чтобы реализовать аутентификацию через userdetails. DaoauthenticationProvider будет автоматически загружаться по умолчанию и не нужно настроить вручную.
Давайте сначала посмотрим на AbstractUserDetailSauthenticationProvider и посмотрим на наибольшую основную аутентификацию:
Общественная аутентификация аутентификации (аутентификация аутентификации) выбрасывает AuthenticationException {// быть usernamepasswordAuthenticationtoken assert.isinstanceof (usernamepasswordAuthentication.class, аутентификация, сообщения. Поддерживается ")); // Получить username string username = (authentication.getPrincipal () == null)? "Non_provided": authentication.getName (); Boolean Cachewasused = true; // Получить userdetails от cache user = this.usercache.getuserfromcache (username); if (user == null) {cachewasused = false; try {// retriveUser Abstract Метод получения пользователя пользователя = retriveUser (имя пользователя, (usernamepasswordAuthenticationtoken) аутентификация); } catch (usernameNotFoundException notFound) {logger.debug ("Пользователь '" + username + "' не найдено"); if (hideusernotfoundexceptions) {бросить новое BadcredentialSexception (messages.getMessage ("AbstractUserDetailSauthenticationProvider.badcredentials", "Плохие учетные данные")); } else {бросить notfound; }} Assert.notnull (пользователь, "retrieveuser вернул null - нарушение контракта на интерфейс"); } try {// предварительная проверка, defaultPreauthenticationChecks, проверьте, заблокирован ли пользователь или доступна ли учетная запись для preauthenticationChecks.check (user); // абстрактный метод, пользовательский проверяйте дополнительную аутентификацию. } catch (AuthenticationException Exception) {if (cachewasUsed) {// была проблема, поэтому попробуйте еще раз после проверки // Мы используем последние данные (то есть не из кэша) cachewasused = false; user = retriveUser (имя пользователя, (usernamepasswordAuthenticationtoken) аутентификация); preauthenticationChecks.check (пользователь); exotherauthenticationChecks (пользователь, (usernamepasswordAuthenticationtoken) аутентификация); } else {бросить исключение; }} // после проверки defaultpostauthenticationChecks, проверьте iscredentialSnonexpired postAuthenticationChecks.check (пользователь); if (! cachewasudes) {this.usercache.putuserincache (пользователь); } Object PrincyaltorErnur = user; if (forceprincipalasstring) {principaltorTurn = user.getUsername (); } return CreateSuccessAuthentication (PrincipAltorTurn, Authentication, пользователь); }Приведенный выше тест в основном основан на реализации userdetails, где логика получения и проверки пользователя реализована конкретными классами. Реализация по умолчанию является DaoAuthenticationProvider. Ядро этого класса заключается в том, чтобы позволить разработчикам предоставить userdetailsservice для получения userdetails и passwordEncoder, чтобы проверить, является ли пароль действительным:
private userdetailsservice userdetailsservice; Partyencoder PasswordEncoder;
Чтобы увидеть конкретную реализацию, retrieveUser, напрямую вызовите пользователь userdetailsservice, чтобы получить пользователя:
Защищенные конечные пользователи userdetails retiveuser (string username, usernamepasswordauthenticationtoken authentication) Throws AuthenticationException {userDetails LoadEduser; try {loadedUser = this.getUserDetailsService (). } catch (usernameNotFoundException notFound) {if (authentication.getCredentials ()! = null) {string presentedPassword = authentication.getCredentials (). toString (); PasswordEncoder.SpassWordValid (userNotFoundenCodEdPassword, PresentedPassword, NULL); } бросить notFound; } catch (Exception RepositoryProblem) {бросить новый InternalAuthenticationserviceException (RepositoryProblem.getMessage (), RepositoryProblem); } if (loadEduser == null) {бросить новый internalauthenticationserviceexception ("userDetailsService returned null, что является нарушением контракта на интерфейс"); } return LoadEduser; }Давайте посмотрим на проверку:
Protected void emproteAuthenticationChecks (userDetails userDetails, userNamePassWordAuthenticationToken Authentication) Throws AuthenticationException {Object Salt = null; if (this.saltsource! = null) {salt = this.saltsource.getsalt (userdetails); } if (Authentication.getCredentials () == null) {logger.debug ("Аутентификация не удалась: не предоставлены учетные данные"); бросить новое badcredentialsexception (сообщения.getmessage ("AbstractUserDetailSauthenticationProvider.badcredentials", "Плохие учетные данные")); } // Получить строку пароля пользователя PresentedPassword = authentication.getCredentials (). ToString (); // Сравните, совпадает ли пароль после PasswordEncoder, что пароль UserDetails if (! PasswordEncoder.SpassWordValid (userDetails.getPassword (), PresentedPassword, Salt)) {logger.debug («Ошибка аутентификации: пароль не соответствует сохранению значения»); бросить новое badcredentialsexception (сообщения.getmessage ("AbstractUserDetailSauthenticationProvider.badcredentials", "Плохие учетные данные")); }}Резюме: Чтобы настроить аутентификацию, используйте DaoAuthenticationProvider, вам нужно только предоставить его с помощью пароля и userdetailsservice.
Настройте менеджеры по аутентификации
Spring Security предоставляет аутентификацию класса Builder ManagerBuilder, которая позволяет быстро реализовать пользовательскую аутентификацию.
См. Официальное описание исходного кода:
SecurityBuilder использовал для создания аутентификации Manager. Позволяет легко создавать аутентификацию в памяти, аутентификацию LDAP, аутентификацию на основе JDBC, добавление UserDetailsService и добавление аутентификации Provider's.
AuthenticationManagerBuilder может использоваться для создания AuthenticationManager, который может создавать аутентификацию на основе памяти, аутентификацию LDAP, аутентификацию JDBC и добавлять userDetailsService и AuthenticationProvider.
Простое использование:
@Configuration@enablewebsecurity@enableglobalmethodsecurity (prefostenabled = true, securedenabled = true) public Class Applicationsecurity Extends websecurityConfigurerAdapter {public SecurityConfiguration (AuthenticationManagerBuilder AuthenticationManagerBuilder, userDetailSservice, пользовательский, usekenprisider, tokenprovider, tokenprovider, tokenprovider, tokenprovider, tokenprovider, tokenproviser Corsfilter, SecurityProblemsupport Проблемы Upport) {this.AuthenticationManagerBuilder = AuthenticationManagerBuilder; this.userdetailsservice = userDetailsService; this.tokenprovider = tokenProvider; this.corsfilter = corsfilter; this.problemsupport = wringsupport; } @Postconstruct public void init () {try {AuthenticationManagerBuilder .userDetailsService (userDetailsService) .passWordEncoder (passwordEncoder ()); } catch (Exception e) {бросить новое BeaninitializationException ("Конфигурация безопасности не удалась", E); }} @Override Protected void configure (httpsecurity http) выбрасывает исключение {http .addfilterbefore (corsfilter, usernamepasswordauthenticationfilter.class) .exceptionHandling () .AuthenticationEntryPoint (проблемы) .disable () .Headers () .FrameOptions () .disable (). и () .SessionManagement () .SessionCreationPolicy (SessionCreationPolicy.Staneless). и () .AuthorizeRequests () .antmatchers («/api/regive»). .antmatchers ("/api/outenticate"). armitall () .antmatchers ("/api/account/reset-password/init"). .antmatchers ("/api/**"). Authenticated () .antmatchers ("/Management/Health"). armitall () .antmatchers ("/Management/**"). hasauthority (othortionsConstants.Admin) .antMatchers ("/v2/api-doc/**").).) .antmatchers ("/swagger-resources/configururation/ui"). armitall () .antmatchers ("/swagger-ui/index.html"). hasauthority (worthesconstants.admin). и () .apply (securityConfigurerAdapter ()); }}Авторизация и контроль доступа
Как только аутентификация будет успешной, мы можем продолжать авторизовать, что реализуется через AccessDecisionManager. Существует три реализации фреймворта, по умолчанию основано на позитивной основе, которая производится через AccessDecisionVoter, что немного похоже на поставщика Mamanager для аутентификации провидеров для аутентификации.
Public void Решение (аутентификация аутентификации, объект объекта, коллекция <configattribute> configattributes) выбрасывает AccessDeniedException {int deny = 0; // Traversal DecisionVoter для (AccessDecisionVoter избиратель: getDecisionVoters ()) {// голосование int result = gohat.vote (аутентификация, объект, configattributes); if (logger.isdebugenabled ()) {logger.debug ("избиратель:" + избиратель + ", возвращен:" + result); } switch (result) {case accessDecisionVoter.access_granted: return; Case AccessDecisionVoter.Access_Denied: deny ++; перерыв; по умолчанию: перерыв; }} // veto if (deny> 0) {throw new accessdenieDexception (сообщения. } // Чтобы зайти так далеко, каждый AccessDecisionVoter воздержался от CheckAllowifallabStainDecisions (); }Давайте посмотрим на AccessDecisionVoter:
Boolean Supports (Attribute ConfigatTribute); Boolean Supports (Class <?> Clazz); int голосование (аутентификация аутентификации, S -объект, коллекция <configattribute> attributes);
Объект - это ресурс, к которому пользователь хочет получить доступ, а configattribute - это условие, которое необходимо выполнить объект. Обычно полезная нагрузка - это строка, такая как Role_admin. Итак, давайте посмотрим на реализацию Rolevoter. Ядро состоит в том, чтобы извлечь предоставленность из аутентификации, а затем сравнивать с конфигурацией, соответствуют ли условия.
Public Boolean Supports (ConfigatTribute Attribute) {if (((attribute.getAttribute ()! = null) && attribute.getAttribute (). startSwith (getRolePrefix ())) {return true; } else {return false; }} public boolean supports (class <?> clazz) {return true; } public int hoge (аутентификация аутентификации, объект объекта, коллекция <configattribute> attributes) {if (authentication == null) {return access_denied; } int result = access_abstain; // Получить предоставленную информацию об информационном разбирательстве <? расширяет wreatedAuthority> власти = ExtractAuthorities (аутентификация); for (configattribute attribute: attributes) {if (this.supports (attribute)) {// Доступ к отказанному по умолчанию = access_denied; // Попытка найти соответствующую предоставленную полномочия для (Управление по предоставленности: власти) {// определить, существует ли соответствующий орган, если (attribute.getAttribute (). Equals (atomer.getAuthority ())) {// Вы можете получить доступ к return access_granted; }}}} return result; }Здесь я должен спросить, откуда взялся ConfigatTribute? Фактически, это находится в конфигурации приложений.
Как реализовать веб -безопасность
Spring Security (для пользовательского интерфейса и HTTP Backends) на веб -уровне основана на фильтрах сервлета, а на следующем рисунке показана типичная иерархия обработчиков для одного запроса HTTP.
Spring Security зарегистрирована на веб -уровне через FilterChainProxy в качестве одного фильтра, фильтра внутри прокси.
FilterChainProxy эквивалентен фильтрующему контейнеру. Через VirtualFilterChain каждый внутренний фильтр называется последовательно.
public void dofilter (запрос ServletRequest, ответ ServletResponse, цепочка FilterChain) бросает ioException, ServletException {boolean clearContext = request.getAttribute (filter_applied) == null; if (clearContext) {try {request.setattribute (filter_applied, boolean.true); Dofilterinternal (запрос, ответ, цепь); } наконец {SecurityContexTholder.ClearContext (); request.removeattribute (filter_applied); }} else {dofilterInternal (запрос, ответ, цепь); }} private void dofilterinternal (запрос ServletRequest, ответ на сервис -ответ, цепочка FilterChain) бросает ioException, ServletException {FirewallEdRequest fwrequest = Firewall .getFireWallEdRequest ((httpservlectrequest) запрос); Httpservletresponse fwresponse = брандмауэр. GetfirewalledResponse ((httpservletresponse) ответ); Список <Filter> Filters = GetFilters (fwrequest); if (filters == null || filters.size () == 0) {if (logger.isdebugenabled ()) {logger.debug (urlutils.buildrequesturl (fwrequest) + (фильтры = null? » } fwrequest.reset (); chain.dofilter (fwrequest, fwresponse); возвращаться; } VirtualfilterChain vfc = new VirtualFilterChain (fwrequest, цепь, фильтры); vfc.dofilter (fwrequest, fwresponse); } Частный статический класс VirtualFilterChain реализует FilterChain {Private Final FilterChain OriginalChain; Частный окончательный список <Filter> Дополнительныефильтерные линии; Частный финальный брандмаулэдрекест FiremwermedReedRequest; частный финальный размер Int; Private Int CurrentPosition = 0; Private VirtualFilterChain (брандмалдреотскот, брандмахолдреклет, цепочка FilterChain, список <Filter> Дополнительныефильтеры) {this.originalChain = цепь; это. this.size = extrafilters.size (); this.firewalledRequest = FirewalledRequest; } public void dofilter (запрос ServletRequest, ответ servletresponse) бросает ioException, ServletException {if (currentposition == size) {if (logger.isdebugenabled ()) {logger.debug (urlutils.buildrequesturl (firewepledRequest) + "достигнут цепь; } // Деактивировать путь по поводу понижения, когда мы выходим из цепочки фильтров безопасности this.firewalledRequest.reset (); OriginalChain.dofilter (запрос, ответ); } else {currentPosition ++; Filter nextfilter = exoterfilters.get (currentPosition - 1); if (logger.isdebugenabled ()) {logger.debug (urlutils.buildrequesturl (брандмауллреклет) + "в позиции" + currentposition + "из" + size + "в цепочке дополнительного фильтра; Filter Filter:" + stectfilter.getClass (). GetSiMplename () + ""); } nextfilter.dofilter (запрос, ответ, это); }}}}обратиться к
https://spring.io/guides/topicals/spring-security-architecture/
https://docs.spring.io/spring-security/site/docs/5.0.5.release/reference/htmlsingle/#overall-architecture
Суммировать
Вышеуказанное - все содержание этой статьи. Я надеюсь, что содержание этой статьи имеет определенную справочную ценность для каждого обучения или работы. Если у вас есть какие -либо вопросы, вы можете оставить сообщение для общения. Спасибо за поддержку Wulin.com.