Spring Security in Action - Part 2. Implementation (Chapters 3-7)
Chapter 3. User Management
Chapter 3 covers User, UserDetailService, and UserDetailManager. In the process of user authentication, the authentication provider goes through the process of authenticating the user according to the authentication logic. At this time, this is about the system that manages the memory user.
UserDetils and Implementation
For user descriptions, Spring Security implements and conforms to the ``UserDetilsinterface. You can implementUserDetailsdirectly as a class, or you can build and useUserDetaildepending on usage.UserDetailscontains one or more methods for retrieving permissions, passwords, usernames, or managing account activation and deactivation. Account management is basically processed astrue```, but can be customized and implemented according to separate internal service policies.
public interface UserDetails extends Serializable {
Collection<? extends GrantedAuthority> getAuthorities();
String getPassword();
String getUsername();
boolean isAccountNonExpired();
boolean isAccountNonLocked();
boolean isCredentialsNonExpired();
boolean isEnabled();
}There is a way to implement this by implementing UserDetails as a single class, but you can also build the User model provided for UserDetailService. This model is also an implementation of UserDetails.
In this case, you can also create a UserDetiails``` object by creating a ```UserBuilder``` using the withUsername``` method.
UserDetails userDetails = User.withUsername(member.getEmail())
.password(member.getPassword())
.roles(member.getRole())
.build();About UserDetailService and the three UserDetailManagers
UserDetailService is a core part of authentication and manages users in memory in the authentication logic. The UserDetailService interface has only one method: loadUserByUsername(String username), which checks in memory if there is a user matching Username. If there is no corresponding user, a RuntimeException is thrown as ``UsernameNotFoundException```.
public interface UserDetailsService {
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}This is an example of the ``loadUserByUsername``` implementation presented in the book. You can see that among the users, only users with the same username are extracted and returned to UserDetails.
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException{
return Users.stream()
.filter(u -> u.getUsername().equals(username))
.findFirst()
.orElseThrows(() -> new UsernameNotFoundException("User not found"));
}UserDetailManager extends the functionality of ````UserDetailServiceand adds methods.InMemoryUserDetailsManagerandJdbcUserDetailsManagerare provided as implementation classes ofUserDetailManager. It is used differently depending on whether it uses internal memory or manages users stored in a SQL database and connects directly to the database via JDBC. LdapUserDetailsManager``` is also provided, but when using LDAP, separate dependency settings are required.
public interface UserDetailsManager extends UserDetailsService {
void createUser(UserDetails user);
void updateUser(UserDetails user);
void deleteUser(String username);
void changePassword(String oldPassword, String newPassword);
boolean userExists(String username);
}Additionally, you can change the query of JdbcUserDetailsManager, such as setUsersByUsernameQuery````, and register it as a @Bean with UserDetailsService``` config.
@Bean
public UserDetailsService userDetailsService(DataSource dataSource) {
String usersByUsernameQuery = "select username, password, enabled from spring.users where username = ?";
String authsByUserQuery = "select username, authority from spring.authorities where username = ?";
var userDetailsManager = new JdbcUserDetailsManager(dataSource);
userDetailsManager.setUsersByUsernameQuery(usersByUsernameQuery);
userDetailsManager.setAuthoritiesByUsernameQuery(authsByUserQuery);
return userDetailsManager;
}Chapter 4. Encryption processing
Verify the password using ``PasswordEncorder``` using the password provided by the authentication provider. There are some differences in this part from the time of writing (2023-09), but I would like to describe it based on the book in order to understand the process.
PasswordEncorder interface
You can use encode() for encryption and matches()``` to check whether the encoded string matches the password. upgradeEncoding(CharSequence encodedPassword)defaults to ````false````, but handling true will cause the encoded password to be re-encoded. ThePasswordEncoder``` implementation options provided by Spring security include the following options. Description of each hashing algorithm
+NoOpPasswordEncoder
+BCryptPasswordEncoder
+Pbkdf2PasswordEncoder
- SCryptPasswordEncoder
- Argon2PasswordEncoder
- DelegatingPasswordEncoder
####NoOpPasswordEncoder It should only be used for testing or compatibility with older systems. It is currently Deprecated. ####BCryptPasswordEncoder####
@Bean
public BCryptPasswordEncoder passwordEncoder() {
return newBCryptPasswordEncoder(16);
}You can also specify a strength coefficient that represents the log rounds used in the encoding process. In the example below, 4 is the intensity factor. This value is 2 to the power of 4, or 16 hashing rounds. b is used when creating salt in BCrypt.
SecureRandom s = SecureRandom.getInstanceStrong();
PasswordEncoder p = new BCryptPasswordEncoder(4, s);DelegatingPasswordEncoder
Helps you flexibly manage multiple hashing strategies. Registers NoOpasswordEncoder for the prefix ```{noop}, BCryptPasswordEncoder for {bcrypt}, and SCryptPasswordEncoder for {scrypt}````.
Spring Security provides a static method that returns an implementation of DelegatingPasswordEncoder.
PasswordEncoder passwordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();As Spring Security uses DelegatingPasswordEncoder by default, it now supports several strategies, including deprecated PasswordEncoders such as NoOpPasswordEncoder. At this time, you can see that @SuppressWarnings("deprecation") has been added to the createDelegatingPasswordEncoder method to avoid the deprecated warning for NoOpPasswordEncoder.
So how do we implement encryption other than password encryption?
The Spring Security Cryptography Module (SSCM) provides an alternative to implementing a key generator and encryptor.
StringKeyGenerator keyGenerator = KeyGenerators.string();
String salt = keyGenertor.generateKey();
BytesKeyGenertor keyGenerator = KeyGenerators.secureRandom(16);
BytesKeyGenertor keyGenerator = KeyGenerators.shared(16);An encryptor is an object that implements an encryption algorithm. There are encryptors called BytesEncryptor and TextEncryptor, and each handles different data formats. While BytesEncryptor returns output as a string, TextEncryptor is more general-purpose and takes input data as a byte array.
BytesEncryptor e = Encryptors.stronger(password, salt);TextEncryptor has three main formats: Encryptors.text(), Encryptors.delux(), and Encryptors.queryableText(). Encryptors.text() and Encryptors.delux() return different output even if the encrypt() method is called repeatedly, because an initialization vector is created.
Encryptors.queryableText() is a queryable text that is guaranteed to return the same output if the input is the same.
TextEncryptor e = Encryptors.queryableText(password, salt);
String encrypted = e.encrypt(valueToEncrypt);Chapter 5. Authentication Implementation
- Implement authentication logic with custom AuthenticationProvider
- Use HTTP Basic and form-based login authentication methods
- Understanding and managing the SecurityContext component The authentication filter intercepts the request and delegates the authentication responsibility to the authentication manager. And the authentication result verified by the authentication provider is stored in the security context (SecurityContext). ~~#### Understanding AuthenticationProvider#### ~~
It inherits the Principal contract, which is the Authentication contract. In Authentication, you can add requirements such as a password or details about the authentication request. It is designed to extend the Principal contract of the Java Security API, providing compatibility benefits. (Migration Benefits)
public interface Authentication extends Principal, Serializable {
Collection<? extends GrantedAuthority> getAuthorities();
Object getCredentials();
Object getDetails();
Object getPrincipal();
boolean isAuthenticated();
void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;
}The AuthenticationProvider interface delegates finding the user to UserDetailsService and manages the password in the authentication process with PasswordEncoder.
However, AuthenticationProvider has been deprecated since Spring Security 5, and it is recommended to use AuthenticationManager instead. This is because AuthenticationManager integrates the management of AuthenticationProvider and Providers that support various authentication methods and is responsible for actual authentication processing.
Both AuthenticationProvider and WebSecurityConfigurerAdapter in the implementation are currently deprecated and have not been written.
There is something like this at the end of the article, and I think this is the core content of the book and something that developers who use dependencies should definitely consider.
Frameworks, especially those widely used in applications, are developed with the participation of many smart developers. Even so, there are not many cases where the framework is implemented incorrectly. Let's analyze the application before concluding that a problem is the framework's fault.
If you decide to use a framework, use it as closely as possible for its intended purpose. For example, if you are using Spring Security and feel that you are writing more custom code to implement security than what the framework provides, you should question why this is happening.
Using SecurityContext
A security context is an instance that stores an Authentication object.
public interface SecurityContext extends Serializable{
Authentication getAuthentication();
void setAuthentication(Authentication authentication);
}To manage SecurityContext, Spring Security provides an object called SecurityContextHolder. MODE_THREADLOCAL, MODE_INHRITABLETHREADLOCAL, MODE_GLOBAL options are provided, and settings can be set in Config as follows.
return() -> SecurityContextHolder.setStrategyName( SecurityContextHodler.MODE_INHERITABLETHREADLOCAL);MODE_THREADLOCAL
(Use retention strategy for security context) The default is for each thread to store its own security context details. The new thread also has its own security context and the parent thread's details are not copied into the new thread's security context. To obtain Authentication from this SecurityContext, you can obtain it by directly injecting it into the endpoint parameter.
@GetMapping
public String hello(Authentication a){
return a.getName();
}MODE_INHRITABLETHREADLOCAL
(Using retention strategy for asynchronous calls) Similar to MODE_THREADLOCAL, but for asynchronous methods, tells Spring Security to copy the security context to the next thread. The SecurityContext created in the parent thread can also be shared by child threads. That is, it copies the details from the original thread to the newly created thread in the asynchronous method.
MODE_GLOBAL
(Use retention strategy for standalone applications) Ensures that all threads in an application see the same security context instance. Please note that it does not support thread safety. It is important to remember that the shared thread strategy applies only to threads managed by Spring. There are several Spring Security utility tools that help propagate the security context to newly created threads.
DelegatingSecurityContextRunnable
This is a class that implements the Runnable interface. It receives a Runnable object and the current SecurityContext as a constructor, and propagates the security context when executing the Runnable object in a newly created thread. It can be used after executing a task without a return value.
SecurityContext securityContext = SecurityContextHolder.getContext();
Runnable runnable = new MyRunnable();
Runnable wrappedRunnable = new DelegatingSecurityContextRunnable(runnable, securityContext);
new Thread(wrappedRunnable).start();DelegatingSecurityContextCallable
For operations with a return value, you can use the DelegatingSecurityContextCallable alternative. Specifies a Callable operation to be executed asynchronously by copying the current security context.
DelegatingSecurityContextExecutorService
A class that implements the ExecutorService interface and can automatically handle tasks executed in threads that newly create a security context. Similarly, DelegatingSecurityContextScheduledExecutorService is used as a decorator when security context propagation needs to be implemented for scheduled tasks.
SecurityContext securityContext = SecurityContextHolder.getContext();
ExecutorService executorService = Executors.newFixedThreadPool(10);
ExecutorService wrappedExecutorService = new DelegatingSecurityContextExecutorService(executorService, securityContext);
wrappedExecutorService.submit(new MyRunnable());HTTP Basic Authentication Form
http.httpBasic(c->{
// How to change area name
c.realName("OTHER");
// Apply custom AuthenticationEntryPoint
c.authenticationEntiryPoint(new CustomEntryPoint());
})
http.authorizeRequests().anyRequest().authenticated();AuthenticationEntryPoint interface
Spring Security provides several AuthenticationEntryPoint implementations, which allow you to respond to various authentication failure scenarios. For example, ``LoginUrlAuthenticationEntryPointis often used in web applications, andHttp403ForbiddenEntryPoint``` is often used in REST APIs.
Form-based login authentication
In large applications that require horizontal scalability, it is not recommended to use server-side sessions to manage security contexts. When using form-based login, use the formLogin() method. If you do not log in in Spring Security, you will be redirected to the login page.
http.formLogin();By using @Controller instead of @RestController```, the method return value is rendered as ```home.html rather than sent as an HTTP response.
If you have not logged in at this time, you will be redirected to the /login login page, and if you access the /logout path, you will be redirected to the logout page. (HTTP 302)
@Controller
public class HelloController{
@Getmapping("/home")
public String home(){ return "home.html"; }
}You can use the AuthenticationSuccessHandler and AuthenticationFailureHandler objects for custom configuration of ````formLogin()```.
Chapter 6: Hands-on: Small, Secure Web Applications
This chapter mainly talks about the implementation team. Deprecated content as of the date of writing has been excluded.
- Add dependencies
- You can use Flyway and Liquibase dependencies to specify SQL version.
- Register as PasswordEncoder
@Beanfor password encryption - User, Authority entity settings
- Implementation of the UserDetails interface
@Override
public CustomUserDetails loadUserByUsername(String username){
Supplier<UsernameNotFoundException> s = () -> new UsernameNotFoundException("user not found");
User user = userRepository.findByUsername(username)
.orElseThrow(s);
return new CustomUserDetails(user);
}If the password matches in the authentication logic, encoder.matches(rawPassword, user.getPassword()) has been authenticated, Authentication is returned.
Most of the time, the same functionality can be implemented in several different ways, and you need to choose the simplest solution to make the code easier to understand, thereby reducing the room for errors and security breaches.
Chapter 7. Configure Authorization: Restrict Access
In Spring Security, the authentication filter stores the authenticated user details in the security context and later determines whether to allow the request with the authorization filter.
A user has one or more privileges. That is, with GrantedAuthority, UserDetils has one or more permissions. getAuthorities() method of UserDetils returns all permissions of the user details.
Endpoint permission control
You can allow access only to those with the "WRITE" permission by controlling specific permissions other than ``permitAll()```. In addition, there are three methods for controlling permissions:
- hasAuthority()
- hasAnyAuthority()
- access()
// Use of .hasAuthority()
http.authoriseRequests()
.anyRequest()
.hasAuthority("WRITE");
// Use of .access()
// There may be real-world scenarios where you need to write complex expressions.
http.authoriseRequests()
.anyRequest()
.access("hasAuthority('WRITE') and !hasAuthority('DELETE')");What is Spring Expression (SpEL)?
This is an expression language provided by Spring Framework.
Spring expressions can be used in XML or annotation-based configurations and are used to explore and manipulate object graphs. Spring expressions support various operators and functions of Java and provide various functions such as calling object properties or methods, accessing indexes of lists or maps, and arithmetic operations.
In the book's example, it can be used in more general situations where the access() method is used by using SpEL expressions, such as when accessing an endpoint only after noon.
If the request is not authorized, the HTTP status will return HTTP 403 Forbidden```. Here, unlike authority, role has a thicker texture than authority, but the same GrantedAuthority is used, and the role name must start with ROLE_```.
User.withUsername("john")
.password("qwer1234")
.authorities("ROLE_MEMBER")
.build();You can grant all access permissions with permitAll(), and deny all requests with denyAll().
Comments
No comments yet. Be the first!
164 posts in 테크
- 2233D Gaussian Splatting vs Unreal Engine: Two Ways to Build a 3D World — and Where Each One Ships
- 222LLMs Inside Unreal Engine: The New Skills Game Developers Need in 2026
- 220Living With Claude Fable 5: How the Most Capable Model Changes the Way You Actually Work
- 219Luma's Bet: From Video Generator to a Single Model That Thinks in Pixels
- 215The Best AI Video Models in 2026: Types, Differences, and Where This Is All Going