6 min readOct 24, 2023

--

Securing Your Spring Boot Application with Spring Security

Security is a paramount concern for any application, and when you’re developing a Spring Boot application, you have a powerful tool at your disposal: Spring Security. Spring Security is a robust and highly customizable framework that allows you to control access to your application, manage authentication, and protect your resources. In this blog, we’ll delve into the world of Spring Security, exploring its concepts, components, and providing a hands-on implementation example.

What is Spring Security?

Spring Security is an extension of the Spring Framework that provides comprehensive security features for Java applications. It is used to secure web applications and web services, including REST APIs. Spring Security’s core purpose is to handle authentication and authorization. With Spring Security, you can easily manage user authentication, password storage, access control, and more.

Key Concepts of Spring Security

Before diving into the implementation, let’s understand some key concepts of Spring Security:

Authentication: Authentication is the process of verifying the identity of a user. Spring Security supports various authentication mechanisms, such as form-based, basic authentication, OAuth, and more.

Authorization: Authorization is the process of determining whether an authenticated user has permission to access a specific resource or perform a specific action. Spring Security allows you to define access rules using expressions or configuration.

User Details Service: Spring Security typically relies on a User Details Service to load user details (e.g., username and password) from a data store (usually a database).

Principal and Authorities: In Spring Security, the authenticated user is represented as a principal with associated Authorities (roles or permissions). You can control access to resources based on these authorities.

Security Filter Chain: Spring Security is implemented as a chain of filters that intercept incoming requests and perform various security-related tasks. These filters handle tasks like authentication, authorization, session management, and more.

Now that we have a basic understanding of Spring Security, let’s implement it in a Spring Boot application.

Create a Spring Boot Project

You can use the Spring Initializer to create a new Spring Boot project with the necessary dependencies. Include “Spring Web” and “Spring Security.”

The main class of the Spring Boot application has the @SpringBootApplication annotation, which marks it as the starting point of the application. When you run this class, it starts the Spring Boot application.

@SpringBootApplication

public class DemoApplication {

public static void main(String[] args) {

SpringApplication.run(DemoApplication.class, args);

}

}

When run, the console logs print the default password that was randomly generated as a part of the default security configuration. However, we must implement our own credentials based on our needs. So, let’s look at how we can secure our application. The overall flow of the architecture looks like this:

Entity Classes

To implement our own username and password, we need two entity classes, a user class to store user information and have roles associated with them through many to many relationships. Role class defines roles for users. Product class to check our credentials by implementing two endpoints, one all users can access, and the other only admin can access.

@Data

@Entity

@AllArgsConstructor

@NoArgsConstructor

public class User {

@Id

@GeneratedValue(strategy = GenerationType.IDENTITY)

private Long id;

private String name;

private String email;

private String password;

@ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)

@JoinTable(name = “users_roles”, joinColumns = {

@JoinColumn(name = “user_id”, referencedColumnName = “id”) }, inverseJoinColumns = {

@JoinColumn(name = “role_id”, referencedColumnName = “id”) })

private List<Role> roles;

public User(String name, String email, String password, List<Role> roles) {

this.name = name;

this.email = email;

this.password = password;

this.roles = roles;

}

}

************

@Entity

@Data

@AllArgsConstructor

@NoArgsConstructor

public class Role {

@Id

@GeneratedValue(strategy = GenerationType.IDENTITY)

private Long id;

private String roleName;

public Role(String roleName){

this.roleName = roleName;

}

}

****************

@Entity

@Data

@AllArgsConstructor

@NoArgsConstructor

public class Product {

@Id

@GeneratedValue(strategy = GenerationType.IDENTITY)

private Long id;

private String type;

private Long quantity;

}

Repository Interface

User Repository, Role Repository and Product Repository provides us basic crud operations for the User, Role and Product entity, and it allows us to interact with the role, user and product data in the database.

public interface UserRepository extends JpaRepository<User, Long> {

}

***************

public interface RoleRepository extends JpaRepository<Role, Long> {

}

****************

public interface ProductRepository extends JpaRepository<Product, Long> {

Product findByType(String type);

}

Product Service Class

Product service implementation of the product service interface provides methods for retrieving and adding products, interacting with the Product repository.

public interface ProductService {

public Product getProduct(Long id);

public User addProduct(Product product);

}

*********

@Service

@RequiredArgsConstructor

