1. Introduction
Much of what we have covered to date has been focused on delivering functional capability. Before we go much further into filling in the backend parts of our application or making deployments, we need to begin factoring in security concerns. Information Security is a practice of protecting information by mitigating risks [1] Risks are identified with their impact and appropriate mitigations.
We won’t get into the details of Information Security analysis and making specific trade-offs, but we will cover how we can address the potential mitigations through the use of a framework and how that is performed within Spring Security and Spring Boot.
1.1. Goals
You will learn:
-
key terms relative to implementing access control and privacy
-
the flexibility and power of implementing a filter-based processing architecture
-
the purpose of the core Spring Authentication components
-
how to enable Spring Security
-
to identify key aspects of the default Spring Security
1.2. Objectives
At the conclusion of this lecture and related exercises, you will be able to:
-
define identity, authentication, and authorization and how they can help protect our software system
-
identify the purpose for and differences between encoding, encryption, and cryptographic hashes
-
identify the purpose of a filter-based processing architecture
-
identify the core components within Spring Authentication
-
identity where the current user authentication is held/located
-
how to activate default Spring Security configuration
-
identify and demonstrate the security features of the default Spring Security configuration
-
step through a series of calls through the Security filter chain
2. Access Control
Access Control is one of the key mitigation factors within a security solution.
- Identity
-
We need to know who the caller is and/or who is the request being made for. When you make a request in everyday life (e.g., make a pizza order) — you commonly have to supply your identity so that your request can be associated with you. There can be many layers of systems/software between the human and the action performed, so identity can be more complex than just a single value — but I will keep the examples to a simple username.
- Authentication
-
We need verification of the requester’s identity. This is commonly something known — e.g., a password, PIN, or generated token. Additional or alternate types of authentication like something someone has (e.g., access to a specific mobile phone number or email account, or assigned token generator) are also becoming more common today and are adding a needed additional level of security to more sensitive information.
- Authorization
-
Once we know and can confirm the identity of the requester, we then need to know what actions they are allowed to perform and information they are allowed to access or manipulate. This can be based on assigned roles (e.g., administrator, user), relative role (e.g., creator, owner, member), or releasability (e.g., access markings).
These access control decisions are largely independent of the business logic and can be delegated to the framework. That makes it much easier to develop and test business logic outside of the security protections and to be able to develop and leverage mature and potentially certified access control solutions.
3. Privacy
Privacy is a general term applied to keeping certain information or interactions secret from others. We use various encoding, encryption, and hash functions in order to achieve these goals.
3.1. Encoding
Encoding converts source information into an alternate form that is safe for communication and/or storage. [2] Two primary examples are URL and Base64 encoding of special characters or entire values. Encoding may obfuscate the data, but by itself is not encryption. Anyone knowing the encoding scheme can decode an encoded value and that is its intended purpose.
$ echo -n jim:password | base64 (1)
amltOnBhc3N3b3Jk
$ echo -n amltOnBhc3N3b3Jk | base64 -D
jim:password
1 | echo -n echos the supplied string without new line character added - which would pollute the value |
3.2. Encryption
Encryption is a technique of encoding "plaintext" information into an enciphered form ("ciphertext") with the intention that only authorized parties — in possession of the encryption/decryption keys — can convert back to plaintext. [3] Others not in possession of the keys would be forced to try to break the code thru (hopefully) a significant amount of computation.
There are two primary types of keys — symmetric and asymmetric. For encryption with symmetric keys, the encryptor and decryptor must be in possession of the same/shared key. For encryption with asymmetric keys — there are two keys: public and private. Plaintext encrypted with the shared, public key can only be decrypted with the private key. SSH is an example of using asymmetric encryption.
Asymmetric encryption is more computationally intensive than symmetric
Asymmetric encryption is more computationally intensive than symmetric — so you may find
that asymmetric encryption techniques will embed a dynamically generated symmetric key
used to encrypt a majority of the payload within a smaller area of the payload that is
encrypted with the asymmetric key.
|
$ echo -n "jim:password" > /tmp/plaintext.txt
$ openssl enc -aes-256-cbc -salt -in /tmp/plaintext.txt -base64 \(1)
-pass pass:password > /tmp/ciphertext
$ cat /tmp/ciphertext
U2FsdGVkX18mM2yNc337MS5r/iRJKI+roqkSym0zgMc=
$ openssl enc -d -aes-256-cbc -in /tmp/ciphertext -base64 -pass pass:password (2)
jim:password
$ openssl enc -d -aes-256-cbc -in /tmp/ciphertext -base64 -pass pass:password123 (3)
bad decrypt
4611337836:error:06FFF064:digital envelope routines:CRYPTO_internal:bad decrypt
1 | encrypting file of plaintext with a symmetric/shared key. Result is base64 encoded. |
2 | decrypting file of ciphertext with valid symmetric/shared key after being base64 decoded |
3 | failing to decrypt file of ciphertext with invalid key |
3.3. Cryptographic Hash
A Cryptographic Hash is a one-way algorithm that takes a payload of an arbitrary size and computes a value of a known size that is unique to the input payload. The output is deterministic such that multiple, separate invocations can determine if they were working with the same input value — even if the resulting hash is not technically the same. Cryptographic hashes are good for determining whether information has been tampered with or to avoid storing recoverable password values.
$ echo -n password | md5
5f4dcc3b5aa765d61d8327deb882cf99 (1)
$ echo -n password | md5
5f4dcc3b5aa765d61d8327deb882cf99 (1)
$ echo -n password123 | md5
482c811da5d5b4bc6d497ffa98491e38 (2)
1 | Core hash algorithms produce identical results for same inputs |
2 | Different value produced for different input |
Unlike encryption there is no way to mathematically obtain the original plaintext from the resulting hash. That makes it a great alternative to storing plaintext or encrypted passwords. However, there are still some unwanted vulnerabilities by having the calculated value be the same each time.
By adding some non-private variants to each invocation (called "Salt"), the resulting values can be technically different — making it difficult to use brute force dictionary attacks. The following example uses the Apache htpasswd command to generate a Cryptographic Hash with a Salt value that will be different each time. The first example uses the MD5 algorithm again and the second example uses the Bcrypt algorithm — which is more secure and widely accepted for creating Cryptographic Hashes for passwords.
$ htpasswd -bnm jim password
jim:$apr1$ctNOftbV$SZHs/IA3ytOjx0IZEZ1w5. (1)
$ htpasswd -bnm jim password
jim:$apr1$gLU9VlAl$ihDOzr8PdiCRjF3pna2EE1 (1)
$ htpasswd -bnm jim password123
jim:$apr1$9sJN0ggs$xvqrmNXLq0XZWjMSN/WLG.
1 | Salt added to help defeat dictionary lookups |
$ htpasswd -bnBC 10 jim password
jim:$2y$10$cBJOzUbDurA32SOSC.AnEuhUW269ACaPM7tDtD9vbrEg14i9GdGaS
$ htpasswd -bnBC 10 jim password
jim:$2y$10$RztUum5dBjKrcgiBNQlTHueqDFd60RByYgQPbugPCjv23V/RzfdVG
$ htpasswd -bnBC 10 jim password123
jim:$2y$10$s0I8X22Z1k2wK43S7dUBjup2VI1WUaJwfzX8Mg2Ng0jBxnjCEA0F2
4. Spring Web
Spring Framework operates on a series of core abstractions and a means to leverage them from different callchains. Most of the components are manually assembled through builders and components/beans are often integrated together through the Spring application context. For the web specifically, the callchains are implemented through an initial web interface implemented through the hosting or embedded web server. Often the web.xml will define a certain set of filters that add functionality to the request/response flow. |
Figure 1. Spring Web Framework Operates thru Flexibly Assembled Filters and Core Services
|
5. No Security
We know by now that we can exercise the Spring Application Filter Chain by implementing and calling a controller class. I have implemented a simple example class that I will be using throughout this lecture. At this point in time — no security has been enabled.
5.1. Sample GET
The example controller has two example GET calls that are functionally identical
at this point because we have no security enabled. The following is registered to
the /api/anonymous/hello
URI and the other to /api/authn/hello
.
@RequestMapping(path="/api/anonymous/hello",
method= RequestMethod.GET)
public String getHello(@RequestParam(name = "name", defaultValue = "you") String name) {
return "hello, " + name;
}
We can call the endpoint using the following curl or equivalent browser call.
$ curl -v -X GET "http://localhost:8080/api/anonymous/hello?name=jim"
> GET /api/anonymous/hello?name=jim HTTP/1.1
< HTTP/1.1 200
< Content-Length: 10
<
hello, jim
5.2. Sample POST
The example controller has three example POST calls that are functionally identical
at this point because we have no security or other policies enabled. The following
is registered to the /api/anonymous/hello
URI. The other two are mapped to the
/api/authn/hello
and /api/alt/hello
URIs.
[4].
@RequestMapping(path="/api/anonymous/hello",
method = RequestMethod.POST,
consumes = MediaType.TEXT_PLAIN_VALUE,
produces = MediaType.TEXT_PLAIN_VALUE)
public String postHello(@RequestBody String name) {
return "hello, " + name;
}
We can call the endpoint using the following curl command.
$ curl -v -X POST "http://localhost:8080/api/anonymous/hello" \
-H "Content-Type: text/plain" -d "jim"
> POST /api/anonymous/hello HTTP/1.1
< HTTP/1.1 200
< Content-Length: 10
<
hello, jim
5.3. Sample Static Content
I have not mentioned it before now — but not everything served up by the application has to be live content provided through a controller.
We can place static resources in the src/main/resources/static
folder and have that packaged up and served through URIs relative to the root.
static resource locations
Spring Boot will serve up static content found in src/main/resources/ `-- static `-- content `-- hello_static.txt Anything placed below target/classes/ `-- static <== classpath:/static at runtime `-- content <== /content URI at runtime `-- hello_static.txt |
This would be a common thing to do for css files, images, and other supporting web content. The following is a text file in our sample application.
Hello, static file
The following is an example GET of that static resource file.
$ curl -v -X GET "http://localhost:8080/content/hello_static.txt" > GET /content/hello_static.txt HTTP/1.1 < HTTP/1.1 200 < Content-Length: 19 < Hello, static file
6. Spring Security
The Spring Security framework is integrated into the web callchain using filters that form an internal Security Filter Chain. We will look at the Security Filter Chain in more detail shortly. At this point — just know that the framework is a flexible, filter-based framework where many different authentication schemes can be enabled. Lets take a look first at the core services used by the Security Filter Chain. |
Figure 2. Spring Security Implemented as Extension of Application Filter Chain
|
6.1. Spring Core Authentication Framework
Once we enable Spring Security — a set of core authentication services are instantiated and made available to the Security Filter Chain. The key players are a set of interfaces with the following roles.
Authentication
-
provides both an authentication request and result abstraction. All the key properties (principal, credentials, details) are defined as
java.lang.Object
to allow just about any identity and authentication abstraction be represented. For example, anAuthentication
request has a principal set to the username String and anAuthentication
response has the principal set toUserDetails
containing the username and other account information. AuthenticationManager
-
provides a front door to authentication requests that may be satisfied using one or more
AuthenticationProvider
AuthenticationProvider
-
a specific authenticator with access to
UserDetails
to complete the authentication.Authentication
requests are of a specific type. If this provider supports the type and can verify the identity claim of the caller — anAuthentication
result with additional user details is returned. UserDetailsService
-
a lookup strategy used by
AuthenticationProvider
to obtainUserDetails
by username. There are a few configurable implementations provided by Spring (e.g., JDBC) but we are encouraged to create our own implementations if we have a credentials repository that was not addressed. UserDetails
-
an interface that represents the minimal needed information for a user. This will be made part of the
Authentication
response in theprincipal
property.
6.2. SecurityContext
The authentication is maintained inside of a SecurityContext that can be
manipulated over time.
The current state of authentication is located through static methods
of the SecurityContextHolder
class. Although there are multiple strategies
for maintaining the current SecurityContext with Authentication — the most
common is ThreadLocal
.
7. Spring Boot Security AutoConfiguration
As with most Spring Boot libraries — we have to do very little to get started.
Most of what you were shown above is instantiated with a single additional dependency
on the spring-boot-starter-security
artifact.
7.1. Maven Dependency
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>
This artifact triggers three (3) AutoConfiguration classes in the spring-boot-autoconfiguration artifact.
-
For Spring Boot < 2.7, the auto-configuration classes will be named in META-INF/spring.factories:
# org.springframework-boot:spring-boot-autoconfigure/META-INF/spring.factories ... org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration,\ org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration,\ org.springframework.boot.autoconfigure.security.servlet.SecurityFilterAutoConfiguration,\
-
For Spring Boot >= 2.7, the auto-configuration classes will be named in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports .Spring Boot Starter Security (>= 2.7)
# org.springframework-boot:spring-boot-autoconfigure/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration org.springframework.boot.autoconfigure.security.servlet.SecurityFilterAutoConfiguration
The details of this may not be that important except to understand how the default behavior was assembled and how future customizations override this behavior.
7.2. SecurityAutoConfiguration
The SecurityAutoConfiguration imports two @Configuration
classes that conditionally
wire up the security framework discussed with default implementations.
-
SpringBootWebSecurityConfiguration makes sure there is at least a default
SecurityFilterChain
(more on that later) which-
requires all URIs be authenticated
-
activates FORM and BASIC authentication
-
enables CSRF and other security protections
@Bean @Order(SecurityProperties.BASIC_AUTH_ORDER) //very low priority SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception { http.authorizeRequests().anyRequest().authenticated(); http.formLogin(); http.httpBasic(); return http.build(); }
-
-
WebSecurityEnablerConfiguration activates all security components by supplying the
@EnableWebSecurity
annotation when the security classes are present in the classpath.
7.3. WebSecurityConfiguration
WebSecurityConfiguration
gathers all the SecurityFilterChain
beans, obtains filters for each, and forms the runtime FilterChains
.
7.4. UserDetailsServiceAutoConfiguration
The UserDetailsServiceAutoConfiguration simply defines an in-memory UserDetailsService
if one is not yet present. This is one of the provided implementations mentioned earlier — but still just a demonstration toy. The UserDetailsService
is populated with one user:
-
name:
user
, unless defined -
password: generated, unless defined
Example Output from Generated PasswordUsing generated security password: ff40aeec-44c2-495a-bbbf-3e0751568de3
Overrides can be supplied in properties
spring.security.user.name: user spring.security.user.password: password
7.5. SecurityFilterAutoConfiguration
The SecurityFilterAutoConfiguration establishes the springSecurityFilterChain
filter chain, implemented as a DelegatingFilterProxy
.
The delegate of this proxy is supplied by the details of the SecurityAutoConfiguration
.
8. Default FilterChain
When we activated Spring security we added a level of filters that
were added to the Application Filter Chain. The first was a
DelegatingFilterProxy
that lazily instantiated the filter using
a delegate obtained from the Spring application context. This delegate
ends up being a FilterChainProxy
which has a prioritized list of
SecurityFilterChain
(implemented using DefaultSecurityFilterChain
).
Each SecurityFilterChain
has a requestMatcher and a set of zero or
more Filters
. Zero filters essentially bypasses security for a particular
URI pattern.
9. Default Secured Application
With all that said — and all we really did was add an artifact dependency to the project — the following shows where the Auto-Configuration left our application.
9.1. Form Authentication Activated
Form Authentication has been activated and we are now stopped from accessing all URLs
without first entering a valid username and password. Remember, the default username is
user
and the default password was output to the console unless we supplied one in properties.
The following shows the result of a redirect when attempting to access any URL in the
application.
-
We entered http://localhost:8080/api/anonymous/hello?name=jim
-
Application saw there was no authentication for the session and redirected to /login page
-
Login URL, html, and CSS supplied by spring-boot-starter-security
If we call the endpoint from curl, without indicating we can visit an HTML page, we get flatly rejected with a 401/UNAUTHORIZED. The response does inform us that BASIC Authentication is available.
$ curl -v http://localhost:8080/authn/hello?name=jim > GET /authn/hello?name=jim HTTP/1.1 < HTTP/1.1 401 < Set-Cookie: JSESSIONID=D124368C884557286BF59F70888C0D39; Path=/; HttpOnly < WWW-Authenticate: Basic realm="Realm" (1) {"timestamp":"2020-07-01T23:32:39.909+00:00","status":401, "error":"Unauthorized","message":"Unauthorized","path":"/authn/hello"}
1 | WWW-Authenticate header indicates that BASIC Authentication is available |
If we add an Accept header to the curl request with text/html
, we get a
302/REDIRECT to the login page the browser automatically took us to.
$ curl -v http://localhost:8080/authn/hello?name=jim \ -H "Accept: text/plain,text/html" (1) > GET /authn/hello?name=jim HTTP/1.1 > Accept: text/plain, text/html < HTTP/1.1 302 < Set-Cookie: JSESSIONID=E132523FE23FA8D18B94E3D55820DF13; Path=/; HttpOnly < Location: http://localhost:8080/login < Content-Length: 0
1 | adding an Accept header accepting text initiates a redirect to login form |
The login (URI /login
) and logout (URI /logout
) forms are supplied as defaults.
If we use the returned JSESSIONID when accessing and successfully completing
the login form — we will continue on to our originally requested URL.
Since we are targeting APIs — we will be disabling that very soon and relying on more stateless authentication mechanisms.
9.2. Basic Authentication Activated
BASIC authentication is also activated by default. This is usable by our API
out of the gate, so we will use this a bit more in examples. The following shows an example
BASIC encoding of the username:password
values in a Base64 string and then supplying the
result of that encoding in an Authorization
header prefixed with the work "BASIC ".
$ echo -n user:ff40aeec-44c2-495a-bbbf-3e0751568de3 | base64
dXNlcjpmZjQwYWVlYy00NGMyLTQ5NWEtYmJiZi0zZTA3NTE1NjhkZTM=
$ curl -v -X GET http://localhost:8080/api/anonymous/hello?name=jim \
-H "Authorization: BASIC dXNlcjpmZjQwYWVlYy00NGMyLTQ5NWEtYmJiZi0zZTA3NTE1NjhkZTM="
> GET /api/anonymous/hello?name=jim HTTP/1.1
> Authorization: BASIC dXNlcjpmZjQwYWVlYy00NGMyLTQ5NWEtYmJiZi0zZTA3NTE1NjhkZTM=
>
< HTTP/1.1 200 (1)
< Content-Length: 10
hello, jim
1 | request with successful BASIC authentication gives us the results of intended URL |
Base64 web sites available if command-line tool not available
I am using a command-line tool for easy demonstration and privacy.
There are various
websites that will perform the encode/decode for
you as well. Obviously, using a public website for real usernames and passwords
would be a bad idea.
|
curl can Automatically Supply Authorization Header
You can avoid the manual step of base64 encoding the username:password and manually supplying the Authorization header with curl by using the plaintext -u username:password option.
|
9.3. Authentication Required Activated
If we do not supply the Authorization
header or do not supply a valid value,
we get a 401/UNAUTHORIZED status response back from the interface telling us
our credentials are either invalid (did not match username:password) or
were not provided.
$ echo -n user:badpassword | base64 (2)
dXNlcjpiYWRwYXNzd29yZA==
$ curl -v -X GET http://localhost:8080/api/anonymous/hello?name=jim -u user:badpassword (1)
> GET /api/anonymous/hello?name=jim HTTP/1.1
> Authorization: BASIC dXNlcjpiYWRwYXNzd29yZA== (2)
>
< HTTP/1.1 401
< WWW-Authenticate: Basic realm="Realm"
< Set-Cookie: JSESSIONID=32B6CDB8E899A82A1B7D55BC88CA5CBE; Path=/; HttpOnly
< WWW-Authenticate: Basic realm="Realm"
< Content-Length: 0
1 | bad username:password supplied |
2 | demonstrating source of Authorization header |
9.4. Username/Password Can be Supplied
To make things more consistent during this stage of our learning, we can manually assign a username and password using properties.
spring.security.user.name: user spring.security.user.password: password
$ curl -v -X GET "http://localhost:8080/api/authn/hello?name=jim" -u user:password > GET /api/authn/hello?name=jim HTTP/1.1 > Authorization: BASIC dXNlcjpwYXNzd29yZA== < HTTP/1.1 200 < Set-Cookie: JSESSIONID=7C5045AE82C58F0E6E7E76961E0AFF57; Path=/; HttpOnly < Content-Length: 10 hello, jim
9.5. CSRF Protection Activated
The default Security Filter chain contains CSRF protections — which is a defense mechanism developed to prevent alternate site from providing the client browser a page that performs an unsafe (POST, PUT, or DELETE) call to an alternate site the client has as established session with. The server makes a CSRF token available to us using a GET and will be expecting that value on the next POST, PUT, or DELETE.
$ curl -v -X POST "http://localhost:8080/api/authn/hello" \ -u user:password -H "Content-Type: text/plain" -d "jim" > POST /api/authn/hello HTTP/1.1 > Authorization: BASIC dXNlcjpwYXNzd29yZA== > Content-Type: text/plain > Content-Length: 3 < HTTP/1.1 401 < Set-Cookie: JSESSIONID=3EEB3625749482AD9E44A3B7E25A0EE4; Path=/; HttpOnly < WWW-Authenticate: Basic realm="Realm" < Content-Length: 0
9.6. Other Headers
Spring has, by default, generated additional headers to help with client interactions that primarily have to do with common security issues.
$ curl -v http://localhost:8080/api/anonymous/hello?name=jim -u user:password > GET /api/anonymous/hello?name=jim HTTP/1.1 > Authorization: BASIC dXNlcjpwYXNzd29yZA== > < HTTP/1.1 200 < Set-Cookie: JSESSIONID=EC5EB9D1182F8AC77E290D12AD3BF369; Path=/; HttpOnly < X-Content-Type-Options: nosniff < X-XSS-Protection: 1; mode=block < Cache-Control: no-cache, no-store, max-age=0, must-revalidate < Pragma: no-cache < Expires: 0 < X-Frame-Options: DENY < Content-Type: text/plain;charset=UTF-8 < Content-Length: 10 < Date: Thu, 02 Jul 2020 10:45:32 GMT < hello, jim
- Set-Cookie
-
a command header to set a small amount of information in the browser to be returned to the server on follow-on calls. [5] This permits the server to keep track of a user session so that a login state can be retained on follow-on calls.
- X-Content-Type-Options
-
informs the browser that supplied
Content-Type
header responses have been deliberately assigned [6] and to avoid Mime Sniffing — a problem caused by servers serving uploaded content meant to masquerade as alternate MIME types. - X-XSS-Protection
-
a header that informs the browser what to do in the event of a Cross-Site Scripting attack is detected. There seems to be a lot of skepticism of its value for certain browsers [7]
- Cache-Control
-
a header that informs the client how the data may be cached. [8] This value can be set by the controller response but is set to a non-cache state by default here.
- Pragma
-
an HTTP/1.0 header that has been replaced by Cache-Control in HTTP 1.1. [9]
- Expires
-
a header that contains the date/time when the data should be considered stale and should be re-validated with the server. [10]
- X-Frame-Options
-
informs the browser whether the contents of the page can be displayed in a frame. [10] This helps prevent site content from being hijacked in an unauthorized manner. This will not be pertinent to our API responses.
10. Default FilterChainProxy Bean
The above behavior was put in place by the default Security Auto-Configuration — which is primarily placed within an instance of the FilterChainProxy
class [11].
This makes the FilterChainProxy
class a convenient place for a breakpoint when debugging security flows.
The FilterChainProxy
is configured with a set of firewall rules that address such things
as bad URI expressions that have been known to hurt web applications and zero or more
SecurityFilterChains arranged in priority order (first match wins).
The default configuration has a single SecurityFilterChain
that matches all URIs,
requires authentication, and also adds the other aspects we have seen so far.
Below is a list of filters put in place by the default configuration. This — by far — is not all the available filters. I wanted to at least provide a description of the default ones before we start looking to selectively configure the chain.
It is a pretty dry topic to just list them off. It would be best if you had the svc/svc-security/noauthn-security-example
example loaded in an IDE with:
-
the pom updated to include the
spring-boot-starter-security
Starter Activates Default Security Policies<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>
-
a breakpoint set on "FilterChainProxy.doFilterInternal()" to clearly display the list of filters that will be used for the request.
-
another breakpoint set on "FilterChainProxy.VirtualFilterChain.doFilter()" to pause in between each filter.
-
a browser open with network logging active and ready to navigate to http://localhost:8080/api/authn/hello?name=jim
Whenever we make a request in the default state - we will most likely visit the following filters.
- WebAsyncManagerIntegrationFilter
-
Establishes an association between the SecurityContext (where the current caller’s credentials are held) and potential async responses making use of the
Callable
feature. Caller identity is normally unique to a thread and obtained through aThreadLocal
. Anything completing in an alternate thread must have a strategy to resolve the identity of this user by some other means. - SecurityContextPersistenceFilter
-
Manages SecurityContext in between calls. If appropriate — stores the SecurityContext and clears it from the call on exit. If present — restores the SecurityContext on following calls.
- HeaderWriterFilter
-
Issues standard headers (shown earlier) that can normally be set to a fixed value and optionally overridden by controller responses.
- CsrfFilter
-
Checks all non-safe (POST, PUT, and DELETE) calls for a special Cross-Site Request Forgery (CSRF) token either in the payload or header that matches what is expected for the session. This attempts to make sure that anything that is modified on this site — came from this site and not a malicious source. This does nothing for all safe (GET, HEAD, OPTIONS, and TRACE)
- LogoutFilter
-
Looks for calls to logout URI. If matches, it ends the login for all types of sessions, and terminates the chain.
- UsernamePasswordAuthenticationFilter
-
This instance of this filter is put in place to obtain the username and password submitted by the login page. Therefore anything that is not
POST /login
is ignored. The actualPOST /login
requests have their username and password extracted, authenticated - DefaultLoginPageGeneratingFilter
-
Handles requests for the login URI (
POST /login
). This produces the login page, terminates the chain, and returns to caller. - DefaultLogoutPageGeneratingFilter
-
Handles requests for the logout URI (
GET /logout
). This produces the logout page, terminates the chain, and returns to the caller. - BasicAuthenticationFilter
-
Looks for BASIC Authentication header credentials, performs authentication, and continues the flow if successful or if no credentials where present. If credentials were not successful it calls an authentication entry point that handles a proper response for BASIC Authentication and ends the flow.
- RequestCacheAwareFilter
-
This retrieves an original request that was redirected to a login page and continues it on that path.
- SecurityContextHolderAwareRequestFilter
-
Wraps the HttpServletRequest so that the security-related calls (isAuthenticated(), authenticate(), login(), logout()) are resolved using the Spring security context.
- AnonymousAuthenticationFilter
-
Assigns anonymous use to security context if no user is identified
- SessionManagementFilter
-
Performs any required initialization and security checks in order to setup the current session
- ExceptionTranslationFilter
-
Attempts to augment any thrown AccessDeniedException and AuthenticationException with details related to the denial. It does not add any extra value if those exceptions are not thrown. This will save the current request (for access by RequestCacheAwareFilter) and commence an authentication for AccessDeniedExceptions if the current user is anonymous. The saved current request will allow the subsequent login to complete with a resumption of the original target. If FORM Authentication is active — the commencement will result in a 302/REDIRECT to the
/login
URI. - FilterSecurityInterceptor
-
Applies the authenticated user against access constraints. It throws an AccessDeniedException if denied, which is caught by the ExceptionTranslationFilter.
This is also where the security filter chain hands control over to the application filter chain where the endpoint will get invoked.
11. Summary
In this module we learned:
-
the importance of identity, authentication, and authorization within security
-
the purpose for and differences between encoding, encryption, and cryptographic hashes
-
purpose of a filter-based processing architecture
-
the identity of the core components within Spring Authentication
-
where the current user authentication is held/located
-
how to activate default Spring Security configuration
-
the security features of the default Spring Security configuration
-
to step through a series of calls through the Security filter chain for the ability to debug future access problems