Implementing Security in Spring Boot Applications with Spring Security

Introduction to Spring Security

Spring Security provides comprehensive security services for Java EE applications. As a core component of the Spring ecosystem, it implements layered security architecture where each application layer can be protected independently. This framework enables fine-grained access control at the controller, service, and data access levels through annotation-based configuration.

The framework's modular design ensures low coupling between components, allowing developers to combine different security modules to meet specific requirements. Unlike Apache Shiro which is primarily suited for monolithic applications, Spring Security entegrates seamlessly with microservices architectures built using Spring Cloud.

Two fundamental aspects of application security are authentication (verifying identity) and authorization (determining permissions). Spring Security addresses both domains effective. The framework includes essential dependencies like spring-security-web and spring-security-config, which Spring Boot encapsulates through the spring-boot-starter-security starter module.

Implementation Example

Create a new Spring Boot project with required dependencies:

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-devtools</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>
    <dependency>
        <groupId>org.thymeleaf.extras</groupId>
        <artifactId>thymeleaf-extras-springsecurity4</artifactId>
    </dependency>
</dependencies>

Security Configuration

Create a security configuration class extending WebSecurityConfigurerAdapter with the @EnableWebSecurity annotation:

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Resource
    private UserDetailsService userDetailsService;

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService)
            .passwordEncoder(new BCryptPasswordEncoder());
    }

    @Bean
    public UserDetailsService memoryUserDetailsService() {
        InMemoryUserDetailsManager userManager = new InMemoryUserDetailsManager();
        userManager.createUser(
            User.withUsername("developer")
                .password(new BCryptPasswordEncoder().encode("secret"))
                .roles("USER")
                .build()
        );
        userManager.createUser(
            User.withUsername("administrator")
                .password(new BCryptPasswordEncoder().encode("secret"))
                .roles("ADMIN", "USER")
                .build()
        );
        return userManager;
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .antMatchers("/assets/**", "/home").permitAll()
                .antMatchers("/profile/**").hasRole("USER")
                .antMatchers("/admin/**").access("hasRole('ADMIN') and hasRole('DBA')")
                .and()
            .formLogin()
                .loginPage("/signin")
                .failureForwardUrl("/signin-error")
                .and()
            .logout()
                .logoutSuccessUrl("/")
                .invalidateHttpSession(true)
                .deleteCookies("session-id")
                .and()
            .exceptionHandling()
                .accessDeniedPage("/forbidden");
    }
}

BCryptPasswordEncoder implements SHA-256 hashing with salt and secret key encryption. During registration, user passwords are hashed and stored in the database. During authentication, entered passwords are hashed using the same algorithm and compared with stored values. This one-way process ensures that even if the database is compromised, passwords remain difficult to crack.

Controller Implementation

@Controller
public class PageController {

    @RequestMapping("/")
    public String root() {
        return "redirect:/home";
    }

    @RequestMapping("/home")
    public String home() {
        return "home";
    }

    @RequestMapping("/profile/dashboard")
    public String userProfile() {
        return "profile/dashboard";
    }

    @RequestMapping("/signin")
    public String signin() {
        return "signin";
    }

    @RequestMapping("/signin-error")
    public String signinError(Model model) {
        model.addAttribute("signinError", true);
        return "signin";
    }

    @GetMapping("/forbidden")
    public String accessForbidden() {
        return "forbidden";
    }
}

Template Configuration

Configure Thymeleaf in application.yml:

spring:
  thymeleaf:
    mode: HTML5
    encoding: UTF-8
    cache: false

Sign-in template signin.html:


<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Authentication</title>
    <link rel="stylesheet" th:href="@{/assets/style.css}" />
</head>
<body>
    <h1>User Authentication</h1>
    <p>Standard user: developer / secret</p>
    <p>Administrator: administrator / secret</p>
    <p th:if="${signinError}" class="error">Invalid credentials</p>
    <form th:action="@{/signin}" method="post">
        <label for="username">Username:</label>
        <input type="text" id="username" name="username" autofocus>
        <label for="password">Password:</label>
        <input type="password" id="password" name="password">
        <input type="submit" value="Sign In">
    </form>
    <a th:href="@{/home}">Return to Home</a>
</body>
</html>

Home template home.html:


<html xmlns:th="http://www.thymeleaf.org" 
      xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4">
<head>
    <title>Spring Security Demo</title>
    <meta charset="utf-8" />
    <link rel="stylesheet" th:href="@{/assets/style.css}" />
</head>
<body>
    <h1>Welcome to Secure Application</h1>
    <p>This page is publicly accessible.</p>

    <div th:fragment="userPanel" sec:authorize="isAuthenticated()">
        Authenticated User: <span sec:authentication="name"></span> |
        Assigned Roles: <span sec:authentication="principal.authorities"></span>
        <div>
            <form th:action="@{/logout}" method="post">
                <input type="submit" value="Sign Out" />
            </form>
        </div>
    </div>

    - <a th:href="@{/profile/dashboard}">Access Protected Profile Section</a>
</body>
</html>

Restricted access template forbidden.html:


<html xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4">
<body>
    <div>
        <h2>Insufficient Permissions</h2>
        <div sec:authorize="isAuthenticated()">
            <p>User authenticated</p>
            <p>Identity: <span sec:authentication="name"></span></p>
            <p>Roles: <span sec:authentication="principal.authorities"></span></p>
        </div>
        <div sec:authorize="isAnonymous()">
            <p>No active session</p>
        </div>
        <p>Access denied to requested resource.</p>
    </div>
