├── 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 | ![Jwt-springboot-reactjs-token-authentication-example](https://loizenai.com/wp-content/uploads/2020/11/Reactjs-SpringBoot-Jwt-Token-Authentication-Example.png) 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 | ![Overall System Architecture Diagram](https://loizenai.com/wp-content/uploads/2020/11/Reactjs-JWT-Authentication-Overall-Diagram-1.png) 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 | ![JWT Authentication Sequence Diagram](https://loizenai.com/wp-content/uploads/2020/11/Reactjs-Jwt-Authentication-Working-Process-Diagram-1.png) 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 | ![Jwt SpringBoot Token Security RestAPIs Diagram Overview](https://loizenai.com/wp-content/uploads/2020/11/Spring-Security-Jwt-Token-Authentication-Architecture-Diagram-1.png) 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 | ![Home Page](https://loizenai.com/wp-content/uploads/2020/11/Reactjs-Home-Page-3.png) 84 | 85 | – User Register page: 86 | 87 | ![User Register page](https://loizenai.com/wp-content/uploads/2020/11/Reactjs-JWT-Authentication-Register-Form-Validation-1.png) 88 | 89 | – Login Page: 90 | 91 | ![Login Page](https://loizenai.com/wp-content/uploads/2020/11/reactjs-jwt-authentication-wrong-login-user-validation-3.png) 92 | 93 | – Profile Page: 94 | 95 | ![Profile Page](https://loizenai.com/wp-content/uploads/2020/11/Reactjs-jwt-authentication-sign-in-successfully-2.png) 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 | ![Project Manager Page](https://loizenai.com/wp-content/uploads/2020/11/Reactjs-JWT-Authentication-PM-Content-2.png) 104 | 105 | – Reactjs Admin page: 106 | 107 | ![Reactjs Admin page](https://loizenai.com/wp-content/uploads/2020/11/Reactjs-jwt-authentication-admin-page-3.png) 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 authorities; 12 | 13 | public JwtResponse(String accessToken, String username, Collection 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 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 authorities; 28 | 29 | public UserPrinciple(Long id, String username, String email, String password, 30 | Collection 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 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 | } --------------------------------------------------------------------------------