public class ProductServiceImpl implements ProductService {

private final ProductRepository productRepository;

@Override

public Product getProduct(String type) {

return productRepository.findByType(type);

}

@Override

public Product addProduct(Product product) {

return productRepository.save(product);

}

}

Controller Class

I implemented a user controller with two endpoints for getting and adding products to test our application.

- The @GetMapping(“/{type}”) endpoint retrieves a product by their type.

- The @PostMapping(“/add”) endpoint adds a new product.

@RestController

@RequiredArgsConstructor

@RequestMapping(“/products”)

public class UserController {

private final ProductService productService;

@GetMapping(“/{type}”)

public ResponseEntity<Product> getProduct(@PathVariable String type){

return new ResponseEntity<Product>(productService.getProduct(type), HttpStatus.OK);

}

@PostMapping(value = “/add”)

public ResponseEntity<Product> addProduct(@RequestBody Product product){

return new ResponseEntity<Product>(productService.addProduct(product), HttpStatus.OK);

}

}

User Details Service

The CustomUserDetailsService class implements the UserDetailsService interface. It loads user details from the database and converts them into CustomUserDetails. This service is essential for retrieving user information, including roles, for authentication and authorization. The CustomUserDetails class implements the UserDetails interface and represents custom user details. It is used for authenticating and authorizing users in Spring Security.

@Data

public class CustomUserDetails implements UserDetails {

private String email;

private String password;

private List<Role> roles;

public CustomUserDetails(User user) {

this.email = user.getEmail();

this.password = user.getPassword();

this.roles = user.getRoles();

}

@Override

public Collection<? extends GrantedAuthority> getAuthorities() {

String[] userRoles = getRoles().stream().map((role) -> role.getRoleName()).toArray(String[]::new);

Collection<GrantedAuthority> authorities = AuthorityUtils.createAuthorityList(userRoles);

return authorities;

}

@Override

public String getPassword() {

return password;

}

@Override

public String getUsername() {

return email;

}

@Override

public boolean isAccountNonExpired() {

return true;

}

@Override

public boolean isAccountNonLocked() {

return true;

}

@Override

public boolean isCredentialsNonExpired() {

return true;

}

@Override

public boolean isEnabled() {

return true;

}

}

*************

@Service

public class CustomUserDetailsService implements UserDetailsService {

@Autowired

private UserRepository userRepository;

@Override

public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {

User user = userRepository.findByEmail(email);

if (user == null) {

throw new UsernameNotFoundException(“User not found”);

}

// Extract roles and convert them to GrantedAuthority

Collection<GrantedAuthority> authorities = new HashSet<>();

for (Role role : user.getRoles()) {

authorities.add(new SimpleGrantedAuthority(“ROLE_” + role.getRoleName()));

}

// Create CustomUserDetails with username, password, and authorities

CustomUserDetails userDetails = new CustomUserDetails(new User(user.getEmail(),

user.getPassword(), user.getRoles())

);

return userDetails;

}

}

Security Filter

With @EnableWebSecurity the Applicationsecurity class is responsible for configuring Spring Security for the application. It defines the security filter chain, authentication manager, and authentication provider.

It configures HTTP security rules, allowing public access to certain endpoints and restricting access to others. It uses BCryptPasswordEncoder for password encoding and hashing. It sets up a custom UserDetailsService (CustomUserDetailsService) to retrieve user details and roles. It creates an AuthenticationProvider to verify user credentials.

@Configuration

@EnableWebSecurity

@RequiredArgsConstructor

public class ApplicationSecurity {

private final CustomUserDetailsService customUserDetailsService;

@Bean

public PasswordEncoder passwordEncoder(){

return new BCryptPasswordEncoder();

}

@Bean

public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception{

return config.getAuthenticationManager();

}

@Bean

public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {

httpSecurity

.csrf((csrf) -> csrf.disable())

.authorizeHttpRequests((authorizeHttpRequests) ->

authorizeHttpRequests

.requestMatchers(HttpMethod.POST,”/products/add”).hasAuthority(“Admin”)

.anyRequest().authenticated()

)

.httpBasic(withDefaults());

return httpSecurity.build();

}

@Bean

public AuthenticationProvider authenticationProvider(){

DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();

authenticationProvider.setUserDetailsService(customUserDetailsService);

authenticationProvider.setPasswordEncoder(passwordEncoder());

return authenticationProvider;

}

}

In summary, we’ve covered the main classes and their roles in securing the application. Spring Security is a crucial aspect of building secure web applications, and understanding its components is essential for creating robust and protected systems.

--

--

No responses yet