Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Implementing Spring Security with Spring Boot and MyBatis

Tech May 11 7

This guide demonstrates how to integrate Spring Security into a Spring Boot application using Gradle, MySQL, and MyBatis. We will cover the essential configurations for authentication and authorization, including password encryption and role-based access control.

Database Setup

First, create a database schema to store user credentials and roles. The following SQL script creates a user table and inserts sample data with distinct roles.

CREATE TABLE `app_user` (
  `id` INT AUTO_INCREMENT PRIMARY KEY,
  `username` VARCHAR(100) UNIQUE NOT NULL,
  `password_hash` VARCHAR(255) NOT NULL,
  `roles` VARCHAR(200) DEFAULT 'ROLE_USER'
);

INSERT INTO `app_user` (`username`, `password_hash`, `roles`) 
VALUES ('admin_user', 'admin_pass', 'ROLE_ADMIN,ROLE_USER');

INSERT INTO `app_user` (`username`, `password_hash`, `roles`) 
VALUES ('standard_user', 'user_pass', 'ROLE_USER');

Gradle Dependencies

Configure the build.gradle file to include necessary dependencies for Spring Boot Web, Security, Thymeleaf, MyBatis, and MySQL connectivity.

plugins {
    id 'java'
    id 'org.springframework.boot' version '2.7.0'
    id 'io.spring.dependency-management' version '1.0.11.RELEASE'
}

group 'com.example'
version '1.0.0'

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.boot:spring-boot-starter-security'
    implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
    implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity5'
    
    implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:2.2.2'
    implementation 'mysql:mysql-connector-java:8.0.29'
    
    implementation 'org.springframework.boot:spring-boot-devtools'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

Application Configuration

Define the database connection details and MyBatis configuration in application.yml.

server:
  port: 8081

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/security_db
    username: root
    password: root
    driver-class-name: com.mysql.cj.jdbc.Driver
  thymeleaf:
    cache: false
    mode: HTML

mybatis:
  mapper-locations: classpath:mapper/*.xml

Security Configuration

Create a configuration class to define security rules. This setup disables CSRF for simplicity, configures public access to static resources, and defines the login/logout behavior.

package com.example.app.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class AppSecurityConfig {

    @Bean
    public BCryptPasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http.csrf().disable()
            .authorizeRequests()
                .antMatchers("/register", "/css/**", "/js/**", "/images/**").permitAll()
                .antMatchers("/admin/**").hasRole("ADMIN")
                .anyRequest().authenticated()
            .and()
            .formLogin()
                .loginPage("/login")
                .defaultSuccessUrl("/home")
                .permitAll()
            .and()
            .logout()
                .logoutSuccessUrl("/login?logout")
                .invalidateHttpSession(true)
                .permitAll();
        
        return http.build();
    }
}

Custom User Details Service

Implement UserDetailsService to load user-specific data from the database during the authentication process.

package com.example.app.security;

import com.example.app.model.AppUser;
import com.example.app.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import java.util.HashSet;
import java.util.Set;

@Service
public class CustomUserDetailsService implements UserDetailsService {

    @Autowired
    private UserService userService;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        AppUser appUser = userService.findByUsername(username);
        if (appUser == null) {
            throw new UsernameNotFoundException("User not found with username: " + username);
        }

        Set<GrantedAuthority> authorities = new HashSet<>();
        for (String role : appUser.getRoles().split(",")) {
            authorities.add(new SimpleGrantedAuthority(role.trim()));
        }

        return new User(appUser.getUsername(), appUser.getPassword(), authorities);
    }
}

Controller Layer

The controller handles HTTP requests and can enforce method-level security using annotations like @PreAuthorize.

package com.example.app.controller;

import com.example.app.model.AppUser;
import com.example.app.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;

@Controller
public class AuthController {

    @Autowired
    private UserService userService;

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

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

    @GetMapping("/register")
    public String showRegistrationForm(Model model) {
        model.addAttribute("user", new AppUser());
        return "register";
    }

    @PostMapping("/register")
    public String registerUser(@ModelAttribute("user") AppUser user) {
        userService.save(user);
        return "redirect:/login";
    }

    @GetMapping("/admin/dashboard")
    @PreAuthorize("hasAuthority('ROLE_ADMIN')")
    public String adminDashboard() {
        return "admin_dashboard";
    }
}

Service Layer

The service layer handles bussiness logic, including encoding passwords before saving them to the database.

package com.example.app.service;

import com.example.app.mapper.UserMapper;
import com.example.app.model.AppUser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;

@Service
public class UserService {

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private BCryptPasswordEncoder passwordEncoder;

    public AppUser findByUsername(String username) {
        return userMapper.findByUsername(username);
    }

    public void save(AppUser user) {
        user.setPassword(passwordEncoder.encode(user.getPassword()));
        user.setRoles("ROLE_USER");
        userMapper.insert(user);
    }
}

Data Access Layer

Define the MyBatis Mapper interface for database operations.

package com.example.app.mapper;

import com.example.app.model.AppUser;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

@Mapper
public interface UserMapper {
    AppUser findByUsername(@Param("username") String username);
    void insert(@Param("user") AppUser user);
}

Entity Class

Create the entity class that maps to the data base table.

package com.example.app.model;

public class AppUser {
    private Integer id;
    private String username;
    private String password;
    private String roles;

    // Getters and Setters
    public Integer getId() { return id; }
    public void setId(Integer id) { this.id = id; }
    
    public String getUsername() { return username; }
    public void setUsername(String username) { this.username = username; }
    
    public String getPassword() { return password; }
    public void setPassword(String password) { this.password = password; }
    
    public String getRoles() { return roles; }
    public void setRoles(String roles) { this.roles = roles; }
}

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.