</body>
</html>

Protected profile template profile/dashboard.html:


<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>Protected Profile Area</title>
    <meta charset="utf-8" />
    <link rel="stylesheet" th:href="@{/assets/style.css}" />
</head>
<body>
    <div th:replace="home :: userPanel"></div>
    <h1>Secure Profile Dashboard</h1>
    <p><a th:href="@{/home}">Back to Home</a></p>
    <p><a th:href="@{/content/articles}">Manage Articles</a></p>
</body>
</html>

Method-Level Security

Enable method-level protection using @EnableGlobalMethodSecurity with prePostEnabled = true. This activates @PreAuthorize and @PostAuthorize annotations supporting Spring Expression Language:

public class Article {
    private Long identifier;
    private String title;
    private String body;

    public Article(Long identifier, String title, String body) {
        this.identifier = identifier;
        this.title = title;
        this.body = body;
    }

    // Getters and setters
}

public interface ArticleService {
    List<Article> getAllArticles();
    void removeArticle(long identifier);
}

@Service
public class ArticleServiceImpl implements ArticleService {
    private List<Article> articles = new ArrayList<>();

    public ArticleServiceImpl() {
        articles.add(new Article(1L, "Spring Framework Guide", "Comprehensive overview"));
        articles.add(new Article(2L, "Microservices Patterns", "Architectural approaches"));
    }

    @Override
    public List<Article> getAllArticles() {
        return articles;
    }

    @Override
    public void removeArticle(long identifier) {
        articles.removeIf(article -> article.getIdentifier() == identifier);
    }
}

@RestController
@RequestMapping("/content/articles")
public class ArticleController {
    @Autowired
    private ArticleService articleService;

    @GetMapping
    public ModelAndView listArticles(Model model) {
        model.addAttribute("articlesCollection", articleService.getAllArticles());
        return new ModelAndView("articles/list", "articleModel", model);
    }

    @PreAuthorize("hasAuthority('ROLE_ADMIN')")
    @GetMapping(value = "/{id}/removal")
    public ModelAndView delete(@PathVariable("id") Long id, Model model) {
        articleService.removeArticle(id);
        model.addAttribute("articlesCollection", articleService.getAllArticles());
        return new ModelAndView("articles/list", "articleModel", model);
    }
}

Article listing template articles/list.html:


<html xmlns:th="http://www.thymeleaf.org" 
      xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4">
<body>
    <p>Current User: <span sec:authentication="name"></span></p>
    <p>User Roles: <span sec:authentication="principal.authorities"></span></p>

    | ID | Title | Description |
|---|---|---|
|  |  |  | <a th:href="@{'/content/articles/' + ${article.identifier}+'/removal'}"> Delete </a> |
</body>
</html>

Database Integration

Add MySQL and JPA dependencies:

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

Database configuration in application.yml:

spring:
  thymeleaf:
    mode: HTML5
    encoding: UTF-8
    cache: false
  datasource:
    url: jdbc:mysql://localhost:3306/security_demo?useUnicode=true&characterEncoding=utf8
    username: root
    password: password
  jpa:
    hibernate:
      ddl-auto: update
    show-sql: true

Entity classes:

@Entity
public class SystemUser implements UserDetails, Serializable {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false, unique = true)
    private String username;

    @Column
    private String password;

    @ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
    @JoinTable(name = "user_authority", 
               joinColumns = @JoinColumn(name = "user_id", referencedColumnName = "id"),
               inverseJoinColumns = @JoinColumn(name = "authority_id", referencedColumnName = "id"))
    private List<Authority> authorities;

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return authorities;
    }

    // Implement other UserDetails methods
}

@Entity
public class Authority implements GrantedAuthority {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false)
    private String name;

    @Override
    public String getAuthority() {
        return name;
    }

    // Getters and setters
}

public interface UserRepository extends JpaRepository<SystemUser, Long> {
    SystemUser findByUsername(String username);
}

@Service
public class SystemUserDetailsService implements UserDetailsService {
    @Autowired
    private UserRepository userRepository;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        return userRepository.findByUsername(username);
    }
}

Database schema:

CREATE DATABASE `security_demo` CHARACTER SET 'utf8' COLLATE 'utf8_general_ci';

CREATE TABLE `authority` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) NOT NULL,
  PRIMARY KEY (`id`)
);

CREATE TABLE `system_user` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `username` varchar(255) NOT NULL,
  `password` varchar(255),
  PRIMARY KEY (`id`)
);

CREATE TABLE `user_authority` (
  `user_id` bigint(20) NOT NULL,
  `authority_id` bigint(20) NOT NULL,
  FOREIGN KEY (`user_id`) REFERENCES `system_user` (`id`),
  FOREIGN KEY (`authority_id`) REFERENCES `authority` (`id`)
);

Initialize test data with encoded passwords:

@RunWith(SpringRunner.class)
@SpringBootTest
public class PasswordEncodingTest {
    @Test
    public void generateEncodedPasswords() {
        BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
        for(int i = 0; i < 5; i++) {
            System.out.println(encoder.encode("secret"));
        }
    }
}

Tags: Spring Security Spring Boot Authentication Authorization thymeleaf

Posted on Sat, 20 Jun 2026 16:49:18 +0000 by RabPHP