In this tutorial, we’re gonna build a Spring Boot JWT Authentication with Spring Security & PostgreSQL Application that supports Token based Authentication & Role based Authorization. You’ll know:
- Appropriate Flow for User Signup & User Login with JWT Authentication
- Spring Boot Application Architecture with Spring Security
- How to configure Spring Security to work with JWT
- How to define Data Models and association for Authentication and Authorization
- Way to use Spring Data JPA to interact with PostgreSQL Database
Lots of interesting things ahead, let’s explore together.
Related Posts:
– Spring Boot Refresh Token with JWT example
– Spring Boot, PostgreSQL: CRUD example with Spring JPA
– Spring Boot + GraphQL + PostgreSQL example
– Spring Boot Unit Test for JPA Repository
– Spring Boot Unit Test for Rest Controller
– @RestControllerAdvice example in Spring Boot
– Documentation: Spring Boot Swagger 3 example
– Caching: Spring Boot Redis Cache example
– Spring Boot custom Validation example
– Dockerize: Docker Compose: Spring Boot and Postgres example
Fullstack:
- Spring Boot + Vuejs: JWT Authentication Example
- Spring Boot + Angular 8: JWT Authentication Example
- Spring Boot + Angular 10: JWT Authentication Example
- Spring Boot + Angular 11: JWT Authentication example
- Spring Boot + Angular 12: JWT Authentication example
- Spring Boot + Angular 13: JWT Authentication example
- Spring Boot + Angular 14: JWT Authentication example
- Spring Boot + Angular 15: JWT Authentication example
- Spring Boot + Angular 16: JWT Authentication example
- Spring Boot + React.js: JWT Authentication example
Contents
- Overview
- Spring Boot Signup & Login with JWT Authentication Flow
- Spring Boot Server Architecture with Spring Security
- Technology
- Project Structure
- Setup new Spring Boot project
- Configure Spring Datasource, JPA, App properties
- Create the models
- Implement Repositories
- Configure Spring Security
- Implement UserDetails & UserDetailsService
- Filter the Requests
- Create JWT Utility class
- Handle Authentication Exception
- Define payloads for Spring RestController
- Create Spring RestAPIs Controllers
- Run & Test
- Conclusion
- Source Code
- Further Reading
Overview of Spring Boot JWT Authentication with PostgreSQL example
We will build a Spring Boot application in that:
- User can signup new account, or login with username & password.
- By User’s role (admin, moderator, user), we authorize the User to access resources
These are APIs that we need to provide:
| Methods | Urls | Actions |
|---|---|---|
| POST | /api/auth/signup | signup new account |
| POST | /api/auth/signin | login an account |
| GET | /api/test/all | retrieve public content |
| GET | /api/test/user | access User’s content |
| GET | /api/test/mod | access Moderator’s content |
| GET | /api/test/admin | access Admin’s content |
The database we will be PostgreSQL. We interact with the database by using Spring configuring project dependency & datasource.
Spring Boot Signup & Login with JWT Authentication Flow
The diagram shows flow of how we implement User Registration, User Login and Authorization process.

A legal JWT must be added to HTTP Authorization Header if Client accesses protected resources.
If you want to store JWT in HttpOnly Cookie, kindly visit:
Spring Boot Login example with JWT HttpOnly Cookie
You will need to implement Refresh Token:

More details at: Spring Boot Refresh Token with JWT example
Spring Boot Server Architecture with Spring Security
You can have an overview of our Spring Boot Server with the diagram below:

Now I will explain it briefly.
Spring Security
– WebSecurityConfig is the crux of our security implementation. It configures cors, csrf, session management, rules for protected resources. We can also extend and customize the default configuration that contains the elements below.
(WebSecurityConfigurerAdapter is deprecated from Spring 2.7.0, you can check the source code for update. More details at:
WebSecurityConfigurerAdapter Deprecated in Spring Boot)
– UserDetailsService interface has a method to load User by username and returns a UserDetails object that Spring Security can use for authentication and validation.
– UserDetails contains necessary information (such as: username, password, authorities) to build an Authentication object.
– UsernamePasswordAuthenticationToken gets {username, password} from login Request, AuthenticationManager will use it to authenticate a login account.
– AuthenticationManager has a DaoAuthenticationProvider (with help of UserDetailsService & PasswordEncoder) to validate UsernamePasswordAuthenticationToken object. If successful, AuthenticationManager returns a fully populated Authentication object (including granted authorities).
– OncePerRequestFilter makes a single execution for each request to our API. It provides a doFilterInternal() method that we will implement parsing & validating JWT, loading User details (using UserDetailsService), checking Authorizaion (using UsernamePasswordAuthenticationToken).
– AuthenticationEntryPoint will catch authentication error.
Repository contains UserRepository & RoleRepository to work with Database, will be imported into Controller.
Controller receives and handles request after it was filtered by OncePerRequestFilter.
– AuthController handles signup/login requests
– TestController has accessing protected resource methods with role based validations.
Understand the architecture deeply and grasp the overview more easier:
Spring Boot Architecture for JWT with Spring Security
Technology
- Java 17 / 11 / 8
- Spring Boot 3 / 2 (with Spring Security, Spring Web, Spring Data JPA)
- jjwt-api 0.11.5
- PostgreSQL
- Maven
Project Structure
This is folders & files structure for our Spring Boot Spring Security & PostgreSQL application:

