├── README.md
├── pom.xml
└── src
├── main
├── java
│ └── com
│ │ └── loizenai
│ │ └── jwtauthentication
│ │ ├── SpringBootJwtAuthenticationExamplesApplication.java
│ │ ├── controller
│ │ ├── AuthRestAPIs.java
│ │ └── TestRestAPIs.java
│ │ ├── message
│ │ ├── request
│ │ │ ├── LoginForm.java
│ │ │ └── SignUpForm.java
│ │ └── response
│ │ │ ├── JwtResponse.java
│ │ │ └── ResponseMessage.java
│ │ ├── model
│ │ ├── Role.java
│ │ ├── RoleName.java
│ │ └── User.java
│ │ ├── repository
│ │ ├── RoleRepository.java
│ │ └── UserRepository.java
│ │ └── security
│ │ ├── WebSecurityConfig.java
│ │ ├── jwt
│ │ ├── JwtAuthEntryPoint.java
│ │ ├── JwtAuthTokenFilter.java
│ │ └── JwtProvider.java
│ │ └── services
│ │ ├── UserDetailsServiceImpl.java
│ │ └── UserPrinciple.java
└── resources
│ ├── application.properties
│ └── roles.sql
└── test
└── java
└── com
└── loizenai
└── jwtauthentication
└── SpringBootJwtAuthenticationExamplesApplicationTests.java
/README.md:
--------------------------------------------------------------------------------
1 | # Jwt Springboot Reactjs Token Authentication Example - React.js Spring Security Login
2 |
3 | Tutorial Link: https://loizenai.com/reactjs-springboot-jwt-token-authentication/
4 |
5 | 
6 |
7 | JSON Web Token (JWT) is an open standard (RFC 7519) that defines a compact and self-contained way for securely transmitting information between parties as a JSON object. And “How to build Reactjs Jwt SpringBoot Token Based Authentication Example?” is one of the most common questions for SpringBoot Java development world. So in the tutorial, I introduce how to implement an application “Reactjs JWT SpringBoot token Authentication Example” with details step by step and 100% running sourcecode.
8 |
9 | – I give you an Epic of the application, a fullstack excutive flow from frontend to backend with overall architecture diagram.
10 | – I give you a layer diagram of Reactjs JWT Application.
11 | – I guide you detail-steps how to implement a security Jwt Token SpringBoot backend.
12 | – I guide you step by step how to develop a Reactjs JWT Authentication application.
13 | – Finally, I do an integrative testing from Reactjs JWT Authentication application to jwt SpringBoot Security RestAPIs.
14 |
15 | ## Overall System Architecture Diagram
16 |
17 | 
18 |
19 | For the Reactjs JWT Authentication tutorial, we have 2 projects:
20 | – Backend project (using SpringBoot or Nodejs Express) provides secured RestAPIs with JWT token.
21 | – Reactjs project will request RestAPIs from Backend system with the JWT Token Authentication implementation.
22 |
23 | ## JWT Authentication Sequence Diagram
24 |
25 | The diagram below show how our system handles User Registration and User Login processes:
26 |
27 | 
28 |
29 | 1. User Registration Phase:
30 | – User uses a React.js register form to post user’s info (name, username, email, role, password) to Backend API /api/auth/signup.
31 | – Backend will check the existing users in database and save user’s signup info to database. Finally, It will return a message (successfully or fail) to
32 |
33 | 2. User Login Phase:
34 | – User posts user/password to signin to Backend RestAPI /api/auth/signin.
35 | – Backend will check the username/password, if it is right, Backend will create and JWT string with secret then return it to Reactjs client.
36 |
37 | After signin, user can request secured resources from backend server by adding the JWT token in Authorization Header. For each request, backend will check the JWT signature and then returns back the resources based on user’s registered authorities.
38 |
39 | ## Reactjs JWT Authentication Diagram Overview
40 |
41 | Reactjs JWT Authentication would be built with 5 main kind blocks:
42 |
43 | - Reactjs Router is a standard library for routing in React. It enables the navigation among views of various components in a React Application, allows changing the browser URL, and keeps the UI in sync with the URL.
44 | - Reactjs Components let you split the UI into independent, reusable pieces, and think about each piece in isolation.
45 | - Reactjs Service is a bridge between Reactjs Component and Backend Server, it is used to do technical logic with Backend Server (using Ajax Engine to fetch data from Backend, or using Local Storage to save user login data) and returned a response data to React.js Components
46 | - Local Storage allow to save key/value pairs in a web browser. It is a place to save the login user’s info.
47 | - Axios – (an Ajax Engine) is a promise-based HTTP client for the browser and Node. js. Axios makes it easy to send asynchronous HTTP requests to REST endpoints and perform CRUD operations.
48 |
49 | ## Jwt SpringBoot Token Security RestAPIs Diagram Overview
50 | This is diagram for Spring Security/JWT (Springboot Token Based Authentication Example) classes that are separated into 3 layers:
51 | – HTTP
52 | – Spring Security
53 | – REST API
54 |
55 | 
56 |
57 | Look at the diagram above, we can easily associate these components with Spring Security Authentication process: receive HTTP request, filter, authenticate, store Authentication data, generate token, get User details, authorize, handle exception…
58 |
59 | At a glance:
60 | – SecurityContextHolder provides access to the SecurityContext.
61 | – SecurityContext holds the Authentication and possibly request-specific security information.
62 | – Authentication represents the principal which includes GrantedAuthority that reflects the application-wide permissions granted to a principal.
63 | – UserDetails contains necessary information to build an Authentication object from DAOs or other source of security data.
64 | – UserDetailsService helps to create a UserDetails from a String-based username and is usually used by AuthenticationProvider.
65 | – JwtAuthTokenFilter (extends OncePerRequestFilter) pre-processes HTTP request, from Token, create Authentication and populate it to SecurityContext.
66 | – JwtProvider validates, parses token String or generates token String from UserDetails.
67 | – UsernamePasswordAuthenticationToken gets username/password from login Request and combines into an instance of Authentication interface.
68 | – AuthenticationManager uses DaoAuthenticationProvider (with help of UserDetailsService & PasswordEncoder) to validate instance of UsernamePasswordAuthenticationToken, then returns a fully populated Authentication instance on successful authentication.
69 | – SecurityContext is established by calling SecurityContextHolder.getContext().setAuthentication(…) with returned authentication object above.
70 | – AuthenticationEntryPoint handles AuthenticationException.
71 | – Access to Restful API is protected by HTTPSecurity and authorized with Method Security Expressions.
72 |
73 | ## Project Goal
74 |
75 | We create a Reactjs JWT Authentication project as below:
76 |
77 | [Reactjs Project structure](https://loizenai.com/wp-content/uploads/2020/11/Reactjs-Jwt-Authentication-project-structure-1.png)
78 |
79 | It includes 8 components and 2 services and a router in app.js file.
80 |
81 | – Home page:
82 |
83 | 
84 |
85 | – User Register page:
86 |
87 | 
88 |
89 | – Login Page:
90 |
91 | 
92 |
93 | – Profile Page:
94 |
95 | 
96 |
97 | – Use Page:
98 |
99 | [User page](https://loizenai.com/wp-content/uploads/2020/11/Reactjs-jwt-authentication-User-Page-Content-3.png)
100 |
101 | – Project Manager Page:
102 |
103 | 
104 |
105 | – Reactjs Admin page:
106 |
107 | 
108 |
109 | ## Realated post
110 |
111 | [Angular 10 + Spring Boot JWT Token Based Authentication Example – Spring Security + MySQL](https://loizenai.com/angular-10-spring-boot-jwt-token-based-authentication-example-spring-security-mysql-database/)
112 | [Angular 10 + Nodejs JWT Token Based Authentication with MySQL Example – Express RestAPIs + JWT + BCryptjs + Sequelize](https://loizenai.com/angular-10-nodejs-jwt-authentication-mysql-examples-tutorials/)
113 | [SpringBoot Token Based Authentication Example – MySQL + JWT+ Spring JPA + RestAPIs](https://loizenai.com/spring-boot-security-jwt-token-bsed-authentication-example-mysql-spring-jpa-restapis/)
114 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | 4.0.0
5 |
6 | org.springframework.boot
7 | spring-boot-starter-parent
8 | 2.2.7.RELEASE
9 |
10 |
11 | com.loizenai
12 | SpringBootJwtAuthenticationExamples
13 | 1
14 | SpringBootJwtAuthenticationExamples
15 | SpringBootJwtAuthenticationExamples
16 |
17 |
18 | 1.8
19 |
20 |
21 |
22 |
23 | org.springframework.boot
24 | spring-boot-starter-data-jpa
25 |
26 |
27 | org.springframework.boot
28 | spring-boot-starter-security
29 |
30 |
31 | org.springframework.boot
32 | spring-boot-starter-web
33 |
34 |
35 | mysql
36 | mysql-connector-java
37 | runtime
38 |
39 |
40 |
41 |
42 | io.jsonwebtoken
43 | jjwt
44 | 0.9.0
45 |
46 |
47 |
48 | org.springframework.boot
49 | spring-boot-starter
50 |
51 |
52 |
53 | org.springframework.boot
54 | spring-boot-starter-test
55 | test
56 |
57 |
58 | org.junit.vintage
59 | junit-vintage-engine
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 | org.springframework.boot
69 | spring-boot-maven-plugin
70 |
71 |
72 |
73 |
74 |
75 |
--------------------------------------------------------------------------------
/src/main/java/com/loizenai/jwtauthentication/SpringBootJwtAuthenticationExamplesApplication.java:
--------------------------------------------------------------------------------
1 | package com.loizenai.jwtauthentication;
2 |
3 | import org.springframework.boot.SpringApplication;
4 | import org.springframework.boot.autoconfigure.SpringBootApplication;
5 |
6 | @SpringBootApplication
7 | public class SpringBootJwtAuthenticationExamplesApplication {
8 |
9 | public static void main(String[] args) {
10 | SpringApplication.run(SpringBootJwtAuthenticationExamplesApplication.class, args);
11 | }
12 |
13 | }
14 |
--------------------------------------------------------------------------------
/src/main/java/com/loizenai/jwtauthentication/controller/AuthRestAPIs.java:
--------------------------------------------------------------------------------
1 | package com.loizenai.jwtauthentication.controller;
2 |
3 | import java.util.HashSet;
4 | import java.util.Set;
5 |
6 | import javax.validation.Valid;
7 |
8 | import org.springframework.beans.factory.annotation.Autowired;
9 | import org.springframework.http.HttpStatus;
10 | import org.springframework.http.ResponseEntity;
11 | import org.springframework.security.authentication.AuthenticationManager;
12 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
13 | import org.springframework.security.core.Authentication;
14 | import org.springframework.security.core.context.SecurityContextHolder;
15 | import org.springframework.security.core.userdetails.UserDetails;
16 | import org.springframework.security.crypto.password.PasswordEncoder;
17 | import org.springframework.web.bind.annotation.CrossOrigin;
18 | import org.springframework.web.bind.annotation.PostMapping;
19 | import org.springframework.web.bind.annotation.RequestBody;
20 | import org.springframework.web.bind.annotation.RequestMapping;
21 | import org.springframework.web.bind.annotation.RestController;
22 |
23 | import com.loizenai.jwtauthentication.message.request.LoginForm;
24 | import com.loizenai.jwtauthentication.message.request.SignUpForm;
25 | import com.loizenai.jwtauthentication.message.response.JwtResponse;
26 | import com.loizenai.jwtauthentication.message.response.ResponseMessage;
27 | import com.loizenai.jwtauthentication.model.Role;
28 | import com.loizenai.jwtauthentication.model.RoleName;
29 | import com.loizenai.jwtauthentication.model.User;
30 | import com.loizenai.jwtauthentication.repository.RoleRepository;
31 | import com.loizenai.jwtauthentication.repository.UserRepository;
32 | import com.loizenai.jwtauthentication.security.jwt.JwtProvider;
33 |
34 | @CrossOrigin(origins = "*", maxAge = 3600)
35 | @RestController
36 | @RequestMapping("/api/auth")
37 | public class AuthRestAPIs {
38 |
39 | @Autowired
40 | AuthenticationManager authenticationManager;
41 |
42 | @Autowired
43 | UserRepository userRepository;
44 |
45 | @Autowired
46 | RoleRepository roleRepository;
47 |
48 | @Autowired
49 | PasswordEncoder encoder;
50 |
51 | @Autowired
52 | JwtProvider jwtProvider;
53 |
54 | @PostMapping("/signin")
55 | public ResponseEntity> authenticateUser(@Valid @RequestBody LoginForm loginRequest) {
56 |
57 | Authentication authentication = authenticationManager.authenticate(
58 | new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getPassword()));
59 |
60 | SecurityContextHolder.getContext().setAuthentication(authentication);
61 |
62 | String jwt = jwtProvider.generateJwtToken(authentication);
63 | UserDetails userDetails = (UserDetails) authentication.getPrincipal();
64 |
65 | return ResponseEntity.ok(new JwtResponse(jwt, userDetails.getUsername(), userDetails.getAuthorities()));
66 | }
67 |
68 | @PostMapping("/signup")
69 | public ResponseEntity> registerUser(@Valid @RequestBody SignUpForm signUpRequest) {
70 | if (userRepository.existsByUsername(signUpRequest.getUsername())) {
71 | return new ResponseEntity<>(new ResponseMessage("Fail -> Username is already taken!"),
72 | HttpStatus.BAD_REQUEST);
73 | }
74 |
75 | if (userRepository.existsByEmail(signUpRequest.getEmail())) {
76 | return new ResponseEntity<>(new ResponseMessage("Fail -> Email is already in use!"),
77 | HttpStatus.BAD_REQUEST);
78 | }
79 |
80 | // Creating user's account
81 | User user = new User(signUpRequest.getFirstname(), signUpRequest.getLastname(),
82 | signUpRequest.getUsername(), signUpRequest.getEmail(),
83 | encoder.encode(signUpRequest.getPassword()));
84 |
85 | Set roles = new HashSet<>();
86 | if(signUpRequest.getRole() != null) {
87 | Set strRoles = signUpRequest.getRole();
88 |
89 | strRoles.forEach(role -> {
90 | switch (role) {
91 | case "admin":
92 | Role adminRole = roleRepository.findByName(RoleName.ROLE_ADMIN)
93 | .orElseThrow(() -> new RuntimeException("Fail! -> Cause: User Role not find."));
94 | roles.add(adminRole);
95 |
96 | break;
97 | case "pm":
98 | Role pmRole = roleRepository.findByName(RoleName.ROLE_PM)
99 | .orElseThrow(() -> new RuntimeException("Fail! -> Cause: User Role not find."));
100 | roles.add(pmRole);
101 |
102 | break;
103 | default:
104 | Role userRole = roleRepository.findByName(RoleName.ROLE_USER)
105 | .orElseThrow(() -> new RuntimeException("Fail! -> Cause: User Role not find."));
106 | roles.add(userRole);
107 | }
108 | });
109 | } else {
110 | // default mode : User register
111 | Role userRole = roleRepository.findByName(RoleName.ROLE_USER)
112 | .orElseThrow(() -> new RuntimeException("Fail! -> Cause: User Role not find."));
113 | roles.add(userRole);
114 | }
115 |
116 | user.setRoles(roles);
117 | userRepository.save(user);
118 |
119 | return new ResponseEntity<>(new ResponseMessage("User "+ signUpRequest.getFirstname() + " is registered successfully!"), HttpStatus.OK);
120 | }
121 | }
--------------------------------------------------------------------------------
/src/main/java/com/loizenai/jwtauthentication/controller/TestRestAPIs.java:
--------------------------------------------------------------------------------
1 | package com.loizenai.jwtauthentication.controller;
2 |
3 | import org.springframework.security.access.prepost.PreAuthorize;
4 | import org.springframework.web.bind.annotation.CrossOrigin;
5 | import org.springframework.web.bind.annotation.GetMapping;
6 | import org.springframework.web.bind.annotation.RestController;
7 |
8 | @CrossOrigin(origins = "*", maxAge = 3600)
9 | @RestController
10 | public class TestRestAPIs {
11 |
12 | @GetMapping("/api/test/user")
13 | @PreAuthorize("hasRole('USER') or hasRole('ADMIN')")
14 | public String userAccess() {
15 | return ">>> User Contents!";
16 | }
17 |
18 | @GetMapping("/api/test/pm")
19 | @PreAuthorize("hasRole('PM') or hasRole('ADMIN')")
20 | public String projectManagementAccess() {
21 | return ">>> Project Management Board";
22 | }
23 |
24 | @GetMapping("/api/test/admin")
25 | @PreAuthorize("hasRole('ADMIN')")
26 | public String adminAccess() {
27 | return ">>> Admin Contents";
28 | }
29 | }
--------------------------------------------------------------------------------
/src/main/java/com/loizenai/jwtauthentication/message/request/LoginForm.java:
--------------------------------------------------------------------------------
1 | package com.loizenai.jwtauthentication.message.request;
2 |
3 | import javax.validation.constraints.NotBlank;
4 | import javax.validation.constraints.Size;
5 |
6 | public class LoginForm {
7 | @NotBlank
8 | @Size(min=3, max = 60)
9 | private String username;
10 |
11 | @NotBlank
12 | @Size(min = 6, max = 40)
13 | private String password;
14 |
15 | public String getUsername() {
16 | return username;
17 | }
18 |
19 | public void setUsername(String username) {
20 | this.username = username;
21 | }
22 |
23 | public String getPassword() {
24 | return password;
25 | }
26 |
27 | public void setPassword(String password) {
28 | this.password = password;
29 | }
30 | }
--------------------------------------------------------------------------------
/src/main/java/com/loizenai/jwtauthentication/message/request/SignUpForm.java:
--------------------------------------------------------------------------------
1 | package com.loizenai.jwtauthentication.message.request;
2 |
3 | import java.util.Set;
4 |
5 | import javax.validation.constraints.*;
6 |
7 | public class SignUpForm {
8 | @NotBlank
9 | @Size(min = 3, max = 50)
10 | private String firstname;
11 |
12 | @NotBlank
13 | @Size(min = 3, max = 50)
14 | private String lastname;
15 |
16 | @NotBlank
17 | @Size(min = 3, max = 50)
18 | private String username;
19 |
20 | @NotBlank
21 | @Size(max = 60)
22 | @Email
23 | private String email;
24 |
25 | private Set role;
26 |
27 | @NotBlank
28 | @Size(min = 6, max = 40)
29 | private String password;
30 |
31 | public String getFirstname() {
32 | return firstname;
33 | }
34 |
35 | public void setFirstname(String firstname) {
36 | this.firstname = firstname;
37 | }
38 |
39 | public String getLastname() {
40 | return lastname;
41 | }
42 |
43 | public void setLastname(String lastname) {
44 | this.lastname = lastname;
45 | }
46 |
47 | public String getUsername() {
48 | return username;
49 | }
50 |
51 | public void setUsername(String username) {
52 | this.username = username;
53 | }
54 |
55 | public String getEmail() {
56 | return email;
57 | }
58 |
59 | public void setEmail(String email) {
60 | this.email = email;
61 | }
62 |
63 | public String getPassword() {
64 | return password;
65 | }
66 |
67 | public void setPassword(String password) {
68 | this.password = password;
69 | }
70 |
71 | public Set getRole() {
72 | return this.role;
73 | }
74 |
75 | public void setRole(Set role) {
76 | this.role = role;
77 | }
78 | }
--------------------------------------------------------------------------------
/src/main/java/com/loizenai/jwtauthentication/message/response/JwtResponse.java:
--------------------------------------------------------------------------------
1 | package com.loizenai.jwtauthentication.message.response;
2 |
3 | import java.util.Collection;
4 |
5 | import org.springframework.security.core.GrantedAuthority;
6 |
7 | public class JwtResponse {
8 | private String token;
9 | private String type = "Bearer";
10 | private String username;
11 | private Collection extends GrantedAuthority> authorities;
12 |
13 | public JwtResponse(String accessToken, String username, Collection extends GrantedAuthority> authorities) {
14 | this.token = accessToken;
15 | this.username = username;
16 | this.authorities = authorities;
17 | }
18 |
19 | public String getAccessToken() {
20 | return token;
21 | }
22 |
23 | public void setAccessToken(String accessToken) {
24 | this.token = accessToken;
25 | }
26 |
27 | public String getTokenType() {
28 | return type;
29 | }
30 |
31 | public void setTokenType(String tokenType) {
32 | this.type = tokenType;
33 | }
34 |
35 | public String getUsername() {
36 | return username;
37 | }
38 |
39 | public void setUsername(String username) {
40 | this.username = username;
41 | }
42 |
43 | public Collection extends GrantedAuthority> getAuthorities() {
44 | return authorities;
45 | }
46 | }
--------------------------------------------------------------------------------
/src/main/java/com/loizenai/jwtauthentication/message/response/ResponseMessage.java:
--------------------------------------------------------------------------------
1 | package com.loizenai.jwtauthentication.message.response;
2 |
3 | public class ResponseMessage {
4 | private String message;
5 |
6 | public ResponseMessage(String message) {
7 | this.message = message;
8 | }
9 |
10 | public String getMessage() {
11 | return message;
12 | }
13 |
14 | public void setMessage(String message) {
15 | this.message = message;
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/main/java/com/loizenai/jwtauthentication/model/Role.java:
--------------------------------------------------------------------------------
1 | package com.loizenai.jwtauthentication.model;
2 |
3 | import javax.persistence.Column;
4 | import javax.persistence.Entity;
5 | import javax.persistence.EnumType;
6 | import javax.persistence.Enumerated;
7 | import javax.persistence.GeneratedValue;
8 | import javax.persistence.GenerationType;
9 | import javax.persistence.Id;
10 | import javax.persistence.Table;
11 |
12 | import org.hibernate.annotations.NaturalId;
13 |
14 | @Entity
15 | @Table(name = "roles")
16 | public class Role {
17 | @Id
18 | @GeneratedValue(strategy = GenerationType.IDENTITY)
19 | private Long id;
20 |
21 | @Enumerated(EnumType.STRING)
22 | @NaturalId
23 | @Column(length = 60)
24 | private RoleName name;
25 |
26 | public Role() {}
27 |
28 | public Role(RoleName name) {
29 | this.name = name;
30 | }
31 |
32 | public Long getId() {
33 | return id;
34 | }
35 |
36 | public void setId(Long id) {
37 | this.id = id;
38 | }
39 |
40 | public RoleName getName() {
41 | return name;
42 | }
43 |
44 | public void setName(RoleName name) {
45 | this.name = name;
46 | }
47 | }
--------------------------------------------------------------------------------
/src/main/java/com/loizenai/jwtauthentication/model/RoleName.java:
--------------------------------------------------------------------------------
1 | package com.loizenai.jwtauthentication.model;
2 |
3 | public enum RoleName {
4 | ROLE_USER,
5 | ROLE_PM,
6 | ROLE_ADMIN
7 | }
--------------------------------------------------------------------------------
/src/main/java/com/loizenai/jwtauthentication/model/User.java:
--------------------------------------------------------------------------------
1 | package com.loizenai.jwtauthentication.model;
2 |
3 | import java.util.HashSet;
4 | import java.util.Set;
5 |
6 | import javax.persistence.Entity;
7 | import javax.persistence.FetchType;
8 | import javax.persistence.GeneratedValue;
9 | import javax.persistence.GenerationType;
10 | import javax.persistence.Id;
11 | import javax.persistence.JoinColumn;
12 | import javax.persistence.JoinTable;
13 | import javax.persistence.ManyToMany;
14 | import javax.persistence.Table;
15 | import javax.persistence.UniqueConstraint;
16 | import javax.validation.constraints.Email;
17 | import javax.validation.constraints.NotBlank;
18 | import javax.validation.constraints.Size;
19 |
20 | import org.hibernate.annotations.NaturalId;
21 |
22 | @Entity
23 | @Table(name = "users", uniqueConstraints = {
24 | @UniqueConstraint(columnNames = {
25 | "username"
26 | }),
27 | @UniqueConstraint(columnNames = {
28 | "email"
29 | })
30 | })
31 | public class User{
32 | @Id
33 | @GeneratedValue(strategy = GenerationType.IDENTITY)
34 | private Long id;
35 |
36 | @NotBlank
37 | @Size(min=3, max = 50)
38 | private String firstname;
39 |
40 | @NotBlank
41 | @Size(min=3, max = 50)
42 | private String lastname;
43 |
44 | @NotBlank
45 | @Size(min=3, max = 50)
46 | private String username;
47 |
48 | @NaturalId
49 | @NotBlank
50 | @Size(max = 50)
51 | @Email
52 | private String email;
53 |
54 | @NotBlank
55 | @Size(min=6, max = 100)
56 | private String password;
57 |
58 | @ManyToMany(fetch = FetchType.LAZY)
59 | @JoinTable(name = "user_roles",
60 | joinColumns = @JoinColumn(name = "user_id"),
61 | inverseJoinColumns = @JoinColumn(name = "role_id"))
62 | private Set roles = new HashSet<>();
63 |
64 | public User() {}
65 |
66 | public User(String firstname, String lastname,
67 | String username, String email, String password) {
68 | this.firstname = firstname;
69 | this.lastname = lastname;
70 | this.username = username;
71 | this.email = email;
72 | this.password = password;
73 | }
74 |
75 | public Long getId() {
76 | return id;
77 | }
78 |
79 | public void setId(Long id) {
80 | this.id = id;
81 | }
82 |
83 | public String getUsername() {
84 | return username;
85 | }
86 |
87 | public void setUsername(String username) {
88 | this.username = username;
89 | }
90 |
91 | public String getFirstname() {
92 | return firstname;
93 | }
94 |
95 | public void setFirstname(String firstname) {
96 | this.firstname = firstname;
97 | }
98 |
99 | public String getLastname() {
100 | return lastname;
101 | }
102 |
103 | public void setLastname(String lastname) {
104 | this.lastname = lastname;
105 | }
106 |
107 | public String getEmail() {
108 | return email;
109 | }
110 |
111 | public void setEmail(String email) {
112 | this.email = email;
113 | }
114 |
115 | public String getPassword() {
116 | return password;
117 | }
118 |
119 | public void setPassword(String password) {
120 | this.password = password;
121 | }
122 |
123 | public Set getRoles() {
124 | return roles;
125 | }
126 |
127 | public void setRoles(Set roles) {
128 | this.roles = roles;
129 | }
130 | }
--------------------------------------------------------------------------------
/src/main/java/com/loizenai/jwtauthentication/repository/RoleRepository.java:
--------------------------------------------------------------------------------
1 | package com.loizenai.jwtauthentication.repository;
2 |
3 | import java.util.Optional;
4 |
5 | import org.springframework.data.jpa.repository.JpaRepository;
6 | import org.springframework.stereotype.Repository;
7 |
8 | import com.loizenai.jwtauthentication.model.Role;
9 | import com.loizenai.jwtauthentication.model.RoleName;
10 |
11 | @Repository
12 | public interface RoleRepository extends JpaRepository {
13 | Optional findByName(RoleName roleName);
14 | }
--------------------------------------------------------------------------------
/src/main/java/com/loizenai/jwtauthentication/repository/UserRepository.java:
--------------------------------------------------------------------------------
1 | package com.loizenai.jwtauthentication.repository;
2 |
3 | import java.util.Optional;
4 |
5 | import org.springframework.data.jpa.repository.JpaRepository;
6 | import org.springframework.stereotype.Repository;
7 |
8 | import com.loizenai.jwtauthentication.model.User;
9 |
10 | @Repository
11 | public interface UserRepository extends JpaRepository {
12 | Optional findByUsername(String username);
13 | Boolean existsByUsername(String username);
14 | Boolean existsByEmail(String email);
15 | }
--------------------------------------------------------------------------------
/src/main/java/com/loizenai/jwtauthentication/security/WebSecurityConfig.java:
--------------------------------------------------------------------------------
1 | package com.loizenai.jwtauthentication.security;
2 |
3 | import org.springframework.beans.factory.annotation.Autowired;
4 | import org.springframework.context.annotation.Bean;
5 | import org.springframework.context.annotation.Configuration;
6 | import org.springframework.security.authentication.AuthenticationManager;
7 | import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
8 | import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
9 | import org.springframework.security.config.annotation.web.builders.HttpSecurity;
10 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
11 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
12 | import org.springframework.security.config.http.SessionCreationPolicy;
13 | import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
14 | import org.springframework.security.crypto.password.PasswordEncoder;
15 | import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
16 |
17 | import com.loizenai.jwtauthentication.security.jwt.JwtAuthEntryPoint;
18 | import com.loizenai.jwtauthentication.security.jwt.JwtAuthTokenFilter;
19 | import com.loizenai.jwtauthentication.security.services.UserDetailsServiceImpl;
20 |
21 | @Configuration
22 | @EnableWebSecurity
23 | @EnableGlobalMethodSecurity(
24 | prePostEnabled = true
25 | )
26 | public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
27 | @Autowired
28 | UserDetailsServiceImpl userDetailsService;
29 |
30 | @Autowired
31 | private JwtAuthEntryPoint unauthorizedHandler;
32 |
33 | @Bean
34 | public JwtAuthTokenFilter authenticationJwtTokenFilter() {
35 | return new JwtAuthTokenFilter();
36 | }
37 |
38 | @Override
39 | public void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
40 | authenticationManagerBuilder
41 | .userDetailsService(userDetailsService)
42 | .passwordEncoder(passwordEncoder());
43 | }
44 |
45 | @Bean
46 | @Override
47 | public AuthenticationManager authenticationManagerBean() throws Exception {
48 | return super.authenticationManagerBean();
49 | }
50 |
51 | @Bean
52 | public PasswordEncoder passwordEncoder() {
53 | return new BCryptPasswordEncoder();
54 | }
55 |
56 | @Override
57 | protected void configure(HttpSecurity http) throws Exception {
58 | http.cors().and().csrf().disable().
59 | authorizeRequests()
60 | .antMatchers("/api/auth/**").permitAll()
61 | .anyRequest().authenticated()
62 | .and()
63 | .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
64 | .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
65 |
66 | http.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class);
67 | }
68 | }
--------------------------------------------------------------------------------
/src/main/java/com/loizenai/jwtauthentication/security/jwt/JwtAuthEntryPoint.java:
--------------------------------------------------------------------------------
1 | package com.loizenai.jwtauthentication.security.jwt;
2 |
3 | import java.io.IOException;
4 |
5 | import javax.servlet.ServletException;
6 | import javax.servlet.http.HttpServletRequest;
7 | import javax.servlet.http.HttpServletResponse;
8 |
9 | import org.slf4j.Logger;
10 | import org.slf4j.LoggerFactory;
11 | import org.springframework.security.core.AuthenticationException;
12 | import org.springframework.security.web.AuthenticationEntryPoint;
13 | import org.springframework.stereotype.Component;
14 |
15 | @Component
16 | public class JwtAuthEntryPoint implements AuthenticationEntryPoint {
17 |
18 | private static final Logger logger = LoggerFactory.getLogger(JwtAuthEntryPoint.class);
19 |
20 | @Override
21 | public void commence(HttpServletRequest request,
22 | HttpServletResponse response,
23 | AuthenticationException e)
24 | throws IOException, ServletException {
25 |
26 | logger.error("Unauthorized error. Message - {}", e.getMessage());
27 | response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Error -> Unauthorized");
28 | }
29 | }
--------------------------------------------------------------------------------
/src/main/java/com/loizenai/jwtauthentication/security/jwt/JwtAuthTokenFilter.java:
--------------------------------------------------------------------------------
1 | package com.loizenai.jwtauthentication.security.jwt;
2 |
3 | import java.io.IOException;
4 |
5 | import javax.servlet.FilterChain;
6 | import javax.servlet.ServletException;
7 | import javax.servlet.http.HttpServletRequest;
8 | import javax.servlet.http.HttpServletResponse;
9 |
10 | import org.slf4j.Logger;
11 | import org.slf4j.LoggerFactory;
12 | import org.springframework.beans.factory.annotation.Autowired;
13 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
14 | import org.springframework.security.core.context.SecurityContextHolder;
15 | import org.springframework.security.core.userdetails.UserDetails;
16 | import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
17 | import org.springframework.web.filter.OncePerRequestFilter;
18 |
19 | import com.loizenai.jwtauthentication.security.services.UserDetailsServiceImpl;
20 |
21 | public class JwtAuthTokenFilter extends OncePerRequestFilter {
22 |
23 | @Autowired
24 | private JwtProvider tokenProvider;
25 |
26 | @Autowired
27 | private UserDetailsServiceImpl userDetailsService;
28 |
29 | private static final Logger logger = LoggerFactory.getLogger(JwtAuthTokenFilter.class);
30 |
31 | @Override
32 | protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
33 | throws ServletException, IOException {
34 | try {
35 |
36 | String jwt = getJwt(request);
37 | if (jwt != null && tokenProvider.validateJwtToken(jwt)) {
38 | String username = tokenProvider.getUserNameFromJwtToken(jwt);
39 |
40 | UserDetails _userDetails = userDetailsService.loadUserByUsername(username);
41 | UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
42 | _userDetails, null, _userDetails.getAuthorities());
43 | authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
44 |
45 | SecurityContextHolder.getContext().setAuthentication(authentication);
46 | }
47 | } catch (Exception e) {
48 | logger.error("Can NOT set user authentication -> Message: {}", e);
49 | }
50 |
51 | filterChain.doFilter(request, response);
52 | }
53 |
54 | private String getJwt(HttpServletRequest request) {
55 | String authHeader = request.getHeader("Authorization");
56 |
57 | if (authHeader != null && authHeader.startsWith("Bearer ")) {
58 | return authHeader.replace("Bearer ", "");
59 | }
60 |
61 | return null;
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/main/java/com/loizenai/jwtauthentication/security/jwt/JwtProvider.java:
--------------------------------------------------------------------------------
1 | package com.loizenai.jwtauthentication.security.jwt;
2 |
3 | import java.util.Date;
4 |
5 | import org.slf4j.Logger;
6 | import org.slf4j.LoggerFactory;
7 | import org.springframework.beans.factory.annotation.Value;
8 | import org.springframework.security.core.Authentication;
9 | import org.springframework.stereotype.Component;
10 |
11 | import com.loizenai.jwtauthentication.security.services.UserPrinciple;
12 |
13 | import io.jsonwebtoken.ExpiredJwtException;
14 | import io.jsonwebtoken.Jwts;
15 | import io.jsonwebtoken.MalformedJwtException;
16 | import io.jsonwebtoken.SignatureAlgorithm;
17 | import io.jsonwebtoken.SignatureException;
18 | import io.jsonwebtoken.UnsupportedJwtException;
19 |
20 | @Component
21 | public class JwtProvider {
22 |
23 | private static final Logger logger = LoggerFactory.getLogger(JwtProvider.class);
24 |
25 | @Value("${loizenai.app.jwtSecret}")
26 | private String jwtSecret;
27 |
28 | @Value("${loizenai.app.jwtExpiration}")
29 | private int jwtExpiration;
30 |
31 | public String generateJwtToken(Authentication authentication) {
32 |
33 | UserPrinciple userPrincipal = (UserPrinciple) authentication.getPrincipal();
34 |
35 | return Jwts.builder()
36 | .setSubject((userPrincipal.getUsername()))
37 | .setIssuedAt(new Date())
38 | .setExpiration(new Date((new Date()).getTime() + jwtExpiration*1000))
39 | .signWith(SignatureAlgorithm.HS512, jwtSecret)
40 | .compact();
41 | }
42 |
43 | public boolean validateJwtToken(String authToken) {
44 | try {
45 | Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(authToken);
46 | return true;
47 | } catch (SignatureException e) {
48 | logger.error("Invalid JWT signature -> Message: {} ", e);
49 | } catch (MalformedJwtException e) {
50 | logger.error("Invalid JWT token -> Message: {}", e);
51 | } catch (ExpiredJwtException e) {
52 | logger.error("Expired JWT token -> Message: {}", e);
53 | } catch (UnsupportedJwtException e) {
54 | logger.error("Unsupported JWT token -> Message: {}", e);
55 | } catch (IllegalArgumentException e) {
56 | logger.error("JWT claims string is empty -> Message: {}", e);
57 | }
58 |
59 | return false;
60 | }
61 |
62 | public String getUserNameFromJwtToken(String token) {
63 | return Jwts.parser()
64 | .setSigningKey(jwtSecret)
65 | .parseClaimsJws(token)
66 | .getBody().getSubject();
67 | }
68 | }
--------------------------------------------------------------------------------
/src/main/java/com/loizenai/jwtauthentication/security/services/UserDetailsServiceImpl.java:
--------------------------------------------------------------------------------
1 | package com.loizenai.jwtauthentication.security.services;
2 |
3 | import org.springframework.beans.factory.annotation.Autowired;
4 | import org.springframework.security.core.userdetails.UserDetails;
5 | import org.springframework.security.core.userdetails.UserDetailsService;
6 | import org.springframework.security.core.userdetails.UsernameNotFoundException;
7 | import org.springframework.stereotype.Service;
8 | import org.springframework.transaction.annotation.Transactional;
9 |
10 | import com.loizenai.jwtauthentication.model.User;
11 | import com.loizenai.jwtauthentication.repository.UserRepository;
12 |
13 | @Service
14 | public class UserDetailsServiceImpl implements UserDetailsService {
15 |
16 | @Autowired
17 | UserRepository userRepository;
18 |
19 | @Override
20 | @Transactional
21 | public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
22 |
23 | User user = userRepository.findByUsername(username).orElseThrow(
24 | () -> new UsernameNotFoundException("User Not Found with -> username or email : " + username));
25 |
26 | return UserPrinciple.build(user);
27 | }
28 | }
--------------------------------------------------------------------------------
/src/main/java/com/loizenai/jwtauthentication/security/services/UserPrinciple.java:
--------------------------------------------------------------------------------
1 | package com.loizenai.jwtauthentication.security.services;
2 |
3 | import java.util.Collection;
4 | import java.util.List;
5 | import java.util.Objects;
6 | import java.util.stream.Collectors;
7 |
8 | import org.springframework.security.core.GrantedAuthority;
9 | import org.springframework.security.core.authority.SimpleGrantedAuthority;
10 | import org.springframework.security.core.userdetails.UserDetails;
11 |
12 | import com.fasterxml.jackson.annotation.JsonIgnore;
13 | import com.loizenai.jwtauthentication.model.User;
14 |
15 | public class UserPrinciple implements UserDetails {
16 | private static final long serialVersionUID = 1L;
17 |
18 | private Long id;
19 |
20 | private String username;
21 |
22 | private String email;
23 |
24 | @JsonIgnore
25 | private String password;
26 |
27 | private Collection extends GrantedAuthority> authorities;
28 |
29 | public UserPrinciple(Long id, String username, String email, String password,
30 | Collection extends GrantedAuthority> authorities) {
31 | this.id = id;
32 | this.username = username;
33 | this.email = email;
34 | this.password = password;
35 | this.authorities = authorities;
36 | }
37 |
38 | public static UserPrinciple build(User user) {
39 | List authorities = user.getRoles().stream().map(role ->
40 | new SimpleGrantedAuthority(role.getName().name())
41 | ).collect(Collectors.toList());
42 |
43 | return new UserPrinciple(
44 | user.getId(),
45 | user.getUsername(),
46 | user.getEmail(),
47 | user.getPassword(),
48 | authorities
49 | );
50 | }
51 |
52 | public Long getId() {
53 | return id;
54 | }
55 |
56 | public String getEmail() {
57 | return email;
58 | }
59 |
60 | @Override
61 | public String getUsername() {
62 | return username;
63 | }
64 |
65 | @Override
66 | public String getPassword() {
67 | return password;
68 | }
69 |
70 | @Override
71 | public Collection extends GrantedAuthority> getAuthorities() {
72 | return authorities;
73 | }
74 |
75 | @Override
76 | public boolean isAccountNonExpired() {
77 | return true;
78 | }
79 |
80 | @Override
81 | public boolean isAccountNonLocked() {
82 | return true;
83 | }
84 |
85 | @Override
86 | public boolean isCredentialsNonExpired() {
87 | return true;
88 | }
89 |
90 | @Override
91 | public boolean isEnabled() {
92 | return true;
93 | }
94 |
95 | @Override
96 | public boolean equals(Object o) {
97 | if (this == o) return true;
98 | if (o == null || getClass() != o.getClass()) return false;
99 |
100 | UserPrinciple user = (UserPrinciple) o;
101 | return Objects.equals(id, user.id);
102 | }
103 | }
--------------------------------------------------------------------------------
/src/main/resources/application.properties:
--------------------------------------------------------------------------------
1 | spring.datasource.url=jdbc:mysql://localhost:3306/loizenaidb
2 | spring.datasource.username=root
3 | spring.datasource.password=12345
4 | spring.jpa.generate-ddl=true
5 |
6 | spring.jpa.hibernate.ddl-auto=create
7 |
8 | # App Properties
9 | loizenai.app.jwtSecret=jwtLoizenai.comSecretKey
10 | loizenai.app.jwtExpiration=86400
--------------------------------------------------------------------------------
/src/main/resources/roles.sql:
--------------------------------------------------------------------------------
1 | INSERT INTO roles(name) VALUES('ROLE_USER');
2 | INSERT INTO roles(name) VALUES('ROLE_PM');
3 | INSERT INTO roles(name) VALUES('ROLE_ADMIN');
--------------------------------------------------------------------------------
/src/test/java/com/loizenai/jwtauthentication/SpringBootJwtAuthenticationExamplesApplicationTests.java:
--------------------------------------------------------------------------------
1 | package com.loizenai.jwtauthentication;
2 |
3 | import org.junit.jupiter.api.Test;
4 | import org.springframework.boot.test.context.SpringBootTest;
5 |
6 | @SpringBootTest
7 | class SpringBootJwtAuthenticationExamplesApplicationTests {
8 |
9 | @Test
10 | void contextLoads() {
11 | System.out.println("Just Change the Output Test - Good Job");
12 | }
13 | }
--------------------------------------------------------------------------------