Spring Security with MySQL Database | Spring Boot
Experienced Spring Boot Developer with over 3+ years of expertise in developing scalable and high-performance web applications and microservices. Proficient in Java and Spring Boot frameworks, with hands- on experience in RESTful APIs and Microservices architecture. Adept at building secure, database-driven applications and integrating various third- party services. Strong problem-solving skills with a focus on delivering clean, maintainable, and efficient code.
Step 1 → Add dependencies.
<!-- Spring Security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.30</version>
<scope>provided</scope>
</dependency>
<!-- MySQL -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<!-- JPA -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- JWT -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
<!-- Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
Step 2 → Implement “JwtAuthenticationEntryPoint“ class.
This class will act as an Exception Handler.
The method “commence“ on this class just checks is the user trying to access the resource is Authorized to access that resource or not. If not, the it throws an exception with error message.
package com.BRS.BookRecomendation.security;
import java.io.PrintWriter;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
import io.jsonwebtoken.io.IOException;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException, java.io.IOException {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
PrintWriter writer = response.getWriter();
writer.println("Access Denied !! " + authException.getMessage());
}
}
Step 3 → Implement “JwtHelper” class.
It has everything regarding JWT Token like -
Token generation
Validate token
Fetch data from token
Check if token is expired
Contains Secret Key
etc.
package com.BRS.BookRecomendation.security;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
@Component
public class JwtHelper {
//requirement :
public static final long JWT_TOKEN_VALIDITY = 5 * 60 * 60;
// public static final long JWT_TOKEN_VALIDITY = 60;
private String secret = "afafasfafafasfasfasfafacasdasfasxASFACASDFACASDFASFASFDAFASFASDAADSCSDFADCVSGCFVADXCcadwavfsfarvf";
//retrieve username from jwt token
public String getUsernameFromToken(String token) {
return getClaimFromToken(token, Claims::getSubject);
}
//retrieve expiration date from jwt token
public Date getExpirationDateFromToken(String token) {
return getClaimFromToken(token, Claims::getExpiration);
}
public <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver) {
final Claims claims = getAllClaimsFromToken(token);
return claimsResolver.apply(claims);
}
//for retrieveing any information from token we will need the secret key
private Claims getAllClaimsFromToken(String token) {
return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
}
//check if the token has expired
private Boolean isTokenExpired(String token) {
final Date expiration = getExpirationDateFromToken(token);
return expiration.before(new Date());
}
//generate token for user
public String generateToken(UserDetails userDetails) {
Map<String, Object> claims = new HashMap<>();
return doGenerateToken(claims, userDetails.getUsername());
}
//while creating the token -
//1. Define claims of the token, like Issuer, Expiration, Subject, and the ID
//2. Sign the JWT using the HS512 algorithm and secret key.
//3. According to JWS Compact Serialization(https://tools.ietf.org/html/draft-ietf-jose-json-web-signature-41#section-3.1)
// compaction of the JWT to a URL-safe string
private String doGenerateToken(Map<String, Object> claims, String subject) {
return Jwts.builder().setClaims(claims).setSubject(subject).setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + JWT_TOKEN_VALIDITY * 1000))
.signWith(SignatureAlgorithm.HS512, secret).compact();
}
//validate token
public Boolean validateToken(String token, UserDetails userDetails) {
final String username = getUsernameFromToken(token);
return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
}
}
Step 3 → Implement “JwtAuthenticationFilter“ class.
Here we are implementing a filter which -
Separates Token and Username from the Authentication Header.
Gets user details using username.
Then validates the token. If successfully validated then the access is granted.
package com.BRS.BookRecomendation.filters;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import com.BRS.BookRecomendation.security.JwtHelper;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.MalformedJwtException;
import io.jsonwebtoken.io.IOException;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private Logger logger = LoggerFactory.getLogger(OncePerRequestFilter.class);
@Autowired
private JwtHelper jwtHelper;
@Autowired
private UserDetailsService userDetailsService;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException, java.io.IOException {
//Getting header data in requestHeader variable
String requestHeader = request.getHeader("Authorization");
logger.info(" Header : {}", requestHeader);
String username = null;
String token = null;
//Code to get token in token variable by removing "Bearer " from the front.
//Once we have the token we fetch username form it using "JwtHelper" class and put username in usnername variable.
if (requestHeader != null && requestHeader.startsWith("Bearer")) {
token = requestHeader.substring(7);
try {
username = this.jwtHelper.getUsernameFromToken(token);
} catch (IllegalArgumentException e) {
logger.info("Illegal Argument while fetching the username !!");
e.printStackTrace();
} catch (ExpiredJwtException e) {
logger.info("Given jwt token is expired !!");
e.printStackTrace();
} catch (MalformedJwtException e) {
logger.info("Some changed has done in token !! Invalid Token");
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
} else {
logger.info("Invalid Header Value !! ");
}
//Once we have username, we now validate the user
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
//Fetching user detail from username
UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
//Asking JwtHelper to validate token
Boolean validateToken = this.jwtHelper.validateToken(token, userDetails);
if (validateToken) {
//set the authentication
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
} else {
logger.info("Validation fails !!");
}
}
filterChain.doFilter(request, response);
}
}
Step 4 → Implementing “SecurityConfig“
In SecurityConfig we define -
Cors
Request Matchers (Defining which resource/urls can be accessed by which Role)
Exception Handling entry point Class
etc
package com.BRS.BookRecomendation.config;
import com.BRS.BookRecomendation.filters.JwtAuthenticationFilter;
import com.BRS.BookRecomendation.security.JwtAuthenticationEntryPoint;
import com.BRS.BookRecomendation.service.UserInfoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class SecurityConfig {
@Autowired
private JwtAuthenticationFilter authFilter;
@Autowired
private JwtAuthenticationEntryPoint point;
@Bean
public UserDetailsService userDetailsService() {
return new UserInfoService(); // Ensure UserInfoService implements UserDetailsService
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable()) // Disable CSRF for stateless APIs
.authorizeHttpRequests(auth -> auth
.requestMatchers("/auth/login", "/auth/signup").permitAll()
.requestMatchers("/book/hello", "/book/user/**").hasAuthority("ROLE_USER")
.requestMatchers("/book/admin/**").hasAuthority("ROLE_ADMIN")
.anyRequest().authenticated() // Protect all other endpoints
)
.sessionManagement(sess -> sess
.sessionCreationPolicy(SessionCreationPolicy.STATELESS) // No sessions
)
.exceptionHandling(ex -> ex.authenticationEntryPoint(point))
.authenticationProvider(authenticationProvider()) // Custom authentication provider
.addFilterBefore(authFilter, UsernamePasswordAuthenticationFilter.class); // Add JWT filter
return http.build();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(); // Password encoding
}
@Bean
public AuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
authenticationProvider.setUserDetailsService(userDetailsService());
authenticationProvider.setPasswordEncoder(passwordEncoder());
return authenticationProvider;
}
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
return config.getAuthenticationManager();
}
}
Step 5 → Implement models “JwtRequest“ and “JwtResponse” Java classes.
package com.BRS.BookRecomendation.models;
import lombok.*;
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@Builder
@ToString
public class JwtRequest {
private String email;
private String password;
}
package com.BRS.BookRecomendation.models;
import lombok.*;
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@Builder
@ToString
public class JwtResponse {
private String jwtToken;
private String username;
}
Step 6 → Implement “AuthController” and “BookController“.
AuthController : It does -
Get Request data in JwtRequest, then passes email and password to “doAuthenticate“ method, for user details to be authenticated.
“doAuthenticate“ method uses “AuthenticationManager“ to Authenticate user details.
Once user is authenticated, it generates JWT token for the user using JwtHelper class. Then it returns JWT Token and username as response.
Also has a “signup“ method which adds new users to database.
package com.BRS.BookRecomendation.controller;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.BRS.BookRecomendation.DTO.Roles;
import com.BRS.BookRecomendation.DTO.SignUpData;
import com.BRS.BookRecomendation.Entities.UserInfo;
import com.BRS.BookRecomendation.models.JwtRequest;
import com.BRS.BookRecomendation.models.JwtResponse;
import com.BRS.BookRecomendation.security.JwtHelper;
import com.BRS.BookRecomendation.service.UserInfoService;
@RestController
@CrossOrigin
@RequestMapping("/auth")
public class AuthController {
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private AuthenticationManager manager;
@Autowired
private JwtHelper helper;
@Autowired
private UserInfoService userInfoService;
private Logger logger = LoggerFactory.getLogger(AuthController.class);
@PostMapping("/signup")
public ResponseEntity<String> addNewUser(@RequestBody SignUpData signUpData) {
if (userInfoService.existsByUsername(signUpData.getEmail())) {
return ResponseEntity.badRequest().body("Error: Email is already in use!");
}
UserInfo userInfo = new UserInfo();
userInfo.setFullName(signUpData.getFullName());
userInfo.setUsername(signUpData.getEmail());
userInfo.setPassword(signUpData.getPassword());
userInfo.setRoles(Roles.ROLE_USER.toString());
userInfoService.addUser(userInfo);
return ResponseEntity.ok("User registered successfully!");
}
@PostMapping("/login")
public ResponseEntity<JwtResponse> login(@RequestBody JwtRequest request) {
//The frontend will ask for Email and Password for login, therefore we all make use on "email" variable instead of "username" in "JwtRequest" class.
this.doAuthenticate(request.getEmail(), request.getPassword());
UserDetails userDetails = userDetailsService.loadUserByUsername(request.getEmail());
String token = this.helper.generateToken(userDetails);
JwtResponse response = JwtResponse.builder()
.jwtToken(token)
.username(userDetails.getUsername()).build();
return new ResponseEntity<>(response, HttpStatus.OK);
}
private void doAuthenticate(String email, String password) {
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(email, password);
try {
manager.authenticate(authentication);
} catch (BadCredentialsException e) {
throw new BadCredentialsException(" Invalid Username or Password !!");
}
}
@ExceptionHandler(BadCredentialsException.class)
public String exceptionHandler() {
return "Credentials Invalid !!";
}
}
BookController : It has “hello” method which is Authorized only to be accessed by user which has role “ROLE_USER“ (We can have similar controller methods which can only be accessed by users with specific roles only).
package com.BRS.BookRecomendation.controller;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.CrossOrigin;
@RestController
@CrossOrigin
@RequestMapping("/book")
public class BookController {
private Logger logger = LoggerFactory.getLogger(AuthController.class);
@GetMapping("/hello")
@PreAuthorize("hasAuthority('ROLE_USER')")
public String sayHello() {
return "Hello, World!";
}
}
Implement “UserInfo“ Entity -
package com.BRS.BookRecomendation.Entities;
import lombok.*;
import jakarta.persistence.*;
@Entity
@Table(name = "users")
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Getter
@Setter
public class UserInfo {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;
private String fullName;
private String password;
private String roles;
}
Implement “UserInfoRepository“ -
package com.BRS.BookRecomendation.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import com.BRS.BookRecomendation.Entities.UserInfo;
import java.util.Optional;
@Repository
public interface UserInfoRepository extends JpaRepository<UserInfo, Long> {
Optional<UserInfo> findByUsername(String username);
}
Implement “UserInfoDetails“ -
package com.BRS.BookRecomendation.DTO;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import com.BRS.BookRecomendation.Entities.UserInfo;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
public class UserInfoDetails implements UserDetails {
private String username; // Changed from 'name' to 'username' for clarity
private String password;
private List<GrantedAuthority> authorities;
public UserInfoDetails(UserInfo userInfo) {
this.username = userInfo.getUsername(); // Assuming 'name' is used as 'username'
this.password = userInfo.getPassword();
this.authorities = List.of(userInfo.getRoles().split(","))
.stream()
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return authorities;
}
@Override
public String getPassword() {
return password;
}
@Override
public String getUsername() {
return username;
}
@Override
public boolean isAccountNonExpired() {
return true; // Implement your logic if you need this
}
@Override
public boolean isAccountNonLocked() {
return true; // Implement your logic if you need this
}
@Override
public boolean isCredentialsNonExpired() {
return true; // Implement your logic if you need this
}
@Override
public boolean isEnabled() {
return true; // Implement your logic if you need this
}
}
Implement “UserInfoService “ -
package com.BRS.BookRecomendation.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import com.BRS.BookRecomendation.DTO.UserInfoDetails;
import com.BRS.BookRecomendation.Entities.UserInfo;
import com.BRS.BookRecomendation.repository.UserInfoRepository;
import java.util.Optional;
@Service
public class UserInfoService implements UserDetailsService {
@Autowired
private UserInfoRepository repository;
@Autowired
private PasswordEncoder encoder;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Optional<UserInfo> userDetail = repository.findByUsername(username); // Assuming 'email' is used as username
// Converting UserInfo to UserDetails
return userDetail.map(UserInfoDetails::new)
.orElseThrow(() -> new UsernameNotFoundException("User not found: " + username));
}
public String addUser(UserInfo userInfo) {
// Encode password before saving the user
userInfo.setPassword(encoder.encode(userInfo.getPassword()));
repository.save(userInfo);
return "User Added Successfully";
}
}
Properties in Application.properties file -
spring.application.name=BookRecomendation
# MySQL Database Configuration
spring.datasource.url=jdbc:mysql://localhost:3306/book_recommendation?createDatabaseIfNotExist=true&useSSL=false&allowPublicKeyRetrieval=true
spring.datasource.username=hbstudent
spring.datasource.password=Pratt@12345
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# JPA/Hibernate Configuration
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL8Dialect
server.port=8010
spring.main.allow-circular-references=true
auth -

Access url -

ddsd

database -
