Spring Security Simple Course
Tomcat nedir?
**Tomcat** və ya tam adı ilə **Apache Tomcat**, Java proqramları üçün geniş istifadə olunan açıq mənbəli bir veb server və servlet konteyneridir. Apache Software Foundation tərəfindən inkişaf etdirilib və əsasən Java dilində yazılmış veb tətbiqləri işə salmaq üçün istifadə olunur.
### **Tomcat nədir və nə üçün istifadə olunur?**
- **Veb Server və Servlet Konteyneri**:
- Tomcat, əsasən bir **servlet konteyneri** kimi işləyir. Bu, onun Java Servlet-lərini işə salmağa və JavaServer Pages (JSP) texnologiyası ilə yazılmış veb səhifələri göstərməyə imkan verməsi deməkdir.
- **Veb Tətbiqlərinin İdarə Edilməsi**:
- Java ilə hazırlanmış veb tətbiqlərini Tomcat vasitəsilə asanlıqla yerləşdirə və idarə edə bilərsiniz. Yəni, Tomcat tətbiqlərinizi internet üzərindən istifadəçilərə çatdırmaq üçün bir platforma rolunu oynayır.
- **Java EE Standartlarını Dəstəkləyir**:
- Tomcat, Java Enterprise Edition (Java EE) standartlarının bir hissəsi olan servlets, JSP və WebSocket kimi texnologiyaları dəstəkləyir. Bu onu Java ilə inkişaf etdirilən müəssisə səviyyəli tətbiqlərin yerləşdirilməsi üçün uyğun edir.
### **Tomcat-ın istifadəsi**
- Java ilə yazılmış veb tətbiqlərin yerləşdirilməsi və işlədilməsi.
- Kiçik və orta ölçülü layihələr üçün bir veb server kimi istifadə olunması.
- Java təlimləri və tədris məqsədləri üçün proqramçıların öz tətbiqlərini sınamaq üçün istifadə etməsi.
Qısacası, Tomcat Java tətbiqlərinin işlədilməsi üçün güclü və populyar bir vasitədir və həm inkişaf, həm də istehsal mühitlərində geniş istifadə olunur.
Servlet nedir?
**Servlet**, Java ilə yazılmış bir server tərəfli proqramdır və əsasən veb serverlərdə işləyərək dinamik veb məzmunu yaratmaq üçün istifadə olunur. Servlet-lər Java EE (Java Enterprise Edition) texnologiyasının bir hissəsidir və veb tətbiqlər üçün backend (arxa tərəf) məntiqini icra etməyə imkan verir. Gəlin servlet-in nə olduğunu və necə işlədiyini daha ətraflı izah edək:
### **Servlet nədir?**
- **Server Tərəfli Proqram**:
- Servlet, server tərəfdə işləyən və istifadəçilərdən gələn istəkləri qəbul edib onlara cavab verən bir Java sinfidir. Məsələn, istifadəçi veb səhifədə bir düyməyə basdıqda və ya bir form doldurub göndərdikdə, servlet bu istəyi qəbul edib emal edə bilər.
- **Veb Tətbiqlərinin Dinamik Məzmun Yaratması**:
- Servlet-lər dinamik məzmun (məsələn, məlumat bazasından məlumatların alınması, istifadəçi girişlərinin yoxlanılması, hesablama əməliyyatları və s.) yaratmaq üçün istifadə olunur və nəticəni HTML, JSON və ya XML formatında istifadəçiyə göndərə bilir.
### **Servlet necə işləyir?**
1. **İstək Göndərilməsi**:
- İstifadəçi brauzerdən bir URL-ə müraciət edir və ya formu göndərir. Bu zaman serverə HTTP istəyi göndərilir.
2. **Server İstəyi Qəbul Edir**:
- Server (məsələn, Tomcat kimi bir servlet konteyneri) bu istəyi qəbul edir və müvafiq servlet-ə yönləndirir.
3. **Servlet-in Emalı**:
- Servlet, bu istəyi emal edir. Məsələn, istifadəçi məlumatlarını yoxlayır, məlumat bazasından məlumat çəkir və ya bəzi məntiqi əməliyyatlar aparır.
4. **Cavabın Yaradılması**:
- Servlet, emalın nəticələrini HTML, JSON və ya digər formatda cavab kimi hazırlayır və bu cavabı server vasitəsilə istifadəçiyə göndərir.
### **Servlet-lərin İstifadə Məqsədləri**
- Dinamik veb məzmun yaratmaq (məsələn, bir onlayn mağazanın məhsul siyahısı).
- İstifadəçi girişlərini emal etmək və doğrulamaq.
- Məlumat bazasına müraciət edərək nəticələri göstərmək.
- Digər server tərəfli funksiyaları yerinə yetirmək.
### **Nümunə**
Məsələn, bir istifadəçi bir veb səhifədə login (giriş) formasını doldurur və göndərir. Bu zaman servlet həmin istifadəçinin məlumatlarını qəbul edir, məlumat bazasında bu məlumatların düzgünlüyünü yoxlayır və nəticəyə uyğun olaraq istifadəçiyə cavab verir.
Qısaca, **Servlet**, veb serverdə işləyən və istifadəçilərdən gələn istəkləri qəbul edərək emal edən bir Java proqramıdır. Veb tətbiqlərinin dinamik və interaktiv olmasına imkan verir.
### **Spring Security Authentication Flow**
1. **User Entered Credentials**:
- The process begins when the user enters their credentials (username and password) on the login form.
2. **Spring Security Filters**:
- These filters act as the entry point to handle the authentication process. The most crucial filter here is `UsernamePasswordAuthenticationFilter` which captures the credentials and sends them to the `AuthenticationManager` for validation.
3. **Authentication Manager**:
- This manager orchestrates the authentication process. It doesn’t authenticate the user itself but delegates the job to one or more `AuthenticationProviders`.
4. **Authentication Providers**:
- These are responsible for the actual authentication logic. The most common one is `DaoAuthenticationProvider`, which works with a `UserDetailsService` to retrieve user details from the database or any other source.
5. **UserDetailsService**:
- This service loads the user details based on the provided username. It interacts with the data source (e.g., database) to fetch user information.
6. **Password Encoder**:
- After retrieving the user’s details, Spring Security compares the provided password with the stored password. The `PasswordEncoder` helps in encoding the raw password and comparing it with the stored (encoded) password.
7. **Authentication Result**:
- If the credentials are valid, the `Authentication` object is marked as authenticated (`isAuthenticated=true`), and the user is considered successfully authenticated.
8. **Returning to Authentication Manager**:
- The authenticated result is sent back through the `AuthenticationManager`.
9. **Security Context**:
- Once authentication is successful, the `Authentication` object is stored in the `SecurityContext`. This context holds the authenticated user's details for the duration of the session.
10. **Access Granted**:
- Finally, if everything is successful, the user gains access to the requested resources.
### **Summary:**
- The user provides credentials.
- Spring Security filters intercept the request.
- The `AuthenticationManager` delegates authentication to `AuthenticationProvider`.
- `UserDetailsService` fetches user details, and the `PasswordEncoder` verifies the password.
- If authentication succeeds, the `SecurityContext` holds the authenticated user's information.
This flow represents a typical Spring Security authentication process in the simplest way! If you have any questions or need further details, feel free to ask.
SpringSecurityFilter:
The very first responsibility of SpringSecurityFilters is to convert credentials from the HttpServletRequest object to Authentication object. Authentication object first isAuth = false
AuthenticationManager:
It is going to take responsibility of completing the authentication and conveying the result back to the filters whether the authentication is successful or not. AuthenticationManger is only going to take the responsibility of completing the authentication, but it is not going to do the actual authentication.
AuthenticationProvider:
Very first activity AuthenticationProvider has to do is it has to load the user details based upon the username result. So to load user details it is going to take help from the UserDetailsManager or UserDetailsService implementation classes. So once the user details are loaded, the user details will be sent back to the AuthenticationProvider. Once the PasswordEncoder says that, okay, the passwords are matching accordingly, the AuthenticationProvider conveys back to the AuthenticationManager saying that, the authentication is successful. So how it is going to convey that? This time inside the response the Authentication object, it is going to have boolean value which is isAuthenticated as true. So based upon this value my AuthenticationManager know whether the authentication is successful or not. The same Authentication object will send back to SecurityFilters so filters know whether the authentication successful or not. Regardless of whether the authentication is successful or not, they are going to store the Authentication details is a SecurityContext. So they will store these authentication object against to a sessionId which is created for a given browser. So if the user is trying to access the same protected page from the same browser based upon the given sessionId the SpringSecurityFilters going to load the authentication object details from the SecurityContext and accordingly they are going to show either successful message or a error message. So this should also give a confirmation to you all the actual authentication execution is only going to happen for the very first request. Because there is no entry for a given sessionId inside the SecurityContext. Once the first request is processed and there is entry inside the SecurityContext for a sessionId, from next time onwards the SecurityFilters are not going to invoke AuthenticationManager. Instead they are going to leverage the details present inside the SecurityContext to send the successful response or to send the error response.
First step: Ferz edekki biz hec bir konfquryasiyasiz Spring Boot Security proyekti yazmiwiq. Sorgu ilk olaraq AuthorizationFilter klasinin doFilter() metoduna daxil olur, sonra DefaultLoginPageGeneratingFilter klasinin doFilter() metoduna gedirl.
-----------------------------
Demeli her wey SpringBootWebSecurityConfiguration
bu hisseden bashlayir. Burada defaultSecurityFilterChain
metod var.
Yes, when your Spring Boot application starts, the SecurityFilterChain
bean is created and initialized, which involves setting up the security filters. This process activates the filters even before any request is sent to the application. That's why your breakpoint on line 15 is triggered during application startup in debug mode. This initialization is part of how Spring Security sets up its filter chain to be ready for handling incoming requests.
package com.eazybytes.springsecuritysection2.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;
import static org.springframework.security.config.Customizer.withDefaults;
@Configuration
public class ProjectSecurityConfig {
@Bean
SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests((requests) -> requests.anyRequest().permitAll());
http.formLogin(withDefaults());
http.httpBasic(withDefaults());
return http.build();
}
}
Sonra sorgu BasicAuthenticationFilter klasinin doFilterInternal() metoduna daxil olur:
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws IOException, ServletException {
try {
Authentication authRequest = this.authenticationConverter.convert(request);
if (authRequest == null) {
this.logger.trace("Did not process authentication request since failed to find "
+ "username and password in Basic Authorization header");
chain.doFilter(request, response);
return;
}
String username = authRequest.getName();
this.logger.trace(LogMessage.format("Found username '%s' in Basic Authorization header", username));
if (authenticationIsRequired(username)) {
Authentication authResult = this.authenticationManager.authenticate(authRequest);
SecurityContext context = this.securityContextHolderStrategy.createEmptyContext();
context.setAuthentication(authResult);
this.securityContextHolderStrategy.setContext(context);
if (this.logger.isDebugEnabled()) {
this.logger.debug(LogMessage.format("Set SecurityContextHolder to %s", authResult));
}
this.rememberMeServices.loginSuccess(request, response, authResult);
this.securityContextRepository.saveContext(context, request, response);
onSuccessfulAuthentication(request, response, authResult);
}
}
catch (AuthenticationException ex) {
this.securityContextHolderStrategy.clearContext();
this.logger.debug("Failed to process authentication request", ex);
this.rememberMeServices.loginFail(request, response);
onUnsuccessfulAuthentication(request, response, ex);
if (this.ignoreFailure) {
chain.doFilter(request, response);
}
else {
this.authenticationEntryPoint.commence(request, response, ex);
}
return;
}
chain.doFilter(request, response);
}
-----------------------
First step:
You're correct! In Spring Security, the `FilterChainProxy`'s `doFilter` method is typically the entry point when handling HTTP requests.
### Explanation
- **`FilterChainProxy`**: This class acts as a proxy for a chain of security filters. It contains multiple filters that apply different security mechanisms (e.g., authentication, authorization, etc.).
- When a request is made, it first passes through the `FilterChainProxy`'s `doFilter` method.
- `FilterChainProxy` then delegates the request to the appropriate security filters in the defined order.
This process is part of how Spring Security intercepts requests and applies security measures before allowing them to reach your application.
Second step:
Yes, you are absolutely right! After the `FilterChainProxy`, the request often proceeds to the `SecurityContextHolderFilter`'s `doFilter` method as part of the Spring Security filter chain.
### Explanation
- **`SecurityContextHolderFilter`**: This filter is responsible for managing the `SecurityContext` lifecycle during a request. It ensures that the `SecurityContext` is properly set up before processing and cleaned up afterward.
- It interacts with `SecurityContextHolder` to make the `SecurityContext` (which holds the `Authentication` details) available throughout the lifecycle of the request.
So, your observation of the request entering `SecurityContextHolderFilter` is correct, and it’s a normal part of the Spring Security filter chain processing.
Filterlerden biri: UsernamePasswordAuthenticationFilter bu filterin
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException {
if (this.postOnly && !request.getMethod().equals("POST")) {
throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
}
String username = obtainUsername(request);
username = (username != null) ? username.trim() : "";
String password = obtainPassword(request);
password = (password != null) ? password : "";
UsernamePasswordAuthenticationToken authRequest = UsernamePasswordAuthenticationToken.unauthenticated(username,
password);
// Allow subclasses to set the "details" property
setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
}
metodu sonuncu lineda gorurukku AuthenticationManager interfacenin implementatsiyasini cagirir
public interface AuthenticationManager {
/**
* Attempts to authenticate the passed {@link Authentication} object, returning a
* fully populated <code>Authentication</code> object (including granted authorities)
* if successful.
* <p>
* An <code>AuthenticationManager</code> must honour the following contract concerning
* exceptions:
* <ul>
* <li>A {@link DisabledException} must be thrown if an account is disabled and the
* <code>AuthenticationManager</code> can test for this state.</li>
* <li>A {@link LockedException} must be thrown if an account is locked and the
* <code>AuthenticationManager</code> can test for account locking.</li>
* <li>A {@link BadCredentialsException} must be thrown if incorrect credentials are
* presented. Whilst the above exceptions are optional, an
* <code>AuthenticationManager</code> must <B>always</B> test credentials.</li>
* </ul>
* Exceptions should be tested for and if applicable thrown in the order expressed
* above (i.e. if an account is disabled or locked, the authentication request is
* immediately rejected and the credentials testing process is not performed). This
* prevents credentials being tested against disabled or locked accounts.
* @param authentication the authentication request object
* @return a fully authenticated object including credentials
* @throws AuthenticationException if authentication fails
*/
Authentication authenticate(Authentication authentication) throws AuthenticationException;
}
ProviderManager klasi AuthenticationManager interface implement edir.
@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;
}
ProviderManager oz novbesinde ise AuthenticationProvider interface authenticate() metodunu cagirir,
The provided class documentation refers to Spring Security's `Authentication` interface, which is a core part of the security framework. This interface is critical in managing the authentication process. Here's why we need it and what each part does:
### Why Do We Need the `Authentication` Interface?
1. **Represents Authentication Requests**:
The `Authentication` interface is used to represent an **authentication request**. Whenever a user tries to log in, Spring Security creates an object implementing this interface (e.g., `UsernamePasswordAuthenticationToken`) to hold the user's credentials (like username and password).
2. **Holds Authentication State**:
It represents both the authentication **request** and the state of the **authenticated principal** (i.e., the user) once authentication is complete. This allows Spring Security to manage both unauthenticated requests (holding just the credentials) and authenticated ones (holding user details and roles).
3. **Integration with SecurityContext**:
After successful authentication, the `Authentication` object is stored in the **SecurityContext** (accessible globally in the application). This makes it easy to track the user’s authentication status and to retrieve information about the authenticated user in other parts of the application.
4. **Security Interceptors and Authorization**:
The `Authentication` object is also used by Spring Security’s **security interceptors** to determine if the user has the necessary permissions (authorities or roles) to access a resource. For instance, when a user requests a secured page, the system checks the `Authentication` object to see if the user has the right permissions.
### Key Components of the `Authentication` Interface
1. **getAuthorities()**:
- This method returns the **authorities** (or roles) granted to the authenticated user. After a successful login, the authorities (e.g., `ROLE_USER`, `ROLE_ADMIN`) determine what the user is allowed to do within the system.
- For example, an `AuthenticationManager` might populate this based on the user's roles retrieved from the database.
2. **getCredentials()**:
- This method returns the user's **credentials** (like a password) that prove the user’s identity. The credentials are passed during the authentication request but are not typically stored after authentication is successful.
- This value may not be stored long-term for security reasons (e.g., passwords are often cleared after validation).
3. **getDetails()**:
- This method can store **additional information** related to the authentication request. This can include information like the user’s IP address, the client they used, or session information, making it useful for logging or additional validation.
4. **getPrincipal()**:
- This represents the **identity of the principal** (i.e., the user). For username/password authentication, this would typically be the username. After successful authentication, the `Principal` may hold additional user details, such as a `UserDetails` object, which includes not just the username but also other information like account status and enabled/disabled flags.
5. **isAuthenticated()**:
- This method indicates whether the token (i.e., the authentication request or object) is currently **authenticated**. Before authentication, this value is `false`. After a successful login, it is set to `true`. This method helps ensure that security components don't waste resources re-authenticating the same user unnecessarily.
6. **setAuthenticated(boolean isAuthenticated)**:
- This method allows setting the authentication status programmatically. It can be used to **manually mark a user as unauthenticated** if needed, such as during a logout process.
- The method also ensures security by preventing unauthorized setting of the authentication state to `true` without proper checks.
### Example Usage:
Here’s how the `Authentication` interface is typically used in a Spring Security application:
1. **User Logs In**:
- The user submits their credentials (username and password).
- These credentials are wrapped in an `Authentication` object (such as `UsernamePasswordAuthenticationToken`) and passed to an `AuthenticationManager`.
2. **AuthenticationManager**:
- The `AuthenticationManager` takes the `Authentication` object, validates the credentials, and if successful, returns a new `Authentication` object where the `isAuthenticated()` method is set to `true` and the user’s authorities (roles) are populated.
3. **Storing in SecurityContext**:
- After successful authentication, the `Authentication` object is stored in the `SecurityContext`, managed by `SecurityContextHolder`.
- This allows the application to retrieve the authenticated user’s details at any time during the session.
4. **Authorization Check**:
- For every request to a protected resource, Spring Security checks the `Authentication` object in the `SecurityContext` to ensure the user has the necessary authorities (permissions or roles).
### Summary:
The `Authentication` interface is essential in Spring Security as it serves as a **central abstraction for handling authentication requests**. It captures user credentials, authorities, and authentication status, and integrates tightly with Spring Security's overall workflow, allowing seamless authentication and authorization across the application. Without this abstraction, it would be much harder to manage and track authentication throughout the application.
Комментарии
Отправить комментарий