security: we configure Spring Security & implement Security Objects here.
WebSecurityConfigUserDetailsServiceImplimplementsUserDetailsServiceUserDetailsImplimplementsUserDetailsAuthEntryPointJwtimplementsAuthenticationEntryPointAuthTokenFilterextendsOncePerRequestFilterJwtUtilsprovides methods for generating, parsing, validating JWT
(WebSecurityConfigurerAdapter is deprecated from Spring 2.7.0, you can check the source code for update. More details at:
WebSecurityConfigurerAdapter Deprecated in Spring Boot)
controllers handle signup/login requests & authorized requests.
AuthController: @PostMapping(‘/signin’), @PostMapping(‘/signup’)TestController: @GetMapping(‘/api/test/all’), @GetMapping(‘/api/test/[role]’)
repository has intefaces that extend Spring Data JPA JpaRepository to interact with PostgreSQL Database.
UserRepositoryextendsJpaRepository<User, Long>RoleRepositoryextendsJpaRepository<Role, Long>
models defines two main models for Authentication (User) & Authorization (Role). They have many-to-many relationship.
User: id, username, email, password, rolesRole: id, name
payload defines classes for Request and Response objects
We also have application.properties for configuring Spring Datasource, Spring Data JPA and App properties (such as JWT Secret string or Token expiration time).
Setup new Spring Boot project
Use Spring web tool or your development tool (Spring Tool Suite, Eclipse, Intellij) to create a Spring Boot project.
Then open pom.xml and add these dependencies:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</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>io.jsonwebtoken</groupId> <artifactId>jjwt-api</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> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-jackson</artifactId> <version>0.11.5</version> <scope>runtime</scope> </dependency> We also need to add one more dependency for PostgreSQL:
<dependency> <groupId>org.postgresql</groupId> <artifactId>postgresql</artifactId> <scope>runtime</scope> </dependency> Configure Spring Datasource, JPA, App properties
Under src/main/resources folder, open application.properties, add some new lines.
For PostgreSQL
spring.datasource.url=jdbc:mysql://localhost:3306/testdb_spring?useSSL=false spring.datasource.username=root spring.datasource.password=123456 spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQLDialect spring.jpa.hibernate.ddl-auto=update # App Properties bezkoder.app.jwtSecret= ======================BezKoder=Spring=========================== bezkoder.app.jwtExpirationMs=86400000 spring.datasource.username&spring.datasource.passwordproperties are the same as your database installation.- Spring Boot uses Hibernate for JPA implementation, we configure
PostgreSQLDialectfor PostgreSQL spring.jpa.hibernate.ddl-autois used for database initialization. We set the value toupdatevalue so that a table will be created in the database automatically corresponding to defined data model. Any change to the model will also trigger an update to the table. For production, this property should bevalidate.
Create the models
We’re gonna have 3 tables in database: users, roles and user_roles for many-to-many relationship.
Let’s define these models.
In models package, create 3 files:
ERole enum in ERole.java.
In this example, we have 3 roles corresponding to 3 enum.
package com.bezkoder.spring.security.postgresql.models; public enum ERole { ROLE_USER, ROLE_MODERATOR, ROLE_ADMIN } Role model in Role.java
package com.bezkoder.spring.security.postgresql.models; import jakarta.persistence.*; @Entity @Table(name = "roles") public class Role { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; @Enumerated(EnumType.STRING) @Column(length = 20) private ERole name; public Role() { } public Role(ERole name) { this.name = name; } // getters and setters } User model in User.java.
It has 5 fields: id, username, email, password, roles.
package com.bezkoder.spring.security.postgresql.models; import java.util.HashSet; import java.util.Set; import jakarta.persistence.*; import jakarta.validation.constraints.Email; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.Size; @Entity @Table( name = "users", uniqueConstraints = { @UniqueConstraint(columnNames = "username"), @UniqueConstraint(columnNames = "email") }) public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @NotBlank @Size(max = 20) private String username; @NotBlank @Size(max = 50) @Email private String email; @NotBlank @Size(max = 120) private String password; @ManyToMany(fetch = FetchType.LAZY) @JoinTable(name = "user_roles", joinColumns = @JoinColumn(name = "user_id"), inverseJoinColumns = @JoinColumn(name = "role_id")) private Set<Role> roles = new HashSet<>(); public User() { } public User(String username, String email, String password) { this.username = username; this.email = email; this.password = password; } // getters and setters } Implement Repositories
Now, each model above needs a repository for persisting and accessing data. In repository package, let’s create 2 repositories.
UserRepository
There are 3 necessary methods that JpaRepository supports.
package com.bezkoder.spring.security.postgresql.repository; import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; import com.bezkoder.spring.security.postgresql.models.User; @Repository public interface UserRepository extends JpaRepository<User, Long> { Optional<User> findByUsername(String username); Boolean existsByUsername(String username); Boolean existsByEmail(String email); } RoleRepository
This repository also extends JpaRepository and provides a finder method.
package com.bezkoder.spring.security.postgresql.repository; import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; import com.bezkoder.spring.security.postgresql.models.ERole; import com.bezkoder.spring.security.postgresql.models.Role; @Repository public interface RoleRepository extends JpaRepository<Role, Long> { Optional<Role> findByName(ERole name); } Configure Spring Security
Without WebSecurityConfigurerAdapter
WebSecurityConfigurerAdapter is deprecated from Spring 2.7.0. More details at:
WebSecurityConfigurerAdapter Deprecated in Spring Boot.
In security package, create WebSecurityConfig class.
WebSecurityConfig.java
package com.bezkoder.spring.security.postgresql.security; 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.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.http.SessionCreationPolicy; 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; import com.bezkoder.spring.security.postgresql.security.jwt.AuthEntryPointJwt; import com.bezkoder.spring.security.postgresql.security.jwt.AuthTokenFilter; import com.bezkoder.spring.security.postgresql.security.services.UserDetailsServiceImpl; @Configuration @EnableMethodSecurity // (securedEnabled = true, // jsr250Enabled = true, // prePostEnabled = true) // by default public class WebSecurityConfig { // extends WebSecurityConfigurerAdapter { @Autowired UserDetailsServiceImpl userDetailsService; @Autowired private AuthEntryPointJwt unauthorizedHandler; @Bean public AuthTokenFilter authenticationJwtTokenFilter() { return new AuthTokenFilter(); } @Bean public DaoAuthenticationProvider authenticationProvider() { DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider(); authProvider.setUserDetailsService(userDetailsService); authProvider.setPasswordEncoder(passwordEncoder()); return authProvider; } @Bean public AuthenticationManager authenticationManager(AuthenticationConfiguration authConfig) throws Exception { return authConfig.getAuthenticationManager(); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http.csrf(csrf -> csrf.disable()) .exceptionHandling(exception -> exception.authenticationEntryPoint(unauthorizedHandler)) .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .authorizeHttpRequests(auth -> auth.requestMatchers("/api/auth/**").permitAll() .requestMatchers("/api/test/**").permitAll() .anyRequest().authenticated() ); http.authenticationProvider(authenticationProvider()); http.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class); return http.build(); } } With WebSecurityConfigurerAdapter
In security package, create WebSecurityConfig class that extends WebSecurityConfigurerAdapter.
WebSecurityConfig.java
package com.bezkoder.spring.security.postgresql.security; 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.config.annotation.authentication.builders.AuthenticationManagerBuilder; 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.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import com.bezkoder.spring.security.postgresql.security.jwt.AuthEntryPointJwt; import com.bezkoder.spring.security.postgresql.security.jwt.AuthTokenFilter; import com.bezkoder.spring.security.postgresql.security.services.UserDetailsServiceImpl; @Configuration @EnableWebSecurity @EnableGlobalMethodSecurity( // securedEnabled = true, // jsr250Enabled = true, prePostEnabled = true) public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired UserDetailsServiceImpl userDetailsService; @Autowired private AuthEntryPointJwt unauthorizedHandler; @Bean public AuthTokenFilter authenticationJwtTokenFilter() { return new AuthTokenFilter(); } @Override public void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception { authenticationManagerBuilder.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder()); } @Bean @Override public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Override protected void configure(HttpSecurity http) throws Exception { http.cors().and().csrf().disable() .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and() .authorizeRequests().antMatchers("/api/auth/**").permitAll() .antMatchers("/api/test/**").permitAll() .anyRequest().authenticated(); http.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class); } } Let me explain the code above.
– @EnableWebSecurity allows Spring to find and automatically apply the class to the global Web Security.
*For Spring Boot 2: @EnableGlobalMethodSecurity provides AOP security on methods. It enables @PreAuthorize, @PostAuthorize, it also supports JSR-250. You can find more parameters in configuration in Method Security Expressions.
– @EnableGlobalMethodSecurity is deprecated in Spring Boot 3. You can use @EnableMethodSecurity instead. For more details, please visit Method Security.
– We override the configure(HttpSecurity http) method from WebSecurityConfigurerAdapter interface. It tells Spring Security how we configure CORS and CSRF, when we want to require all users to be authenticated or not, which filter (AuthTokenFilter) and when we want it to work (filter before UsernamePasswordAuthenticationFilter), which Exception Handler is chosen (AuthEntryPointJwt).
– Spring Security will load User details to perform authentication & authorization. So it has UserDetailsService interface that we need to implement.
– The implementation of UserDetailsService will be used for configuring DaoAuthenticationProvider by AuthenticationManagerBuilder.userDetailsService() method.
– We also need a PasswordEncoder for the DaoAuthenticationProvider. If we don’t specify, it will use plain text.
Implement UserDetails & UserDetailsService
If the authentication process is successful, we can get User’s information such as username, password, authorities from an Authentication object.
Authentication authentication = authenticationManager.authenticate( new UsernamePasswordAuthenticationToken(username, password) ); UserDetails userDetails = (UserDetails) authentication.getPrincipal(); // userDetails.getUsername() // userDetails.getPassword() // userDetails.getAuthorities() If we want to get more data (id, email…), we can create an implementation of this UserDetails interface.
security/services/UserDetailsImpl.java
package com.bezkoder.spring.security.postgresql.security.services; import java.util.Collection; import java.util.List; import java.util.Objects; import java.util.stream.Collectors; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import com.bezkoder.spring.security.postgresql.models.User; import com.fasterxml.jackson.annotation.JsonIgnore; public class UserDetailsImpl implements UserDetails { private static final long serialVersionUID = 1L; private Long id; private String username; private String email; @JsonIgnore private String password; private Collection<? extends GrantedAuthority> authorities; public UserDetailsImpl(Long id, String username, String email, String password, Collection<? extends GrantedAuthority> authorities) { this.id = id; this.username = username; this.email = email; this.password = password; this.authorities = authorities; } public static UserDetailsImpl build(User user) { List<GrantedAuthority> authorities = user.getRoles().stream() .map(role -> new SimpleGrantedAuthority(role.getName().name())) .collect(Collectors.toList()); return new UserDetailsImpl(user.getId(), user.getUsername(), user.getEmail(), user.getPassword(), authorities); } @Override public Collection<? extends GrantedAuthority> getAuthorities() { return authorities; } public Long getId() { return id; } public String getEmail() { return email; } @Override public String getPassword() { return password; } @Override public String getUsername() { return username; } @Override public boolean isAccountNonExpired() { return true; } @Override public boolean isAccountNonLocked() { return true; } @Override public boolean isCredentialsNonExpired() { return true; } @Override public boolean isEnabled() { return true; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; UserDetailsImpl user = (UserDetailsImpl) o; return Objects.equals(id, user.id); } } Look at the code above, you can notice that we convert Set<Role> into List<GrantedAuthority>. It is important to work with Spring Security and Authentication object later.
As I have said before, we need UserDetailsService for getting UserDetails object. You can look at UserDetailsService interface that has only one method:
public interface UserDetailsService { UserDetails loadUserByUsername(String username) throws UsernameNotFoundException; } So we implement it and override loadUserByUsername() method.
security/services/UserDetailsServiceImpl.java
package com.bezkoder.spring.security.postgresql.security.services; 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.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import com.bezkoder.spring.security.postgresql.models.User; import com.bezkoder.spring.security.postgresql.repository.UserRepository; @Service public class UserDetailsServiceImpl implements UserDetailsService { @Autowired UserRepository userRepository; @Override @Transactional public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { User user = userRepository.findByUsername(username) .orElseThrow(() -> new UsernameNotFoundException("User Not Found with username: " + username)); return UserDetailsImpl.build(user); } } In the code above, we get full custom User object using UserRepository, then we build a UserDetails object using static build() method.
Filter the Requests
Let’s define a filter that executes once per request. So we create AuthTokenFilter class that extends OncePerRequestFilter and override doFilterInternal() method.
security/jwt/AuthTokenFilter.java
package com.bezkoder.spring.security.postgresql.security.jwt; import java.io.IOException; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; 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.web.authentication.WebAuthenticationDetailsSource; import org.springframework.util.StringUtils; import org.springframework.web.filter.OncePerRequestFilter; import com.bezkoder.spring.security.postgresql.security.services.UserDetailsServiceImpl; public class AuthTokenFilter extends OncePerRequestFilter { @Autowired private JwtUtils jwtUtils; @Autowired private UserDetailsServiceImpl userDetailsService; private static final Logger logger = LoggerFactory.getLogger(AuthTokenFilter.class); @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { try { String jwt = parseJwt(request); if (jwt != null && jwtUtils.validateJwtToken(jwt)) { String username = jwtUtils.getUserNameFromJwtToken(jwt); UserDetails userDetails = userDetailsService.loadUserByUsername(username); UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); SecurityContextHolder.getContext().setAuthentication(authentication); } } catch (Exception e) { logger.error("Cannot set user authentication: {}", e); } filterChain.doFilter(request, response); } private String parseJwt(HttpServletRequest request) { String headerAuth = request.getHeader("Authorization"); if (StringUtils.hasText(headerAuth) && headerAuth.startsWith("Bearer ")) { return headerAuth.substring(7, headerAuth.length()); } return null; } } What we do inside doFilterInternal():
– get JWT from the Authorization header (by removing Bearer prefix)
– if the request has JWT, validate it, parse username from it
– from username, get UserDetails to create an Authentication object
– set the current UserDetails in SecurityContext using setAuthentication(authentication) method.
After this, everytime you want to get UserDetails, just use SecurityContext like this:
UserDetails userDetails = (UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); // userDetails.getUsername() // userDetails.getPassword() // userDetails.getAuthorities() Create JWT Utility class
This class has 3 funtions:
- generate a
JWTfrom username, date, expiration, secret - get username from
JWT - validate a
JWT
security/jwt/JwtUtils.java
package com.bezkoder.spring.security.postgresql.security.jwt; import java.security.Key; import java.util.Date; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.security.core.Authentication; import org.springframework.stereotype.Component; import com.bezkoder.spring.security.postgresql.security.services.UserDetailsImpl; import io.jsonwebtoken.*; import io.jsonwebtoken.io.Decoders; import io.jsonwebtoken.security.Keys; @Component public class JwtUtils { private static final Logger logger = LoggerFactory.getLogger(JwtUtils.class); @Value("${bezkoder.app.jwtSecret}") private String jwtSecret; @Value("${bezkoder.app.jwtExpirationMs}") private int jwtExpirationMs; public String generateJwtToken(Authentication authentication) { UserDetailsImpl userPrincipal = (UserDetailsImpl) authentication.getPrincipal(); return Jwts.builder() .setSubject((userPrincipal.getUsername())) .setIssuedAt(new Date()) .setExpiration(new Date((new Date()).getTime() + jwtExpirationMs)) .signWith(key(), SignatureAlgorithm.HS256) .compact(); } private Key key() { return Keys.hmacShaKeyFor(Decoders.BASE64.decode(jwtSecret)); } public String getUserNameFromJwtToken(String token) { return Jwts.parserBuilder().setSigningKey(key()).build() .parseClaimsJws(token).getBody().getSubject(); } public boolean validateJwtToken(String authToken) { try { Jwts.parserBuilder().setSigningKey(key()).build().parse(authToken); return true; } catch (MalformedJwtException e) { logger.error("Invalid JWT token: {}", e.getMessage()); } catch (ExpiredJwtException e) { logger.error("JWT token is expired: {}", e.getMessage()); } catch (UnsupportedJwtException e) { logger.error("JWT token is unsupported: {}", e.getMessage()); } catch (IllegalArgumentException e) { logger.error("JWT claims string is empty: {}", e.getMessage()); } return false; } } Remember that we’ve added bezkoder.app.jwtSecret and bezkoder.app.jwtExpirationMs properties in application.properties file, and jwtSecret has 64 characters.
Handle Authentication Exception
Now we create AuthEntryPointJwt class that implements AuthenticationEntryPoint interface. Then we override the commence() method. This method will be triggerd anytime unauthenticated User requests a secured HTTP resource and an AuthenticationException is thrown.
security/jwt/AuthEntryPointJwt.java
package com.bezkoder.spring.security.postgresql.security.jwt; import java.io.IOException; import java.util.HashMap; import java.util.Map; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.MediaType; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.stereotype.Component; import com.fasterxml.jackson.databind.ObjectMapper; @Component public class AuthEntryPointJwt implements AuthenticationEntryPoint { private static final Logger logger = LoggerFactory.getLogger(AuthEntryPointJwt.class); @Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException { logger.error("Unauthorized error: {}", authException.getMessage()); response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Error: Unauthorized"); } } HttpServletResponse.SC_UNAUTHORIZED is the 401 Status code. It indicates that the request requires HTTP authentication.
If you want to customize the response data, just use an ObjectMapper like following code:
@Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException { logger.error("Unauthorized error: {}", authException.getMessage()); response.setContentType(MediaType.APPLICATION_JSON_VALUE); response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); final Map<String, Object> body = new HashMap<>(); body.put("status", HttpServletResponse.SC_UNAUTHORIZED); body.put("error", "Unauthorized"); body.put("message", authException.getMessage()); body.put("path", request.getServletPath()); final ObjectMapper mapper = new ObjectMapper(); mapper.writeValue(response.getOutputStream(), body); } We’ve already built all things for Spring Security. The next sections of this tutorial will show you how to implement Controllers for our RestAPIs.
Define payloads for Spring RestController
Let me summarize the payloads for our RestAPIs:
– Requests:
- LoginRequest: { username, password }
- SignupRequest: { username, email, password }
– Responses:
- JwtResponse: { token, type, id, username, email, roles }
- MessageResponse: { message }
To keep the tutorial not so long, I don’t show these POJOs here.
You can find details for payload classes in source code of the project on Github.
Create Spring RestAPIs Controllers
Controller for Authentication
This controller provides APIs for register and login actions.
– /api/auth/signup
- check existing
username/email - create new
User(withROLE_USERif not specifying role) - save
Userto database usingUserRepository
– /api/auth/signin
- authenticate { username, pasword }
- update
SecurityContextusingAuthenticationobject - generate
JWT - get
UserDetailsfromAuthenticationobject - response contains
JWTandUserDetailsdata
controllers/AuthController.java
package com.bezkoder.spring.security.postgresql.controllers; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.stream.Collectors; import jakarta.validation.Valid; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.web.bind.annotation.CrossOrigin; 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.bezkoder.spring.security.postgresql.models.ERole; import com.bezkoder.spring.security.postgresql.models.Role; import com.bezkoder.spring.security.postgresql.models.User; import com.bezkoder.spring.security.postgresql.payload.request.LoginRequest; import com.bezkoder.spring.security.postgresql.payload.request.SignupRequest; import com.bezkoder.spring.security.postgresql.payload.response.JwtResponse; import com.bezkoder.spring.security.postgresql.payload.response.MessageResponse; import com.bezkoder.spring.security.postgresql.repository.RoleRepository; import com.bezkoder.spring.security.postgresql.repository.UserRepository; import com.bezkoder.spring.security.postgresql.security.jwt.JwtUtils; import com.bezkoder.spring.security.postgresql.security.services.UserDetailsImpl; @CrossOrigin(origins = "*", maxAge = 3600) @RestController @RequestMapping("/api/auth") public class AuthController { @Autowired AuthenticationManager authenticationManager; @Autowired UserRepository userRepository; @Autowired RoleRepository roleRepository; @Autowired PasswordEncoder encoder; @Autowired JwtUtils jwtUtils; @PostMapping("/signin") public ResponseEntity<?> authenticateUser(@Valid @RequestBody LoginRequest loginRequest) { Authentication authentication = authenticationManager .authenticate(new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getPassword())); SecurityContextHolder.getContext().setAuthentication(authentication); String jwt = jwtUtils.generateJwtToken(authentication); UserDetailsImpl userDetails = (UserDetailsImpl) authentication.getPrincipal(); List<String> roles = userDetails.getAuthorities().stream().map(item -> item.getAuthority()) .collect(Collectors.toList()); return ResponseEntity .ok(new JwtResponse(jwt, userDetails.getId(), userDetails.getUsername(), userDetails.getEmail(), roles)); } @PostMapping("/signup") public ResponseEntity<?> registerUser(@Valid @RequestBody SignupRequest signUpRequest) { if (userRepository.existsByUsername(signUpRequest.getUsername())) { return ResponseEntity.badRequest().body(new MessageResponse("Error: Username is already taken!")); } if (userRepository.existsByEmail(signUpRequest.getEmail())) { return ResponseEntity.badRequest().body(new MessageResponse("Error: Email is already in use!")); } // Create new user's account User user = new User(signUpRequest.getUsername(), signUpRequest.getEmail(), encoder.encode(signUpRequest.getPassword())); Set<String> strRoles = signUpRequest.getRole(); Set<Role> roles = new HashSet<>(); if (strRoles == null) { Role userRole = roleRepository.findByName(ERole.ROLE_USER) .orElseThrow(() -> new RuntimeException("Error: Role is not found.")); roles.add(userRole); } else { strRoles.forEach(role -> { switch (role) { case "admin": Role adminRole = roleRepository.findByName(ERole.ROLE_ADMIN) .orElseThrow(() -> new RuntimeException("Error: Role is not found.")); roles.add(adminRole); break; case "mod": Role modRole = roleRepository.findByName(ERole.ROLE_MODERATOR) .orElseThrow(() -> new RuntimeException("Error: Role is not found.")); roles.add(modRole); break; default: Role userRole = roleRepository.findByName(ERole.ROLE_USER) .orElseThrow(() -> new RuntimeException("Error: Role is not found.")); roles.add(userRole); } }); } user.setRoles(roles); userRepository.save(user); return ResponseEntity.ok(new MessageResponse("User registered successfully!")); } } Controller for testing Authorization
There are 4 APIs:
– /api/test/all for public access
– /api/test/user for users has ROLE_USER or ROLE_MODERATOR or ROLE_ADMIN
– /api/test/mod for users has ROLE_MODERATOR
– /api/test/admin for users has ROLE_ADMIN
Do you remember that we used @EnableGlobalMethodSecurity(prePostEnabled = true) (or @EnableMethodSecurity for Spring Boot 3) in WebSecurityConfig class?
@Configuration // @EnableWebSecurity // @EnableGlobalMethodSecurity(prePostEnabled = true) @EnableMethodSecurity // Spring Boot 3 public class WebSecurityConfig extends WebSecurityConfigurerAdapter { ... } Now we can secure methods in our Apis with @PreAuthorize annotation easily.
controllers/TestController.java
package com.bezkoder.spring.security.postgresql.controllers; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.CrossOrigin; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @CrossOrigin(origins = "*", maxAge = 3600) @RestController @RequestMapping("/api/test") public class TestController { @GetMapping("/all") public String allAccess() { return "Public Content."; } @GetMapping("/user") @PreAuthorize("hasRole('USER') or hasRole('MODERATOR') or hasRole('ADMIN')") public String userAccess() { return "User Content."; } @GetMapping("/mod") @PreAuthorize("hasRole('MODERATOR')") public String moderatorAccess() { return "Moderator Board."; } @GetMapping("/admin") @PreAuthorize("hasRole('ADMIN')") public String adminAccess() { return "Admin Board."; } } Run & Test
Run Spring Boot application with command: mvn spring-boot:run
Tables that we define in models package will be automatically generated in Database.
If you check PostgreSQL for example, you can see things like this:
\d users Table "public.users" Column | Type | Modifiers ----------+------------------------+---------------------------------------------------- id | bigint | not null default nextval('users_id_seq'::regclass) email | character varying(50) | password | character varying(120) | username | character varying(20) | Indexes: "users_pkey" PRIMARY KEY, btree (id) "uk6dotkott2kjsp8vw4d0m25fb7" UNIQUE CONSTRAINT, btree (email) "ukr43af9ap4edm43mmtq01oddj6" UNIQUE CONSTRAINT, btree (username) Referenced by: TABLE "user_roles" CONSTRAINT "fkhfh9dx7w3ubf1co1vdev94g3f" FOREIGN KEY (user_id) REFERENCES users(id) \d roles; Table "public.roles" Column | Type | Modifiers --------+-----------------------+---------------------------------------------------- id | integer | not null default nextval('roles_id_seq'::regclass) name | character varying(20) | Indexes: "roles_pkey" PRIMARY KEY, btree (id) Referenced by: TABLE "user_roles" CONSTRAINT "fkh8ciramu9cc9q3qcqiv4ue8a6" FOREIGN KEY (role_id) REFERENCES roles(id) \d user_roles Table "public.user_roles" Column | Type | Modifiers ---------+---------+----------- user_id | bigint | not null role_id | integer | not null Indexes: "user_roles_pkey" PRIMARY KEY, btree (user_id, role_id) Foreign-key constraints: "fkh8ciramu9cc9q3qcqiv4ue8a6" FOREIGN KEY (role_id) REFERENCES roles(id) "fkhfh9dx7w3ubf1co1vdev94g3f" FOREIGN KEY (user_id) REFERENCES users(id) We also need to add some rows into roles table before assigning any role to User.
Run following SQL insert statements:
INSERT INTO roles(name) VALUES('ROLE_USER'); INSERT INTO roles(name) VALUES('ROLE_MODERATOR'); INSERT INTO roles(name) VALUES('ROLE_ADMIN'); Then check the tables:
> SELECT * FROM roles; id | name ----+---------------- 1 | ROLE_USER 2 | ROLE_MODERATOR 3 | ROLE_ADMIN (3 rows) Register some users with /signup API:
- admin with
ROLE_ADMIN - mod with
ROLE_MODERATORandROLE_USER - zkoder with
ROLE_USER

Our tables after signup could look like this.
> SELECT * FROM users; id | email | password | username ----+--------------------+--------------------------------------------------------------+---------- 1 | [email protected] | $2a$10$4K8Vq5mw.nwxl.WRmuYCfevme82c73uGkEcnPbmm/3/YJ3UToie7m | admin 2 | [email protected] | $2a$10$1dCKuQoQqbBNCK.Rb8XQSemwqdHdVAcCTb1kUQLg2key/4VX./TvS | mod 3 | [email protected] | $2a$10$e9Mgd/63paPL0VBj232BH.tQvIgQu0/tBg/rwfyDVMUcQc8djEPle | zkoder (3 rows) > SELECT * FROM roles; id | name ----+---------------- 1 | ROLE_USER 2 | ROLE_MODERATOR 3 | ROLE_ADMIN (3 rows) >SELECT * FROM user_roles; user_id | role_id ---------+--------- 1 | 3 2 | 1 2 | 2 3 | 1 (4 rows) Access public resource: GET /api/test/all

Access protected resource: GET /api/test/user

Login an account: POST /api/auth/signin

Access ROLE_USER resource: GET /api/test/user

Access ROLE_MODERATOR resource: GET /api/test/mod

Access ROLE_ADMIN resource: GET /api/test/admin

Solve Problem: javax.validation cannot be resolved
For Spring Boot 2.3 and later, you can see the compile error:
The import javax.validation cannot be resolved
It is because Validation Starter no longer included in web starters. So you need to add the starter yourself.
– For Maven:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency> – For Gradle:
dependencies { ... implementation 'org.springframework.boot:spring-boot-starter-validation' } Solve Problem with JDK 14
If you run this Spring Boot App with JDK 14 and get following error when trying to authenticate:
FilterChain java.lang.NoClassDefFoundError: javax/xml/bind/DatatypeConverter Just add following dependency to pom.xml:
<dependency> <groupId>jakarta.xml.bind</groupId> <artifactId>jakarta.xml.bind-api</artifactId> <version>2.3.2</version> </dependency> Everything’s gonna work fine.
Conclusion
Congratulation!
Today we’ve learned so many interesting things about Spring Boot PostgreSQL Authentication with Spring Security and JWT – Token based Authentication. Despite we wrote a lot of code, I hope you will understand the overall architecture of the application, and apply it in your project at ease.
For understanding the architecture deeply and grasp the overview more easier:
Spring Boot Architecture for JWT with Spring Security
You should continue to implement Refresh Token:
Spring Boot Refresh Token with JWT example
You can also know how to deploy Spring Boot App on AWS (for free) with this tutorial.
Happy learning! See you again.
Further Reading
- Spring Security Reference
- In-depth Introduction to JWT-JSON Web Token
- Spring Boot Pagination & Filter example | Spring JPA, Pageable
Fullstack CRUD App:
– Spring Boot + Vue.js example
– Angular 8 + Spring Boot + PostgreSQL example
– Angular 10 + Spring Boot + PostgreSQL example
– Angular 11 + Spring Boot + PostgreSQL example
– Angular 12 + Spring Boot + PostgreSQL example
– Angular 13 + Spring Boot + PostgreSQL example
– Angular 14 + Spring Boot + PostgreSQL example
– Angular 15 + Spring Boot + PostgreSQL example
– Angular 16 + Spring Boot + PostgreSQL example
– Spring Boot + React + PostgreSQL example
Associations:
– JPA One To One example with Hibernate in Spring Boot
– JPA One To Many example with Hibernate and Spring Boot
– JPA Many to Many example with Hibernate in Spring Boot
Documentation: Spring Boot + Swagger 3 example (with OpenAPI 3)
Caching: Spring Boot Redis Cache example
Dockerize: Docker Compose: Spring Boot and Postgres example
Spring Boot + GraphQL + PostgreSQL example
Source Code
You can find the complete source code for this tutorial on Github.
If you want to store JWT in HttpOnly Cookie, kindly visit:
Spring Boot Login example with JWT HttpOnly Cookie
Validate the signup request (password, confirm password):
Spring Boot custom Validation example

hi, what I must write instead of this
bezkoder.app.jwtSecret= bezKoderSecretKey
bezkoder.app.jwtExpirationMs= 86400000
Hey!
Very wonderful tutorial you got up there. Would be very wonderful for both learning and reference.
Anyways I stumble upon it when I was searching for the best way to store JWT and use it while making requests.
I know already know about the Browser local storage but there seem to be a lot of security concerns as regards that method. So it’s being recommended to already store JWT in an HttpOnly cookie in the server but I haven’t seen a clear implementation of the latter method anywhere, I’ve been searching :(
Please what do you have to say about these security concerns in your opinion as an experienced developer and also I would appreciate it so much if you could help me with some tips on the implementation or a tutorial you can point me to?
Hi, if you want to store JWT in HttpOnly Cookie, kindly visit:
Spring Boot Login example with JWT HttpOnly Cookie
Hi,
first of all, thank you so much for sharing. Amazing work!
I get the RuntimeException: Error: “Role is not found” when sending the post request to /api/auth/signup. In the request, I also send the role. For example:
{
“username”: “yassine”,
“email”: “[email protected]”,
“password”: “password”,
“role”: [“user”, “admin”]
}
Do you have an idea what could this be?
Again, thanks a lot for sharing this knowledge.
Hi, did you use SQL to insert 3 records into
rolestable?Hi,
thank you so much!
That was actually the step that I did not perform. Now it’s working.
Thanks a lot!
it’s really very clear, well done for the work done. I have a question about creating accounts: how do I avoid using register some users with / signup API to create admin accounts? By disabling the API?
Hi,
What if most users don’t log out of the application explicitly and just close the browser instead – the number of expired refresh tokens will keep increasing over time in the database table, right? What’s the best way to deal with this – introduce a scheduled clean up job or do cleanup whenever a new token is added to database?
Thank you
During the implementation of this code, I found that a method called
getRole()is used in the class AuthController for the classSignUpRequestbut this method was not implemented for this class, can I please get some clarification.Hi, the method was implemented :) You can read the source code for clarification.
https://github.com/bezkoder/spring-boot-security-postgresql/blob/master/src/main/java/com/bezkoder/spring/security/postgresql/payload/request/SignupRequest.java
I am looking for a full-stack ForgotPassword concept.
To manage when users want to reset their password with JavaMail
I am able to mail, but I still need to figure out how to implement refresh token.
Hey! First of all, amazing project. Thanks so much for sharing!
I have a problem when wanting to register a new user. I get the error “java.lang.RuntimeException: Error: Role is not found.” Have you ever heard of this problem?
Thanks so much, Alex
Hi, kindly make sure that you use the correct
rolein HTTP request body.I think “javax.validation cannot be resolved” should be at the beginning
Thanks a lot, you save me! Really useful and very clean code. Congratulations!
Hi! Great article, really appreciated it, two problems I came across:
1.: getting this error when trying to run after finishing everything:
***************************
APPLICATION FAILED TO START
***************************
Description:
Field authenticationManager in required a bean of type ‘org.springframework.security.authentication.AuthenticationManager’ that could not be found.
Action:
Consider defining a bean of type ‘org.springframework.security.authentication.AuthenticationManager’ in your configuration.
I solved this one by inserting this:
@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
into my websecurityconfigadapter.
2.: I managed to create users, which showed up in the db, but when i tried to sign in postman just threw a 500 error. I found that the problem was that new Date(string) is deprecated, and the solution for that was either using a parser to convert jwtExpirationMs from string to long, or just simply add a long value in generateJwtToken method.
Really thank you so much for this article helped me a lot, and made it easy to understand how everything works together.