Spring Boot Token Based Authentication With Spring Security and JWT
Spring Boot Token Based Authentication With Spring Security and JWT
In this tutorial, we’re gonna build a Spring Boot Application that supports Token based
Authentication with JWT. 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/MySQL Database
– Related Post: Spring Boot, Spring Data JPA – Building Rest CRUD API example
– More Practice:
– Fullstack:
Contents [hide]
User can signup new account, or login with username & password.
By User’s role (admin, moderator, user), we authorize the User to access resources
The database we will use could be PostgreSQL or MySQL depending on the way we configure
project dependency & datasource.
– 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 ).
– TestController has accessing protected resource methods with role based validations.
Technology
Java 8
Spring Boot 2.1.8 (with Spring Security, Spring Web, Spring Data JPA)
jjwt 0.9.1
PostgreSQL/MySQL
Maven 3.6.1
Project Structure
This is folders & files structure for our Spring Boot application:
security: we configure Spring Security & implement Security Objects here.
repository has intefaces that extend Spring Data JPA JpaRepository to interact with Database.
UserRepository extends JpaRepository
RoleRepository extends JpaRepository
models defines two main models for Authentication ( User ) & Authorization ( Role ). They have
many-to-many relationship.
We also have application.properties for configuring Spring Datasource, Spring Data JPA and App
properties (such as JWT Secret string or Token expiration time).
<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</artifactId>
<version>0.9.1</version>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
For PostgreSQL
spring.datasource.url= jdbc:postgresql://localhost:5432/testdb
spring.datasource.username= postgres
spring.datasource.password= 123
spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation= true
spring.jpa.properties.hibernate.dialect=
org.hibernate.dialect.PostgreSQLDialect
# App Properties
bezkoder.app.jwtSecret= bezKoderSecretKey
bezkoder.app.jwtExpirationMs= 86400000
For MySQL
spring.datasource.url= jdbc:mysql://localhost:3306/testdb?useSSL=false
spring.datasource.username= root
spring.datasource.password= 123456
spring.jpa.properties.hibernate.dialect=
org.hibernate.dialect.MySQL5InnoDBDialect
spring.jpa.hibernate.ddl-auto= update
# App Properties
bezkoder.app.jwtSecret= bezKoderSecretKey
bezkoder.app.jwtExpirationMs= 86400000
package com.bezkoder.springjwt.models;
import javax.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() {
package com.bezkoder.springjwt.models;
import java.util.HashSet;
import java.util.Set;
import javax.persistence.*;
import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;
import javax.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() {
}
Implement Repositories
Now, each model above needs a repository for persisting and accessing data. In repository
package, let’s create 2 repositories.
UserRepository
package com.bezkoder.springjwt.repository;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import com.bezkoder.springjwt.models.User;
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByUsername(String username);
package com.bezkoder.springjwt.repository;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import com.bezkoder.springjwt.models.ERole;
import com.bezkoder.springjwt.models.Role;
@Repository
public interface RoleRepository extends JpaRepository<Role, Long> {
Optional<Role> findByName(ERole name);
}
WebSecurityConfig.java
package com.bezkoder.springjwt.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.Authent
icationManagerBuilder;
import
org.springframework.security.config.annotation.method.configuration.EnableGlob
alMethodSecurity;
import
org.springframework.security.config.annotation.web.builders.HttpSecurity;
import
org.springframework.security.config.annotation.web.configuration.EnableWebSecu
rity;
import
org.springframework.security.config.annotation.web.configuration.WebSecurityCo
nfigurerAdapter;
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.UsernamePasswordAuthentication
Filter;
import com.bezkoder.springjwt.security.jwt.AuthEntryPointJwt;
import com.bezkoder.springjwt.security.jwt.AuthTokenFilter;
import com.bezkoder.springjwt.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).passwordEn
coder(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).an
d()
.authorizeRequests().antMatchers("/api/auth/**").permitAll()
.antMatchers("/api/test/**").permitAll()
.anyRequest().authenticated();
http.addFilterBefore(authenticationJwtTokenFilter(),
UsernamePasswordAuthenticationFilter.class);
}
}
– Spring Security will load User details to perform authentication & authorization. So it has
UserDetailsService interface that we need to implement.
Authentication authentication =
authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(username, password)
);
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.springjwt.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.springjwt.models.User;
import com.fasterxml.jackson.annotation.JsonIgnore;
@JsonIgnore
private String password;
@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;
}
@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 into List . 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:
security/services/UserDetailsServiceImpl.java
package com.bezkoder.springjwt.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.springjwt.models.User;
import com.bezkoder.springjwt.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.
security/jwt/AuthTokenFilter.java
package com.bezkoder.springjwt.security.jwt;
import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import
org.springframework.security.authentication.UsernamePasswordAuthenticationToke
n;
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.springjwt.security.services.UserDetailsServiceImpl;
@Autowired
private UserDetailsServiceImpl userDetailsService;
@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);
}
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()
security/jwt/JwtUtils.java
package com.bezkoder.springjwt.security.jwt;
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.springjwt.security.services.UserDetailsImpl;
import io.jsonwebtoken.*;
@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;
return false;
}
}
security/jwt/AuthEntryPointJwt.java
package com.bezkoder.springjwt.security.jwt;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
@Component
public class AuthEntryPointJwt implements AuthenticationEntryPoint {
@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");
}
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.
– Responses:
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.
– /api/auth/signup
– /api/auth/signin
authenticate { username, pasword }
update SecurityContext using Authentication object
generate JWT
get UserDetails from Authentication object
response contains JWT and UserDetails data
controllers/AuthController.java
package com.bezkoder.springjwt.controllers;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import javax.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.UsernamePasswordAuthenticationToke
n;
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.springjwt.models.ERole;
import com.bezkoder.springjwt.models.Role;
import com.bezkoder.springjwt.models.User;
import com.bezkoder.springjwt.payload.request.LoginRequest;
import com.bezkoder.springjwt.payload.request.SignupRequest;
import com.bezkoder.springjwt.payload.response.JwtResponse;
import com.bezkoder.springjwt.payload.response.MessageResponse;
import com.bezkoder.springjwt.repository.RoleRepository;
import com.bezkoder.springjwt.repository.UserRepository;
import com.bezkoder.springjwt.security.jwt.JwtUtils;
import com.bezkoder.springjwt.security.services.UserDetailsImpl;
@Autowired
UserRepository userRepository;
@Autowired
RoleRepository roleRepository;
@Autowired
PasswordEncoder encoder;
@Autowired
JwtUtils jwtUtils;
@PostMapping("/signin")
public ResponseEntity<?> authenticateUser(@Valid @RequestBody LoginRequest
loginRequest) {
SecurityContextHolder.getContext().setAuthentication(authentication);
String jwt = jwtUtils.generateJwtToken(authentication);
@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!"));
}
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);
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter { ... }
Now we can secure methods in our Apis with @PreAuthorize annotation easily.
controllers/TestController.java
package com.bezkoder.springjwt.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;
@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.";
}
}
\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:
id | name
----+----------------
1 | ROLE_USER
2 | ROLE_MODERATOR
3 | ROLE_ADMIN
(3 rows)
id | email | password
| username
----+--------------------+----------------------------------------------------
----------+----------
1 | admin@bezkoder.com |
$2a$10$4K8Vq5mw.nwxl.WRmuYCfevme82c73uGkEcnPbmm/3/YJ3UToie7m | admin
2 | mod@bezkoder.com |
$2a$10$1dCKuQoQqbBNCK.Rb8XQSemwqdHdVAcCTb1kUQLg2key/4VX./TvS | mod
3 | user@bezkoder.com |
$2a$10$e9Mgd/63paPL0VBj232BH.tQvIgQu0/tBg/rwfyDVMUcQc8djEPle | zkoder
(3 rows)
id | name
----+----------------
1 | ROLE_USER
2 | ROLE_MODERATOR
3 | ROLE_ADMIN
(3 rows)
user_id | role_id
---------+---------
1 | 3
2 | 1
2 | 2
3 | 1
(4 rows)
Conclusion
Congratulation!
Today we’ve learned so many interesting things about Spring Security and JWT Token based
Authentication in just a Spring Boot example.
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.
You can also know how to deploy Spring Boot App on AWS (for free) with this tutorial.
Further Reading
Spring Security Reference
In-depth Introduction to JWT-JSON Web Token
Spring Boot + Vuejs: Authentication with JWT & Spring Security Example
Source Code
You can find the complete source code for this tutorial on Github.