Spring Security
Что такое Tomcat?
Apache Tomcat – это программа (сервер), которая позволяет Java-приложениям работать в интернете. Если продолжить аналогию с рестораном, которую мы использовали ранее, то Tomcat – это как кухня ресторана, где происходит вся магия.
Как Tomcat работает?
Когда вы разрабатываете веб-приложение на Java (например, с использованием Spring Boot), вам нужно где-то запустить его, чтобы пользователи могли к нему обращаться через интернет. Tomcat выступает в роли специального сервера, который:
- Получает запросы от клиентов (например, когда кто-то заходит на ваш сайт).
- Передает эти запросы сервлетам (о которых мы говорили ранее).
- Возвращает ответы клиентам после обработки сервлетами.
Простыми словами
Tomcat – это "веб-сервер" или "контейнер сервлетов", который:
- Умеет принимать HTTP-запросы (то есть запросы, которые отправляет ваш браузер при переходе на сайт).
- Запускает ваши Java-приложения и обрабатывает запросы с помощью сервлетов.
Почему Tomcat нужен?
Java-приложения не могут самостоятельно работать в интернете. Для этого нужен сервер, который умеет обрабатывать HTTP-запросы и запускать Java-код. Tomcat как раз и является таким сервером.
Пример
Когда вы запускаете свое Spring Boot приложение, Tomcat автоматически стартует и начинает слушать запросы на определенном порту (например, на порту 8080). Когда вы заходите на http://localhost:8080/welcome
, Tomcat получает ваш запрос, передает его сервлету, сервлет формирует ответ, и Tomcat возвращает этот ответ вашему браузеру.
Таким образом, Tomcat – это программа, которая обеспечивает связь между вашими Java-приложениями и интернетом.
Хорошо, давайте объясню еще проще, что такое servlet.
Что такое сервлет?
Сервлет (Servlet) — это небольшой кусочек Java-кода, который живет на сервере и отвечает на запросы от клиентов (например, от браузеров).
Представьте себе сервлет как официанта в ресторане:
- Когда вы (клиент) приходите в ресторан и делаете заказ (отправляете запрос), официант (сервлет) берет ваш заказ и передает его на кухню (сервер).
- Затем он приносит вам блюдо, которое вы заказали (ответ на ваш запрос).
Как это работает в веб-приложении?
- Когда вы набираете в браузере адрес сайта, например
http://example.com/welcome
, ваш запрос отправляется на сервер. - Сервер получает запрос и передает его сервлету.
- Сервлет берет ваш запрос, обрабатывает его (например, проверяет, какой у вас запрос, какие данные нужны), и формирует ответ (например, HTML-страницу или простой текст "Добро пожаловать").
- Затем сервлет отправляет этот ответ обратно на сервер, а сервер возвращает его вашему браузеру.
Пример работы сервлета
- Вы заходите на сайт, и ваш запрос попадает на сервер.
- Сервлет видит, что вы запросили страницу "welcome".
- Он создает для вас сообщение "Welcome to Spring Boot!" и отправляет его обратно в ваш браузер.
Простыми словами
Сервлет – это "официант" на сервере, который принимает запросы от клиентов, обрабатывает их и отправляет обратно ответы.
1. Authentication
Who are you?
user/password -> parvin -> what can you do?
LDAP
2. Authorization
What you can do?
OAUTH
RBAC(Role base access control) - api filter
ABAC(attribute base access control) OPA(open policy agent)
AuthenticationManager
interface is a core component responsible for processing authentication requests. It serves as the primary interface for authenticating a user's credentials and determining whether they are valid.Authentication:
- The
authenticate
method takes anAuthentication
object as a parameter, which contains the user's credentials (e.g., username and password). - It returns a fully authenticated
Authentication
object if the authentication is successful. - If authentication fails, it throws an
AuthenticationException
.
- The
Implementation:
- The most common implementation of
AuthenticationManager
isProviderManager
, which delegates the authentication process to a list ofAuthenticationProvider
instances. AuthenticationProvider
is another interface that performs the actual authentication logic.
- The most common implementation of
AuthenticationManager
interface in Spring Security. It overrides the authenticate
method and is responsible for coordinating authentication using a list of AuthenticationProvider
instances. Let's take a closer look at how ProviderManager
works:
ProviderManager Overview
The ProviderManager
class is designed to delegate authentication requests to one or more AuthenticationProvider
instances. Each AuthenticationProvider
is responsible for a specific type of authentication (e.g., username/password, OAuth2, etc.).
Delegation to Providers:
ProviderManager
holds a list ofAuthenticationProvider
instances.- When the
authenticate
method is called, it iterates through eachAuthenticationProvider
in the list. - Each
AuthenticationProvider
attempts to authenticate theAuthentication
object.
Handling Authentication:
- If an
AuthenticationProvider
successfully authenticates the request, it returns a fully populatedAuthentication
object. - If an
AuthenticationProvider
cannot authenticate the request, it throws anAuthenticationException
. ProviderManager
catches the exception and continues to the next provider.
- If an
Exception Handling:
- If all
AuthenticationProvider
instances fail to authenticate the request,ProviderManager
throws the last caughtAuthenticationException
.
- If all
The AuthenticationProvider
interface in Spring Security plays a crucial role in the authentication process by allowing the system to support multiple authentication mechanisms in a modular and extensible way. While it's true that both AuthenticationManager
and AuthenticationProvider
have an authenticate
method, their purposes and scopes are different. Let's explore why the AuthenticationProvider
interface is necessary and how it fits into the overall authentication architecture.
Purpose of AuthenticationProvider
The AuthenticationProvider
interface allows Spring Security to support different authentication mechanisms through separate, focused components. Each AuthenticationProvider
implementation is responsible for a specific type of authentication.
Key Differences and Roles
AuthenticationManager:
- Role: Coordinates the overall authentication process.
- Responsibility: Delegates the authentication request to multiple
AuthenticationProvider
instances. - Scope: Broad, as it manages multiple providers.
AuthenticationProvider:
- Role: Performs the actual authentication logic.
- Responsibility: Authenticates a specific type of credential (e.g., username/password, OAuth2 token).
- Scope: Narrow, focused on one type of authentication.
The DaoAuthenticationProvider
class implements the AuthenticationProvider
interface in Spring Security. It is a specific implementation that uses a UserDetailsService
to retrieve user information from a data source (like a database) and a PasswordEncoder
to validate the user's credentials (password).
Overview of DaoAuthenticationProvider
The DaoAuthenticationProvider
is one of the most commonly used authentication providers in Spring Security. It performs authentication by loading user details from a UserDetailsService
and then checking the provided password against the stored password using a PasswordEncoder
.
Key Components
UserDetailsService:
- Responsible for retrieving user details (such as username, password, and authorities) from a data source.
- Common implementations include
InMemoryUserDetailsManager
,JdbcUserDetailsManager
, and custom implementations that query a database.
PasswordEncoder:
- Used to encode and match passwords.
- Common implementations include
BCryptPasswordEncoder
,Pbkdf2PasswordEncoder
, andNoOpPasswordEncoder
.
How DaoAuthenticationProvider Works
UserDetailsService:
- The
UserDetailsService
is used to load the user details by username. It returns aUserDetails
object that contains the user's information.
- The
PasswordEncoder:
- The
PasswordEncoder
is used to encode the password when saving a user and to match the raw password provided at login with the encoded password stored in the database.
- The
authenticate Method:
- The
DaoAuthenticationProvider
'sauthenticate
method retrieves the user details using theUserDetailsService
. - It then checks if the provided password matches the stored password using the
PasswordEncoder
.
- The
public interface UserDetailsService {
/**
* Locates the user based on the username. In the actual implementation, the search
* may possibly be case sensitive, or case insensitive depending on how the
* implementation instance is configured. In this case, the <code>UserDetails</code>
* object that comes back may have a username that is of a different case than what
* was actually requested..
* @param username the username identifying the user whose data is required.
* @return a fully populated user record (never <code>null</code>)
* @throws UsernameNotFoundException if the user could not be found or the user has no
* GrantedAuthority
*/
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}
AuthenticationManager
— это основной компонент в Spring Security, отвечающий за аутентификацию пользователей. Проще говоря, это "проверяющий", который получает учетные данные пользователя (например, имя пользователя и пароль), проверяет их и решает, допустить ли пользователя в систему или нет.
Объяснение простыми словами
Проверка пользователя: Когда вы вводите логин и пароль на сайте,
AuthenticationManager
принимает эти данные и проверяет, правильные ли они.Решение о доступе: Если данные верны, он разрешает вам войти и получить доступ к защищенным ресурсам. Если нет — отклоняет доступ.
Как это работает
Пользователь вводит свои данные: Вы вводите имя пользователя и пароль на странице входа.
Создается объект аутентификации: Эти данные упаковываются в специальный объект, который содержит информацию для проверки.
Передача в AuthenticationManager: Этот объект передается
AuthenticationManager
для проверки.Проверка данных:
AuthenticationManager
использует различные методы (например, проверку в базе данных), чтобы убедиться, что данные верны.- Он может обращаться к одному или нескольким провайдерам аутентификации (
AuthenticationProvider
), которые фактически выполняют проверку.
Возврат результата:
- Успех: Если данные верны, вы получаете доступ, и система знает, кто вы и какие у вас права.
- Неудача: Если данные неверны, доступ отклоняется, и вы получаете сообщение об ошибке.
Пример из реальной жизни
Представьте охранника на входе в офисное здание:
- Вы: Человек, который хочет войти в здание.
- Пропуск: Ваши учетные данные (например, пропуск или удостоверение личности).
- Охранник:
AuthenticationManager
, который проверяет ваши документы. - Процесс:
- Вы показываете пропуск охраннику.
- Охранник проверяет его подлинность и сверяет с базой данных сотрудников.
- Если все в порядке, он пускает вас внутрь.
- Если нет, он просит вас покинуть здание.
Зачем нужен AuthenticationManager
Безопасность: Он обеспечивает, чтобы только проверенные и авторизованные пользователи могли получить доступ к системе.
Гибкость: Позволяет использовать разные способы аутентификации (пароли, токены, отпечатки пальцев и т.д.).
Масштабируемость: Можно легко добавлять новые методы проверки без изменения основной логики приложения.
Как вы с ним взаимодействуете
Как разработчик, вы можете настроить AuthenticationManager
в своем приложении:
Простой пример:
java@Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth .inMemoryAuthentication() .withUser("user") .password("{noop}password") .roles("USER"); } }
В этом примере мы настраиваем простую аутентификацию в памяти с одним пользователем.
Использование базы данных: Вы можете настроить его для проверки пользователей из базы данных, LDAP-сервера или других источников.
Ключевые моменты
AuthenticationManager — это интерфейс, а не конкретный класс. Реальную работу выполняют его реализации.
AuthenticationProvider:
AuthenticationManager
может использовать несколько провайдеров аутентификации для поддержки разных способов проверки.Централизованный контроль: Он объединяет все проверки в одном месте, что упрощает управление безопасностью приложения.
Заключение
AuthenticationManager
— это "дверь" в ваше приложение, через которую проходят все пользователи. Он проверяет, кто вы, и решает, можно ли вам войти. Понимание его работы поможет вам создавать безопасные и надежные приложения с гибкой настройкой способов аутентификации.
Proyekti iwe salan kimi ilk iwe duwen hisse buradir:
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.web;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import jakarta.servlet.Filter;
import jakarta.servlet.http.HttpServletRequest;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.log.LogMessage;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.StringUtils;
/**
* Standard implementation of {@code SecurityFilterChain}.
*
* @author Luke Taylor
* @author Jinwoo Bae
* @since 3.1
*/
public final class DefaultSecurityFilterChain implements SecurityFilterChain {
private static final Log logger = LogFactory.getLog(DefaultSecurityFilterChain.class);
private final RequestMatcher requestMatcher;
private final List<Filter> filters;
public DefaultSecurityFilterChain(RequestMatcher requestMatcher, Filter... filters) {
this(requestMatcher, Arrays.asList(filters));
}
public DefaultSecurityFilterChain(RequestMatcher requestMatcher, List<Filter> filters) {
if (filters.isEmpty()) {
logger.debug(LogMessage.format("Will not secure %s", requestMatcher));
}
else {
List<String> filterNames = new ArrayList<>();
for (Filter filter : filters) {
filterNames.add(filter.getClass().getSimpleName());
}
String names = StringUtils.collectionToDelimitedString(filterNames, ", ");
logger.debug(LogMessage.format("Will secure %s with filters: %s", requestMatcher, names));
}
this.requestMatcher = requestMatcher;
this.filters = new ArrayList<>(filters);
}
public RequestMatcher getRequestMatcher() {
return this.requestMatcher;
}
@Override
public List<Filter> getFilters() {
return this.filters;
}
@Override
public boolean matches(HttpServletRequest request) {
return this.requestMatcher.matches(request);
}
@Override
public String toString() {
return this.getClass().getSimpleName() + " [RequestMatcher=" + this.requestMatcher + ", Filters=" + this.filters
+ "]";
}
}
private void internalDoFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
if (this.pos < this.n) {
ApplicationFilterConfig filterConfig = this.filters[this.pos++];
try {
Filter filter = filterConfig.getFilter();
if (request.isAsyncSupported() && !filterConfig.getFilterDef().getAsyncSupportedBoolean()) {
request.setAttribute("org.apache.catalina.ASYNC_SUPPORTED", Boolean.FALSE);
}
if (Globals.IS_SECURITY_ENABLED) {
ServletRequest req = request;
ServletResponse res = response;
Principal principal = ((HttpServletRequest)req).getUserPrincipal();
Object[] args = new Object[]{req, res, this};
SecurityUtil.doAsPrivilege("doFilter", filter, classType, args, principal);
} else {
filter.doFilter(request, response, this);
}
} catch (ServletException | RuntimeException | IOException var15) {
Exception e = var15;
throw e;
} catch (Throwable var16) {
Throwable e = var16;
e = ExceptionUtils.unwrapInvocationTargetException(e);
ExceptionUtils.handleThrowable(e);
throw new ServletException(sm.getString("filterChain.filter"), e);
}
} else {
try {
if (this.dispatcherWrapsSameObject) {
lastServicedRequest.set(request);
lastServicedResponse.set(response);
}
if (request.isAsyncSupported() && !this.servletSupportsAsync) {
request.setAttribute("org.apache.catalina.ASYNC_SUPPORTED", Boolean.FALSE);
}
if (request instanceof HttpServletRequest && response instanceof HttpServletResponse && Globals.IS_SECURITY_ENABLED) {
ServletRequest req = request;
ServletResponse res = response;
Principal principal = ((HttpServletRequest)req).getUserPrincipal();
Object[] args = new Object[]{req, res};
SecurityUtil.doAsPrivilege("service", this.servlet, classTypeUsedInService, args, principal);
} else {
this.servlet.service(request, response);
}
} catch (ServletException | RuntimeException | IOException var17) {
Exception e = var17;
throw e;
} catch (Throwable var18) {
Throwable e = var18;
e = ExceptionUtils.unwrapInvocationTargetException(e);
ExceptionUtils.handleThrowable(e);
throw new ServletException(sm.getString("filterChain.servlet"), e);
} finally {
if (this.dispatcherWrapsSameObject) {
lastServicedRequest.set((Object)null);
lastServicedResponse.set((Object)null);
}
}
}
}
@Override
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);
Throwable requestRejectedException = this.throwableAnalyzer
.getFirstThrowableOfType(RequestRejectedException.class, causeChain);
if (!(requestRejectedException instanceof RequestRejectedException)) {
throw ex;
}
this.requestRejectedHandler.handle((HttpServletRequest) request, (HttpServletResponse) response,
(RequestRejectedException) requestRejectedException);
}
finally {
this.securityContextHolderStrategy.clearContext();
request.removeAttribute(FILTER_APPLIED);
}
}
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
Class<? extends Authentication> toTest = authentication.getClass();
AuthenticationException lastException = null;
AuthenticationException parentException = null;
Authentication result = null;
Authentication parentResult = null;
int currentPosition = 0;
int size = this.providers.size();
for (AuthenticationProvider provider : getProviders()) {
if (!provider.supports(toTest)) {
continue;
}
if (logger.isTraceEnabled()) {
logger.trace(LogMessage.format("Authenticating request with %s (%d/%d)",
provider.getClass().getSimpleName(), ++currentPosition, size));
}
try {
result = provider.authenticate(authentication);
if (result != null) {
copyDetails(authentication, result);
break;
}
}
catch (AccountStatusException | InternalAuthenticationServiceException ex) {
prepareException(ex, authentication);
// SEC-546: Avoid polling additional providers if auth failure is due to
// invalid account status
throw ex;
}
catch (AuthenticationException ex) {
lastException = ex;
}
}
if (result == null && this.parent != null) {
// Allow the parent to try.
try {
parentResult = this.parent.authenticate(authentication);
result = parentResult;
}
catch (ProviderNotFoundException ex) {
// ignore as we will throw below if no other exception occurred prior to
// calling parent and the parent
// may throw ProviderNotFound even though a provider in the child already
// handled the request
}
catch (AuthenticationException ex) {
parentException = ex;
lastException = ex;
}
}
if (result != null) {
if (this.eraseCredentialsAfterAuthentication && (result instanceof CredentialsContainer)) {
// Authentication is complete. Remove credentials and other secret data
// from authentication
((CredentialsContainer) result).eraseCredentials();
}
// If the parent AuthenticationManager was attempted and successful then it
// will publish an AuthenticationSuccessEvent
// This check prevents a duplicate AuthenticationSuccessEvent if the parent
// AuthenticationManager already published it
if (parentResult == null) {
this.eventPublisher.publishAuthenticationSuccess(result);
}
return result;
}
// Parent was null, or didn't authenticate (or throw an exception).
if (lastException == null) {
lastException = new ProviderNotFoundException(this.messages.getMessage("ProviderManager.providerNotFound",
new Object[] { toTest.getName() }, "No AuthenticationProvider found for {0}"));
}
// If the parent AuthenticationManager was attempted and failed then it will
// publish an AbstractAuthenticationFailureEvent
// This check prevents a duplicate AbstractAuthenticationFailureEvent if the
// parent AuthenticationManager already published it
if (parentException == null) {
prepareException(lastException, authentication);
}
throw lastException;
}
Комментарии
Отправить комментарий