From 76f7085f87b582bc6572c8f7641c5caa7e8ff71e Mon Sep 17 00:00:00 2001 From: Tien Nguyen Truong Date: Mon, 16 Nov 2020 10:29:56 +0700 Subject: [PATCH 1/8] add references --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index 245d368..7eb7010 100644 --- a/README.md +++ b/README.md @@ -11,8 +11,15 @@ For more detail, please visit: > [Spring Boot + Angular 8 JWT Authentication](https://bezkoder.com/angular-spring-boot-jwt-auth/) +> [Spring Boot + Angular 10 JWT Authentication](https://bezkoder.com/angular-10-spring-boot-jwt-auth/) + > [Spring Boot + React JWT Authentication](https://bezkoder.com/spring-boot-react-jwt-auth/) +Run both Back-end & Front-end in one place: +> [Integrate Angular with Spring Boot Rest API](https://bezkoder.com/integrate-angular-spring-boot/) + +> [Integrate React.js with Spring Boot Rest API](https://bezkoder.com/integrate-reactjs-spring-boot/) + ## Dependency – If you want to use PostgreSQL: ```xml From 3f9ce85a2cc2f838bfa0e1ac1949b0eb3afa055d Mon Sep 17 00:00:00 2001 From: Tien Nguyen Truong Date: Sun, 29 Nov 2020 15:12:56 +0700 Subject: [PATCH 2/8] add references --- README.md | 24 +++++++++++++++++- pom.xml | 6 ++--- ...ntication-spring-security-architecture.png | Bin 0 -> 25362 bytes ...wt-authentication-spring-security-flow.png | Bin 0 -> 27417 bytes src/main/resources/application.properties | 13 +++------- 5 files changed, 30 insertions(+), 13 deletions(-) create mode 100644 spring-boot-jwt-authentication-spring-security-architecture.png create mode 100644 spring-boot-jwt-authentication-spring-security-flow.png diff --git a/README.md b/README.md index 7eb7010..e8977ec 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,21 @@ # Spring Boot JWT Authentication example with Spring Security & Spring Data JPA +## User Registration, User Login and Authorization process. +The diagram shows flow of how we implement User Registration, User Login and Authorization process. + +![spring-boot-jwt-authentication-spring-security-flow](spring-boot-jwt-authentication-spring-security-flow.png) + +## Spring Boot Server Architecture with Spring Security +You can have an overview of our Spring Boot Server with the diagram below: + +![spring-boot-jwt-authentication-spring-security-architecture](spring-boot-jwt-authentication-spring-security-architecture.png) + For more detail, please visit: > [Secure Spring Boot App with Spring Security & JWT Authentication](https://bezkoder.com/spring-boot-jwt-authentication/) > [For MongoDB](https://bezkoder.com/spring-boot-jwt-auth-mongodb/) -# Fullstack +## Fullstack Authentication > [Spring Boot + Vue.js JWT Authentication](https://bezkoder.com/spring-boot-vue-js-authentication-jwt-spring-security/) @@ -15,6 +25,18 @@ For more detail, please visit: > [Spring Boot + React JWT Authentication](https://bezkoder.com/spring-boot-react-jwt-auth/) +## Fullstack CRUD App + +> [Vue.js + Spring Boot + MySQL/PostgreSQL example](https://bezkoder.com/spring-boot-vue-js-crud-example/) + +> [Angular + Spring Boot + MySQL example](https://bezkoder.com/angular-10-spring-boot-crud/) + +> [Angular + Spring Boot + PostgreSQL example](https://bezkoder.com/angular-10-spring-boot-postgresql/) + +> [React + Spring Boot + MySQL example](https://bezkoder.com/react-spring-boot-crud/) + +> [React + Spring Boot + PostgreSQL example](https://bezkoder.com/spring-boot-react-postgresql/) + Run both Back-end & Front-end in one place: > [Integrate Angular with Spring Boot Rest API](https://bezkoder.com/integrate-angular-spring-boot/) diff --git a/pom.xml b/pom.xml index 048d6d3..907ac6a 100644 --- a/pom.xml +++ b/pom.xml @@ -36,8 +36,8 @@ - org.postgresql - postgresql + mysql + mysql-connector-java runtime @@ -52,7 +52,7 @@ spring-boot-starter-test test - + org.springframework.security spring-security-test diff --git a/spring-boot-jwt-authentication-spring-security-architecture.png b/spring-boot-jwt-authentication-spring-security-architecture.png new file mode 100644 index 0000000000000000000000000000000000000000..b238cb3b050b101bf27bb95f69daffd9a813b9c2 GIT binary patch literal 25362 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ˈwD(K?DG&&m?tG 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
    u(zQn$2zt7lqDsZ%0vp+xu#Li z3Oa4>nMxmtZcVJY)wuct`^Do6AEL*bsCR7(vMyg!>)bemU1gq$* z#@3`QE>2jYX1uj-vyN*uwfn%91G>FF9pQHEe&FlV_^l>I8Wl%e5lia+hp}vXsJ3+nWiXm)_ zS$<|AzGDWGw`%;{DF}J%PYg_BmzH|O&(1oh4@-Ad+>zM!2*KqV+eP3uCD{ZlsBxJ= z^Ww?D8BV9&nkhkBedgJ7z6rqxvt@&zJVNqo6KfZvl*s23bx@5+VOI|YJ&JLMRxURg zQu_LNABI5U!`ch%r)y%FhC(KJKX=xp%-ykc_qTD@35wxlhGHx_-`_KUY24n}O%(y_ zG_D=})T8X=BfQ*g0EoI+P8vRRFn|rMLeJcfSmeh!Pp4vN zSr!yrwaoL=M?|7TJhxQUbeH@?wxKUT7h}Hq$Wqg>2G8D(Mkg}LzF=c>^iM42n!2Bc z^GgT%p!j9zy|AqtE|be$<*6$DlxntREw$S>@N^7<+&}D6q@g7X-m;uCamjM-^^>Nc zv{pqs=y`GxCbFj{Q~5FKjeNwuQ8$5%w7AOg-qAe+XDCG)!QKIot6te^Z zN%}!jIG7Y>AQS!?oW$L0j~Kd~LoRrz+;H-ph`>Qs4uuO~d#R`{7w+@nKT_3@6bv^x zwZH2mI_i%!u>;9Rq}>e(Bq>3dS#v;*KSp2MF!}e}mq*&aqcXkx4EOF$=WX^nK4eQV zu?M{#xp|FEV3?WM?3!9-uxa8W#Krar9-vlzR4OOx;9T%2t&j^SJA=V*tvM4YlvOy@ zQ({Y84pnE=L@Fa)Af=#OPrEgX;4sAaOG?cb{>BAR!)%Nt2Q=T699*fD#kB#$oDzRL zPduDkG$8>n2#AvwF!)%0)ww}Qtw{Y8w;73(jEFq=rW;r%3d29B6MbxLj5V?Lx0~-x zcE^K$!qAh0sq_^1_E#Iqi2AjbgUz*y&?CokbR=XVEAERrQ0hPnm^kk0PE8zG@dsEV zCIgm)FRK~S%_o@JS41V;m>@JMKcN$@2bF|6FK>S`T6u1JiC*Lq`mB`!w)^#Ti)7vC z$an<@&Li1NIrLrJ09dbmDaL(J5|TuriD1SOwXFN{;^$}+(fX5CX*{gbrst{%KgfIy zfeop=PS=MHwRp~&1u3Pl?V|0K!4@M z$7lWm67ERR5@>(G7M-Euhr3R~cM`!X9j7q=ph9X>c=nH{AEwmIu`_X=%%(q_lBqOq z0Zw%-(P#g74LF8z2!DNwh%jAv10As|?pbbs?VC^w2CG8&P5E9Y`j;%`IL` z0!izg@Y?;WZEn^0t&!(dqo4Gec%b(ZqNDh&V{JX2Dw>;G~ec82*H`t*r_@(nao($PpF`2Xo9Nmu#j hVgHw#?|-hJoU25eUB1vVXGJ=&hKi1Ixzhco{|}$I3Ss~N literal 0 HcmV?d00001 diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index eea124f..d02fe7a 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,13 +1,8 @@ -## Spring DATASOURCE (DataSourceAutoConfiguration & DataSourceProperties) -spring.datasource.url= jdbc:postgresql://localhost:5432/testdb -spring.datasource.username= postgres -spring.datasource.password= 123 +spring.datasource.url= jdbc:mysql://localhost:3306/testdb?useSSL=false +spring.datasource.username= root +spring.datasource.password= 123456 -spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation= true -# The SQL dialect makes Hibernate generate better SQL for the chosen database -spring.jpa.properties.hibernate.dialect= org.hibernate.dialect.PostgreSQLDialect - -# Hibernate ddl auto (create, create-drop, validate, update) +spring.jpa.properties.hibernate.dialect= org.hibernate.dialect.MySQL5InnoDBDialect spring.jpa.hibernate.ddl-auto= update # App Properties From 7628a6b60bb69fdcc2661e255a8d103827be0f1c Mon Sep 17 00:00:00 2001 From: Tien Nguyen Truong Date: Sun, 13 Dec 2020 17:05:21 +0700 Subject: [PATCH 3/8] add references --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index e8977ec..15d1cf1 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,8 @@ For more detail, please visit: > [Spring Boot + Angular 10 JWT Authentication](https://bezkoder.com/angular-10-spring-boot-jwt-auth/) +> [Spring Boot + Angular 11 JWT Authentication](https://bezkoder.com/angular-11-spring-boot-jwt-auth/) + > [Spring Boot + React JWT Authentication](https://bezkoder.com/spring-boot-react-jwt-auth/) ## Fullstack CRUD App From 3c425d7a6f7f07d6c14823796f70e73b582ca102 Mon Sep 17 00:00:00 2001 From: Tien Nguyen Truong Date: Wed, 21 Apr 2021 07:32:13 +0700 Subject: [PATCH 4/8] add references --- README.md | 33 ++++++++++++++++-- ...ng-boot-refresh-token-jwt-example-flow.png | Bin 0 -> 30571 bytes 2 files changed, 31 insertions(+), 2 deletions(-) create mode 100644 spring-boot-refresh-token-jwt-example-flow.png diff --git a/README.md b/README.md index 15d1cf1..1e099d4 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,12 @@ For more detail, please visit: > [For MongoDB](https://bezkoder.com/spring-boot-jwt-auth-mongodb/) +## Refresh Token + +![spring-boot-refresh-token-jwt-example-flow](spring-boot-refresh-token-jwt-example-flow.png) + +For instruction: [Spring Boot Refresh Token with JWT example](https://bezkoder.com/spring-boot-refresh-token-jwt/) + ## Fullstack Authentication > [Spring Boot + Vue.js JWT Authentication](https://bezkoder.com/spring-boot-vue-js-authentication-jwt-spring-security/) @@ -31,19 +37,42 @@ For more detail, please visit: > [Vue.js + Spring Boot + MySQL/PostgreSQL example](https://bezkoder.com/spring-boot-vue-js-crud-example/) -> [Angular + Spring Boot + MySQL example](https://bezkoder.com/angular-10-spring-boot-crud/) +> [Angular 8 + Spring Boot + MySQL example](https://bezkoder.com/angular-spring-boot-crud/) + +> [Angular 8 + Spring Boot + PostgreSQL example](https://bezkoder.com/angular-spring-boot-postgresql/) -> [Angular + Spring Boot + PostgreSQL example](https://bezkoder.com/angular-10-spring-boot-postgresql/) +> [Angular 10 + Spring Boot + MySQL example](https://bezkoder.com/angular-10-spring-boot-crud/) + +> [Angular 10 + Spring Boot + PostgreSQL example](https://bezkoder.com/angular-10-spring-boot-postgresql/) + +> [Angular 11 + Spring Boot + MySQL example](https://bezkoder.com/angular-11-spring-boot-crud/) + +> [Angular 11 + Spring Boot + PostgreSQL example](https://bezkoder.com/angular-11-spring-boot-postgresql/) > [React + Spring Boot + MySQL example](https://bezkoder.com/react-spring-boot-crud/) > [React + Spring Boot + PostgreSQL example](https://bezkoder.com/spring-boot-react-postgresql/) +> [React + Spring Boot + MongoDB example](https://bezkoder.com/react-spring-boot-mongodb/) + Run both Back-end & Front-end in one place: > [Integrate Angular with Spring Boot Rest API](https://bezkoder.com/integrate-angular-spring-boot/) > [Integrate React.js with Spring Boot Rest API](https://bezkoder.com/integrate-reactjs-spring-boot/) +> [Integrate Vue.js with Spring Boot Rest API](https://bezkoder.com/integrate-vue-spring-boot/) + +More Practice: +> [Spring Boot File upload example with Multipart File](https://bezkoder.com/spring-boot-file-upload/) + +> [Exception handling: @RestControllerAdvice example in Spring Boot](https://bezkoder.com/spring-boot-restcontrolleradvice/) + +> [Spring Boot Repository Unit Test with @DataJpaTest](https://bezkoder.com/spring-boot-unit-test-jpa-repo-datajpatest/) + +> [Deploy Spring Boot App on AWS – Elastic Beanstalk](https://bezkoder.com/deploy-spring-boot-aws-eb/) + +> [Secure Spring Boot App with Spring Security & JWT Authentication](https://bezkoder.com/spring-boot-jwt-authentication/) + ## Dependency – If you want to use PostgreSQL: ```xml diff --git a/spring-boot-refresh-token-jwt-example-flow.png b/spring-boot-refresh-token-jwt-example-flow.png new file mode 100644 index 0000000000000000000000000000000000000000..5f3d258a17cb2f9b9ea743822f2661c03e860f84 GIT binary patch literal 30571 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 Date: Sat, 11 Dec 2021 12:13:08 +0700 Subject: [PATCH 5/8] update to spring 2.6.1 --- README.md | 27 ++- pom.xml | 7 +- .../SpringBootSecurityJwtApplication.java | 2 +- .../springjwt/controllers/AuthController.java | 180 +++++++++--------- .../springjwt/controllers/TestController.java | 40 ++-- .../com/bezkoder/springjwt/models/ERole.java | 6 +- .../com/bezkoder/springjwt/models/Role.java | 46 ++--- .../com/bezkoder/springjwt/models/User.java | 152 +++++++-------- .../payload/request/LoginRequest.java | 2 +- .../payload/request/SignupRequest.java | 94 ++++----- .../payload/response/JwtResponse.java | 92 ++++----- .../payload/response/MessageResponse.java | 20 +- .../springjwt/repository/RoleRepository.java | 2 +- .../springjwt/repository/UserRepository.java | 6 +- .../springjwt/security/WebSecurityConfig.java | 68 +++---- .../security/jwt/AuthEntryPointJwt.java | 30 ++- .../security/jwt/AuthTokenFilter.java | 63 +++--- .../springjwt/security/jwt/JwtUtils.java | 84 ++++---- .../security/services/UserDetailsImpl.java | 174 ++++++++--------- .../services/UserDetailsServiceImpl.java | 18 +- ...SpringBootSecurityJwtApplicationTests.java | 5 +- 21 files changed, 580 insertions(+), 538 deletions(-) diff --git a/README.md b/README.md index 1e099d4..1d95520 100644 --- a/README.md +++ b/README.md @@ -31,11 +31,19 @@ For instruction: [Spring Boot Refresh Token with JWT example](https://bezkoder.c > [Spring Boot + Angular 11 JWT Authentication](https://bezkoder.com/angular-11-spring-boot-jwt-auth/) +> [Spring Boot + Angular 12 JWT Authentication](https://www.bezkoder.com/angular-12-spring-boot-jwt-auth/) + > [Spring Boot + React JWT Authentication](https://bezkoder.com/spring-boot-react-jwt-auth/) ## Fullstack CRUD App -> [Vue.js + Spring Boot + MySQL/PostgreSQL example](https://bezkoder.com/spring-boot-vue-js-crud-example/) +> [Vue.js + Spring Boot + H2 Embedded database example](https://www.bezkoder.com/spring-boot-vue-js-crud-example/) + +> [Vue.js + Spring Boot + MySQL example](https://www.bezkoder.com/spring-boot-vue-js-mysql/) + +> [Vue.js + Spring Boot + PostgreSQL example](https://www.bezkoder.com/spring-boot-vue-js-postgresql/) + +> [Angular 8 + Spring Boot + Embedded database example](https://www.bezkoder.com/angular-spring-boot-crud/) > [Angular 8 + Spring Boot + MySQL example](https://bezkoder.com/angular-spring-boot-crud/) @@ -49,6 +57,18 @@ For instruction: [Spring Boot Refresh Token with JWT example](https://bezkoder.c > [Angular 11 + Spring Boot + PostgreSQL example](https://bezkoder.com/angular-11-spring-boot-postgresql/) +> [Angular 12 + Spring Boot + Embedded database example](https://www.bezkoder.com/angular-12-spring-boot-crud/) + +> [Angular 12 + Spring Boot + MySQL example](https://www.bezkoder.com/angular-12-spring-boot-mysql/) + +> [Angular 12 + Spring Boot + PostgreSQL example](https://www.bezkoder.com/angular-12-spring-boot-postgresql/) + +> [Angular 13 + Spring Boot + H2 Embedded Database example](https://www.bezkoder.com/spring-boot-angular-13-crud/) + +> [Angular 13 + Spring Boot + MySQL example](https://www.bezkoder.com/spring-boot-angular-13-mysql/) + +> [Angular 13 + Spring Boot + PostgreSQL example](https://www.bezkoder.com/spring-boot-angular-13-postgresql/) + > [React + Spring Boot + MySQL example](https://bezkoder.com/react-spring-boot-crud/) > [React + Spring Boot + PostgreSQL example](https://bezkoder.com/spring-boot-react-postgresql/) @@ -69,9 +89,10 @@ More Practice: > [Spring Boot Repository Unit Test with @DataJpaTest](https://bezkoder.com/spring-boot-unit-test-jpa-repo-datajpatest/) -> [Deploy Spring Boot App on AWS – Elastic Beanstalk](https://bezkoder.com/deploy-spring-boot-aws-eb/) +Deployment: +> [Deploy Spring Boot App on AWS – Elastic Beanstalk](https://www.bezkoder.com/deploy-spring-boot-aws-eb/) -> [Secure Spring Boot App with Spring Security & JWT Authentication](https://bezkoder.com/spring-boot-jwt-authentication/) +> [Docker Compose Spring Boot and MySQL example](https://www.bezkoder.com/docker-compose-spring-boot-mysql/) ## Dependency – If you want to use PostgreSQL: diff --git a/pom.xml b/pom.xml index 907ac6a..eed808f 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ org.springframework.boot spring-boot-starter-parent - 2.1.8.RELEASE + 2.6.1 com.bezkoder @@ -29,6 +29,11 @@ org.springframework.boot spring-boot-starter-security + + + org.springframework.boot + spring-boot-starter-validation + org.springframework.boot diff --git a/src/main/java/com/bezkoder/springjwt/SpringBootSecurityJwtApplication.java b/src/main/java/com/bezkoder/springjwt/SpringBootSecurityJwtApplication.java index d13abd7..c723518 100644 --- a/src/main/java/com/bezkoder/springjwt/SpringBootSecurityJwtApplication.java +++ b/src/main/java/com/bezkoder/springjwt/SpringBootSecurityJwtApplication.java @@ -7,7 +7,7 @@ public class SpringBootSecurityJwtApplication { public static void main(String[] args) { - SpringApplication.run(SpringBootSecurityJwtApplication.class, args); + SpringApplication.run(SpringBootSecurityJwtApplication.class, args); } } diff --git a/src/main/java/com/bezkoder/springjwt/controllers/AuthController.java b/src/main/java/com/bezkoder/springjwt/controllers/AuthController.java index e598702..4287ba1 100644 --- a/src/main/java/com/bezkoder/springjwt/controllers/AuthController.java +++ b/src/main/java/com/bezkoder/springjwt/controllers/AuthController.java @@ -36,94 +36,94 @@ @RestController @RequestMapping("/api/auth") public class AuthController { - @Autowired - AuthenticationManager authenticationManager; - - @Autowired - UserRepository userRepository; - - @Autowired - RoleRepository roleRepository; - - @Autowired - PasswordEncoder encoder; - - @Autowired - JwtUtils jwtUtils; - - @PostMapping("/signin") - public ResponseEntity authenticateUser(@Valid @RequestBody LoginRequest loginRequest) { - - Authentication authentication = authenticationManager.authenticate( - new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getPassword())); - - SecurityContextHolder.getContext().setAuthentication(authentication); - String jwt = jwtUtils.generateJwtToken(authentication); - - UserDetailsImpl userDetails = (UserDetailsImpl) authentication.getPrincipal(); - List roles = userDetails.getAuthorities().stream() - .map(item -> item.getAuthority()) - .collect(Collectors.toList()); - - return ResponseEntity.ok(new JwtResponse(jwt, - userDetails.getId(), - userDetails.getUsername(), - userDetails.getEmail(), - roles)); - } - - @PostMapping("/signup") - public ResponseEntity registerUser(@Valid @RequestBody SignupRequest signUpRequest) { - if (userRepository.existsByUsername(signUpRequest.getUsername())) { - return ResponseEntity - .badRequest() - .body(new MessageResponse("Error: Username is already taken!")); - } - - if (userRepository.existsByEmail(signUpRequest.getEmail())) { - return ResponseEntity - .badRequest() - .body(new MessageResponse("Error: Email is already in use!")); - } - - // Create new user's account - User user = new User(signUpRequest.getUsername(), - signUpRequest.getEmail(), - encoder.encode(signUpRequest.getPassword())); - - Set strRoles = signUpRequest.getRole(); - Set roles = new HashSet<>(); - - if (strRoles == null) { - Role userRole = roleRepository.findByName(ERole.ROLE_USER) - .orElseThrow(() -> new RuntimeException("Error: Role is not found.")); - roles.add(userRole); - } else { - strRoles.forEach(role -> { - switch (role) { - case "admin": - Role adminRole = roleRepository.findByName(ERole.ROLE_ADMIN) - .orElseThrow(() -> new RuntimeException("Error: Role is not found.")); - roles.add(adminRole); - - break; - case "mod": - Role modRole = roleRepository.findByName(ERole.ROLE_MODERATOR) - .orElseThrow(() -> new RuntimeException("Error: Role is not found.")); - roles.add(modRole); - - break; - default: - Role userRole = roleRepository.findByName(ERole.ROLE_USER) - .orElseThrow(() -> new RuntimeException("Error: Role is not found.")); - roles.add(userRole); - } - }); - } - - user.setRoles(roles); - userRepository.save(user); - - return ResponseEntity.ok(new MessageResponse("User registered successfully!")); - } + @Autowired + AuthenticationManager authenticationManager; + + @Autowired + UserRepository userRepository; + + @Autowired + RoleRepository roleRepository; + + @Autowired + PasswordEncoder encoder; + + @Autowired + JwtUtils jwtUtils; + + @PostMapping("/signin") + public ResponseEntity authenticateUser(@Valid @RequestBody LoginRequest loginRequest) { + + Authentication authentication = authenticationManager.authenticate( + new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getPassword())); + + SecurityContextHolder.getContext().setAuthentication(authentication); + String jwt = jwtUtils.generateJwtToken(authentication); + + UserDetailsImpl userDetails = (UserDetailsImpl) authentication.getPrincipal(); + List roles = userDetails.getAuthorities().stream() + .map(item -> item.getAuthority()) + .collect(Collectors.toList()); + + return ResponseEntity.ok(new JwtResponse(jwt, + userDetails.getId(), + userDetails.getUsername(), + userDetails.getEmail(), + roles)); + } + + @PostMapping("/signup") + public ResponseEntity registerUser(@Valid @RequestBody SignupRequest signUpRequest) { + if (userRepository.existsByUsername(signUpRequest.getUsername())) { + return ResponseEntity + .badRequest() + .body(new MessageResponse("Error: Username is already taken!")); + } + + if (userRepository.existsByEmail(signUpRequest.getEmail())) { + return ResponseEntity + .badRequest() + .body(new MessageResponse("Error: Email is already in use!")); + } + + // Create new user's account + User user = new User(signUpRequest.getUsername(), + signUpRequest.getEmail(), + encoder.encode(signUpRequest.getPassword())); + + Set strRoles = signUpRequest.getRole(); + Set roles = new HashSet<>(); + + if (strRoles == null) { + Role userRole = roleRepository.findByName(ERole.ROLE_USER) + .orElseThrow(() -> new RuntimeException("Error: Role is not found.")); + roles.add(userRole); + } else { + strRoles.forEach(role -> { + switch (role) { + case "admin": + Role adminRole = roleRepository.findByName(ERole.ROLE_ADMIN) + .orElseThrow(() -> new RuntimeException("Error: Role is not found.")); + roles.add(adminRole); + + break; + case "mod": + Role modRole = roleRepository.findByName(ERole.ROLE_MODERATOR) + .orElseThrow(() -> new RuntimeException("Error: Role is not found.")); + roles.add(modRole); + + break; + default: + Role userRole = roleRepository.findByName(ERole.ROLE_USER) + .orElseThrow(() -> new RuntimeException("Error: Role is not found.")); + roles.add(userRole); + } + }); + } + + user.setRoles(roles); + userRepository.save(user); + + return ResponseEntity.ok(new MessageResponse("User registered successfully!")); + } } diff --git a/src/main/java/com/bezkoder/springjwt/controllers/TestController.java b/src/main/java/com/bezkoder/springjwt/controllers/TestController.java index 298a7ea..e03b5cb 100644 --- a/src/main/java/com/bezkoder/springjwt/controllers/TestController.java +++ b/src/main/java/com/bezkoder/springjwt/controllers/TestController.java @@ -10,26 +10,26 @@ @RestController @RequestMapping("/api/test") public class TestController { - @GetMapping("/all") - public String allAccess() { - return "Public Content."; - } - - @GetMapping("/user") - @PreAuthorize("hasRole('USER') or hasRole('MODERATOR') or hasRole('ADMIN')") - public String userAccess() { - return "User Content."; - } + @GetMapping("/all") + public String allAccess() { + return "Public Content."; + } - @GetMapping("/mod") - @PreAuthorize("hasRole('MODERATOR')") - public String moderatorAccess() { - return "Moderator Board."; - } + @GetMapping("/user") + @PreAuthorize("hasRole('USER') or hasRole('MODERATOR') or hasRole('ADMIN')") + public String userAccess() { + return "User Content."; + } - @GetMapping("/admin") - @PreAuthorize("hasRole('ADMIN')") - public String adminAccess() { - return "Admin Board."; - } + @GetMapping("/mod") + @PreAuthorize("hasRole('MODERATOR')") + public String moderatorAccess() { + return "Moderator Board."; + } + + @GetMapping("/admin") + @PreAuthorize("hasRole('ADMIN')") + public String adminAccess() { + return "Admin Board."; + } } diff --git a/src/main/java/com/bezkoder/springjwt/models/ERole.java b/src/main/java/com/bezkoder/springjwt/models/ERole.java index 9acf4a4..a113c68 100644 --- a/src/main/java/com/bezkoder/springjwt/models/ERole.java +++ b/src/main/java/com/bezkoder/springjwt/models/ERole.java @@ -1,7 +1,7 @@ package com.bezkoder.springjwt.models; public enum ERole { - ROLE_USER, - ROLE_MODERATOR, - ROLE_ADMIN + ROLE_USER, + ROLE_MODERATOR, + ROLE_ADMIN } diff --git a/src/main/java/com/bezkoder/springjwt/models/Role.java b/src/main/java/com/bezkoder/springjwt/models/Role.java index 44b82ad..abf816e 100644 --- a/src/main/java/com/bezkoder/springjwt/models/Role.java +++ b/src/main/java/com/bezkoder/springjwt/models/Role.java @@ -5,35 +5,35 @@ @Entity @Table(name = "roles") public class Role { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Integer id; + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Integer id; - @Enumerated(EnumType.STRING) - @Column(length = 20) - private ERole name; + @Enumerated(EnumType.STRING) + @Column(length = 20) + private ERole name; - public Role() { + public Role() { - } + } - public Role(ERole name) { - this.name = name; - } + public Role(ERole name) { + this.name = name; + } - public Integer getId() { - return id; - } + public Integer getId() { + return id; + } - public void setId(Integer id) { - this.id = id; - } + public void setId(Integer id) { + this.id = id; + } - public ERole getName() { - return name; - } + public ERole getName() { + return name; + } - public void setName(ERole name) { - this.name = name; - } + public void setName(ERole name) { + this.name = name; + } } \ No newline at end of file diff --git a/src/main/java/com/bezkoder/springjwt/models/User.java b/src/main/java/com/bezkoder/springjwt/models/User.java index 6e3eaa2..9f03ab1 100644 --- a/src/main/java/com/bezkoder/springjwt/models/User.java +++ b/src/main/java/com/bezkoder/springjwt/models/User.java @@ -9,81 +9,81 @@ import javax.validation.constraints.Size; @Entity -@Table( name = "users", - uniqueConstraints = { - @UniqueConstraint(columnNames = "username"), - @UniqueConstraint(columnNames = "email") - }) +@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 roles = new HashSet<>(); - - public User() { - } - - public User(String username, String email, String password) { - this.username = username; - this.email = email; - this.password = password; - } - - public Long getId() { - return id; - } - - public void setId(Long id) { - this.id = id; - } - - public String getUsername() { - return username; - } - - public void setUsername(String username) { - this.username = username; - } - - public String getEmail() { - return email; - } - - public void setEmail(String email) { - this.email = email; - } - - public String getPassword() { - return password; - } - - public void setPassword(String password) { - this.password = password; - } - - public Set getRoles() { - return roles; - } - - public void setRoles(Set roles) { - this.roles = roles; - } + @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 roles = new HashSet<>(); + + public User() { + } + + public User(String username, String email, String password) { + this.username = username; + this.email = email; + this.password = password; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public Set getRoles() { + return roles; + } + + public void setRoles(Set roles) { + this.roles = roles; + } } diff --git a/src/main/java/com/bezkoder/springjwt/payload/request/LoginRequest.java b/src/main/java/com/bezkoder/springjwt/payload/request/LoginRequest.java index e0e7f9f..8b6bedf 100644 --- a/src/main/java/com/bezkoder/springjwt/payload/request/LoginRequest.java +++ b/src/main/java/com/bezkoder/springjwt/payload/request/LoginRequest.java @@ -4,7 +4,7 @@ public class LoginRequest { @NotBlank - private String username; + private String username; @NotBlank private String password; diff --git a/src/main/java/com/bezkoder/springjwt/payload/request/SignupRequest.java b/src/main/java/com/bezkoder/springjwt/payload/request/SignupRequest.java index 515e0a5..899f199 100644 --- a/src/main/java/com/bezkoder/springjwt/payload/request/SignupRequest.java +++ b/src/main/java/com/bezkoder/springjwt/payload/request/SignupRequest.java @@ -3,52 +3,52 @@ import java.util.Set; import javax.validation.constraints.*; - + public class SignupRequest { - @NotBlank - @Size(min = 3, max = 20) - private String username; - - @NotBlank - @Size(max = 50) - @Email - private String email; - - private Set role; - - @NotBlank - @Size(min = 6, max = 40) - private String password; - - public String getUsername() { - return username; - } - - public void setUsername(String username) { - this.username = username; - } - - public String getEmail() { - return email; - } - - public void setEmail(String email) { - this.email = email; - } - - public String getPassword() { - return password; - } - - public void setPassword(String password) { - this.password = password; - } - - public Set getRole() { - return this.role; - } - - public void setRole(Set role) { - this.role = role; - } + @NotBlank + @Size(min = 3, max = 20) + private String username; + + @NotBlank + @Size(max = 50) + @Email + private String email; + + private Set role; + + @NotBlank + @Size(min = 6, max = 40) + private String password; + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public Set getRole() { + return this.role; + } + + public void setRole(Set role) { + this.role = role; + } } diff --git a/src/main/java/com/bezkoder/springjwt/payload/response/JwtResponse.java b/src/main/java/com/bezkoder/springjwt/payload/response/JwtResponse.java index 35ae5a4..ac387f0 100644 --- a/src/main/java/com/bezkoder/springjwt/payload/response/JwtResponse.java +++ b/src/main/java/com/bezkoder/springjwt/payload/response/JwtResponse.java @@ -3,62 +3,62 @@ import java.util.List; public class JwtResponse { - private String token; - private String type = "Bearer"; - private Long id; - private String username; - private String email; - private List roles; + private String token; + private String type = "Bearer"; + private Long id; + private String username; + private String email; + private List roles; - public JwtResponse(String accessToken, Long id, String username, String email, List roles) { - this.token = accessToken; - this.id = id; - this.username = username; - this.email = email; - this.roles = roles; - } + public JwtResponse(String accessToken, Long id, String username, String email, List roles) { + this.token = accessToken; + this.id = id; + this.username = username; + this.email = email; + this.roles = roles; + } - public String getAccessToken() { - return token; - } + public String getAccessToken() { + return token; + } - public void setAccessToken(String accessToken) { - this.token = accessToken; - } + public void setAccessToken(String accessToken) { + this.token = accessToken; + } - public String getTokenType() { - return type; - } + public String getTokenType() { + return type; + } - public void setTokenType(String tokenType) { - this.type = tokenType; - } + public void setTokenType(String tokenType) { + this.type = tokenType; + } - public Long getId() { - return id; - } + public Long getId() { + return id; + } - public void setId(Long id) { - this.id = id; - } + public void setId(Long id) { + this.id = id; + } - public String getEmail() { - return email; - } + public String getEmail() { + return email; + } - public void setEmail(String email) { - this.email = email; - } + public void setEmail(String email) { + this.email = email; + } - public String getUsername() { - return username; - } + public String getUsername() { + return username; + } - public void setUsername(String username) { - this.username = username; - } + public void setUsername(String username) { + this.username = username; + } - public List getRoles() { - return roles; - } + public List getRoles() { + return roles; + } } diff --git a/src/main/java/com/bezkoder/springjwt/payload/response/MessageResponse.java b/src/main/java/com/bezkoder/springjwt/payload/response/MessageResponse.java index 4a3a59f..068e179 100644 --- a/src/main/java/com/bezkoder/springjwt/payload/response/MessageResponse.java +++ b/src/main/java/com/bezkoder/springjwt/payload/response/MessageResponse.java @@ -1,17 +1,17 @@ package com.bezkoder.springjwt.payload.response; public class MessageResponse { - private String message; + private String message; - public MessageResponse(String message) { - this.message = message; - } + public MessageResponse(String message) { + this.message = message; + } - public String getMessage() { - return message; - } + public String getMessage() { + return message; + } - public void setMessage(String message) { - this.message = message; - } + public void setMessage(String message) { + this.message = message; + } } diff --git a/src/main/java/com/bezkoder/springjwt/repository/RoleRepository.java b/src/main/java/com/bezkoder/springjwt/repository/RoleRepository.java index 06aa459..a1882d0 100644 --- a/src/main/java/com/bezkoder/springjwt/repository/RoleRepository.java +++ b/src/main/java/com/bezkoder/springjwt/repository/RoleRepository.java @@ -10,5 +10,5 @@ @Repository public interface RoleRepository extends JpaRepository { - Optional findByName(ERole name); + Optional findByName(ERole name); } diff --git a/src/main/java/com/bezkoder/springjwt/repository/UserRepository.java b/src/main/java/com/bezkoder/springjwt/repository/UserRepository.java index 74f2df9..d48a98c 100644 --- a/src/main/java/com/bezkoder/springjwt/repository/UserRepository.java +++ b/src/main/java/com/bezkoder/springjwt/repository/UserRepository.java @@ -9,9 +9,9 @@ @Repository public interface UserRepository extends JpaRepository { - Optional findByUsername(String username); + Optional findByUsername(String username); - Boolean existsByUsername(String username); + Boolean existsByUsername(String username); - Boolean existsByEmail(String email); + Boolean existsByEmail(String email); } diff --git a/src/main/java/com/bezkoder/springjwt/security/WebSecurityConfig.java b/src/main/java/com/bezkoder/springjwt/security/WebSecurityConfig.java index 405cf6b..b73080d 100644 --- a/src/main/java/com/bezkoder/springjwt/security/WebSecurityConfig.java +++ b/src/main/java/com/bezkoder/springjwt/security/WebSecurityConfig.java @@ -21,46 +21,46 @@ @Configuration @EnableWebSecurity @EnableGlobalMethodSecurity( - // securedEnabled = true, - // jsr250Enabled = true, - prePostEnabled = true) + // securedEnabled = true, + // jsr250Enabled = true, + prePostEnabled = true) public class WebSecurityConfig extends WebSecurityConfigurerAdapter { - @Autowired - UserDetailsServiceImpl userDetailsService; + @Autowired + UserDetailsServiceImpl userDetailsService; - @Autowired - private AuthEntryPointJwt unauthorizedHandler; + @Autowired + private AuthEntryPointJwt unauthorizedHandler; - @Bean - public AuthTokenFilter authenticationJwtTokenFilter() { - return new AuthTokenFilter(); - } + @Bean + public AuthTokenFilter authenticationJwtTokenFilter() { + return new AuthTokenFilter(); + } - @Override - public void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception { - authenticationManagerBuilder.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder()); - } + @Override + public void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception { + authenticationManagerBuilder.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder()); + } - @Bean - @Override - public AuthenticationManager authenticationManagerBean() throws Exception { - return super.authenticationManagerBean(); - } + @Bean + @Override + public AuthenticationManager authenticationManagerBean() throws Exception { + return super.authenticationManagerBean(); + } - @Bean - public PasswordEncoder passwordEncoder() { - return new BCryptPasswordEncoder(); - } + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } - @Override - protected void configure(HttpSecurity http) throws Exception { - http.cors().and().csrf().disable() - .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and() - .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and() - .authorizeRequests().antMatchers("/api/auth/**").permitAll() - .antMatchers("/api/test/**").permitAll() - .anyRequest().authenticated(); + @Override + protected void configure(HttpSecurity http) throws Exception { + http.cors().and().csrf().disable() + .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and() + .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and() + .authorizeRequests().antMatchers("/api/auth/**").permitAll() + .antMatchers("/api/test/**").permitAll() + .anyRequest().authenticated(); - http.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class); - } + http.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class); + } } diff --git a/src/main/java/com/bezkoder/springjwt/security/jwt/AuthEntryPointJwt.java b/src/main/java/com/bezkoder/springjwt/security/jwt/AuthEntryPointJwt.java index b7e2647..903f6a8 100644 --- a/src/main/java/com/bezkoder/springjwt/security/jwt/AuthEntryPointJwt.java +++ b/src/main/java/com/bezkoder/springjwt/security/jwt/AuthEntryPointJwt.java @@ -1,6 +1,8 @@ package com.bezkoder.springjwt.security.jwt; import java.io.IOException; +import java.util.HashMap; +import java.util.Map; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; @@ -8,20 +10,34 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.http.MediaType; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.stereotype.Component; +import com.fasterxml.jackson.databind.ObjectMapper; + @Component public class AuthEntryPointJwt implements AuthenticationEntryPoint { - private static final Logger logger = LoggerFactory.getLogger(AuthEntryPointJwt.class); + private static final Logger logger = LoggerFactory.getLogger(AuthEntryPointJwt.class); + + @Override + public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) + throws IOException, ServletException { + logger.error("Unauthorized error: {}", authException.getMessage()); + + response.setContentType(MediaType.APPLICATION_JSON_VALUE); + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + + final Map body = new HashMap<>(); + body.put("status", HttpServletResponse.SC_UNAUTHORIZED); + body.put("error", "Unauthorized"); + body.put("message", authException.getMessage()); + body.put("path", request.getServletPath()); - @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"); - } + final ObjectMapper mapper = new ObjectMapper(); + mapper.writeValue(response.getOutputStream(), body); + } } diff --git a/src/main/java/com/bezkoder/springjwt/security/jwt/AuthTokenFilter.java b/src/main/java/com/bezkoder/springjwt/security/jwt/AuthTokenFilter.java index 4a906a8..2b3b6d3 100644 --- a/src/main/java/com/bezkoder/springjwt/security/jwt/AuthTokenFilter.java +++ b/src/main/java/com/bezkoder/springjwt/security/jwt/AuthTokenFilter.java @@ -20,43 +20,46 @@ import com.bezkoder.springjwt.security.services.UserDetailsServiceImpl; public class AuthTokenFilter extends OncePerRequestFilter { - @Autowired - private JwtUtils jwtUtils; + @Autowired + private JwtUtils jwtUtils; - @Autowired - private UserDetailsServiceImpl userDetailsService; + @Autowired + private UserDetailsServiceImpl userDetailsService; - private static final Logger logger = LoggerFactory.getLogger(AuthTokenFilter.class); + private static final Logger logger = LoggerFactory.getLogger(AuthTokenFilter.class); - @Override - protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) - throws ServletException, IOException { - try { - String jwt = parseJwt(request); - if (jwt != null && jwtUtils.validateJwtToken(jwt)) { - String username = jwtUtils.getUserNameFromJwtToken(jwt); + @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)); + 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); - } + SecurityContextHolder.getContext().setAuthentication(authentication); + } + } catch (Exception e) { + logger.error("Cannot set user authentication: {}", e); + } - filterChain.doFilter(request, response); - } + filterChain.doFilter(request, response); + } - private String parseJwt(HttpServletRequest request) { - String headerAuth = request.getHeader("Authorization"); + private String parseJwt(HttpServletRequest request) { + String headerAuth = request.getHeader("Authorization"); - if (StringUtils.hasText(headerAuth) && headerAuth.startsWith("Bearer ")) { - return headerAuth.substring(7, headerAuth.length()); - } + if (StringUtils.hasText(headerAuth) && headerAuth.startsWith("Bearer ")) { + return headerAuth.substring(7, headerAuth.length()); + } - return null; - } + return null; + } } diff --git a/src/main/java/com/bezkoder/springjwt/security/jwt/JwtUtils.java b/src/main/java/com/bezkoder/springjwt/security/jwt/JwtUtils.java index 400b563..00c8157 100644 --- a/src/main/java/com/bezkoder/springjwt/security/jwt/JwtUtils.java +++ b/src/main/java/com/bezkoder/springjwt/security/jwt/JwtUtils.java @@ -13,46 +13,46 @@ @Component public class JwtUtils { - private static final Logger logger = LoggerFactory.getLogger(JwtUtils.class); - - @Value("${bezkoder.app.jwtSecret}") - private String jwtSecret; - - @Value("${bezkoder.app.jwtExpirationMs}") - private int jwtExpirationMs; - - public String generateJwtToken(Authentication authentication) { - - UserDetailsImpl userPrincipal = (UserDetailsImpl) authentication.getPrincipal(); - - return Jwts.builder() - .setSubject((userPrincipal.getUsername())) - .setIssuedAt(new Date()) - .setExpiration(new Date((new Date()).getTime() + jwtExpirationMs)) - .signWith(SignatureAlgorithm.HS512, jwtSecret) - .compact(); - } - - public String getUserNameFromJwtToken(String token) { - return Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(token).getBody().getSubject(); - } - - public boolean validateJwtToken(String authToken) { - try { - Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(authToken); - return true; - } catch (SignatureException e) { - logger.error("Invalid JWT signature: {}", e.getMessage()); - } catch (MalformedJwtException e) { - logger.error("Invalid JWT token: {}", e.getMessage()); - } catch (ExpiredJwtException e) { - logger.error("JWT token is expired: {}", e.getMessage()); - } catch (UnsupportedJwtException e) { - logger.error("JWT token is unsupported: {}", e.getMessage()); - } catch (IllegalArgumentException e) { - logger.error("JWT claims string is empty: {}", e.getMessage()); - } - - return false; - } + private static final Logger logger = LoggerFactory.getLogger(JwtUtils.class); + + @Value("${bezkoder.app.jwtSecret}") + private String jwtSecret; + + @Value("${bezkoder.app.jwtExpirationMs}") + private int jwtExpirationMs; + + public String generateJwtToken(Authentication authentication) { + + UserDetailsImpl userPrincipal = (UserDetailsImpl) authentication.getPrincipal(); + + return Jwts.builder() + .setSubject((userPrincipal.getUsername())) + .setIssuedAt(new Date()) + .setExpiration(new Date((new Date()).getTime() + jwtExpirationMs)) + .signWith(SignatureAlgorithm.HS512, jwtSecret) + .compact(); + } + + public String getUserNameFromJwtToken(String token) { + return Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(token).getBody().getSubject(); + } + + public boolean validateJwtToken(String authToken) { + try { + Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(authToken); + return true; + } catch (SignatureException e) { + logger.error("Invalid JWT signature: {}", e.getMessage()); + } catch (MalformedJwtException e) { + logger.error("Invalid JWT token: {}", e.getMessage()); + } catch (ExpiredJwtException e) { + logger.error("JWT token is expired: {}", e.getMessage()); + } catch (UnsupportedJwtException e) { + logger.error("JWT token is unsupported: {}", e.getMessage()); + } catch (IllegalArgumentException e) { + logger.error("JWT claims string is empty: {}", e.getMessage()); + } + + return false; + } } diff --git a/src/main/java/com/bezkoder/springjwt/security/services/UserDetailsImpl.java b/src/main/java/com/bezkoder/springjwt/security/services/UserDetailsImpl.java index fafc5a9..6452f34 100644 --- a/src/main/java/com/bezkoder/springjwt/security/services/UserDetailsImpl.java +++ b/src/main/java/com/bezkoder/springjwt/security/services/UserDetailsImpl.java @@ -13,91 +13,91 @@ import com.fasterxml.jackson.annotation.JsonIgnore; public class UserDetailsImpl implements UserDetails { - private static final long serialVersionUID = 1L; - - private Long id; - - private String username; - - private String email; - - @JsonIgnore - private String password; - - private Collection authorities; - - public UserDetailsImpl(Long id, String username, String email, String password, - Collection authorities) { - this.id = id; - this.username = username; - this.email = email; - this.password = password; - this.authorities = authorities; - } - - public static UserDetailsImpl build(User user) { - List authorities = user.getRoles().stream() - .map(role -> new SimpleGrantedAuthority(role.getName().name())) - .collect(Collectors.toList()); - - return new UserDetailsImpl( - user.getId(), - user.getUsername(), - user.getEmail(), - user.getPassword(), - authorities); - } - - @Override - public Collection getAuthorities() { - return authorities; - } - - public Long getId() { - return id; - } - - public String getEmail() { - return email; - } - - @Override - public String getPassword() { - return password; - } - - @Override - public String getUsername() { - return username; - } - - @Override - public boolean isAccountNonExpired() { - return true; - } - - @Override - public boolean isAccountNonLocked() { - return true; - } - - @Override - public boolean isCredentialsNonExpired() { - return true; - } - - @Override - public boolean isEnabled() { - return true; - } - - @Override - public boolean equals(Object o) { - if (this == o) - return true; - if (o == null || getClass() != o.getClass()) - return false; - UserDetailsImpl user = (UserDetailsImpl) o; - return Objects.equals(id, user.id); - } + private static final long serialVersionUID = 1L; + + private Long id; + + private String username; + + private String email; + + @JsonIgnore + private String password; + + private Collection authorities; + + public UserDetailsImpl(Long id, String username, String email, String password, + Collection authorities) { + this.id = id; + this.username = username; + this.email = email; + this.password = password; + this.authorities = authorities; + } + + public static UserDetailsImpl build(User user) { + List authorities = user.getRoles().stream() + .map(role -> new SimpleGrantedAuthority(role.getName().name())) + .collect(Collectors.toList()); + + return new UserDetailsImpl( + user.getId(), + user.getUsername(), + user.getEmail(), + user.getPassword(), + authorities); + } + + @Override + public Collection getAuthorities() { + return authorities; + } + + public Long getId() { + return id; + } + + public String getEmail() { + return email; + } + + @Override + public String getPassword() { + return password; + } + + @Override + public String getUsername() { + return username; + } + + @Override + public boolean isAccountNonExpired() { + return true; + } + + @Override + public boolean isAccountNonLocked() { + return true; + } + + @Override + public boolean isCredentialsNonExpired() { + return true; + } + + @Override + public boolean isEnabled() { + return true; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + UserDetailsImpl user = (UserDetailsImpl) o; + return Objects.equals(id, user.id); + } } diff --git a/src/main/java/com/bezkoder/springjwt/security/services/UserDetailsServiceImpl.java b/src/main/java/com/bezkoder/springjwt/security/services/UserDetailsServiceImpl.java index 9a3ed41..de65d7e 100644 --- a/src/main/java/com/bezkoder/springjwt/security/services/UserDetailsServiceImpl.java +++ b/src/main/java/com/bezkoder/springjwt/security/services/UserDetailsServiceImpl.java @@ -12,16 +12,16 @@ @Service public class UserDetailsServiceImpl implements UserDetailsService { - @Autowired - UserRepository userRepository; + @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)); + @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); - } + return UserDetailsImpl.build(user); + } } diff --git a/src/test/java/com/bezkoder/springjwt/SpringBootSecurityJwtApplicationTests.java b/src/test/java/com/bezkoder/springjwt/SpringBootSecurityJwtApplicationTests.java index 60fe0c3..42ffdd0 100644 --- a/src/test/java/com/bezkoder/springjwt/SpringBootSecurityJwtApplicationTests.java +++ b/src/test/java/com/bezkoder/springjwt/SpringBootSecurityJwtApplicationTests.java @@ -1,11 +1,8 @@ package com.bezkoder.springjwt; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.junit4.SpringRunner; -@RunWith(SpringRunner.class) @SpringBootTest public class SpringBootSecurityJwtApplicationTests { From edaf3082e0444cbd6e8e3dd9d8d3fe9b939af84f Mon Sep 17 00:00:00 2001 From: tienbku Date: Fri, 9 Sep 2022 15:27:08 +0700 Subject: [PATCH 6/8] update WebSecurityConfig for new Spring Boot version --- README.md | 43 ++++++++---- pom.xml | 2 +- .../springjwt/security/WebSecurityConfig.java | 69 ++++++++++++++----- 3 files changed, 83 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index 1d95520..d0d856a 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,27 @@ For more detail, please visit: For instruction: [Spring Boot Refresh Token with JWT example](https://bezkoder.com/spring-boot-refresh-token-jwt/) +## More Practice: +> [Spring Boot File upload example with Multipart File](https://bezkoder.com/spring-boot-file-upload/) + +> [Exception handling: @RestControllerAdvice example in Spring Boot](https://bezkoder.com/spring-boot-restcontrolleradvice/) + +> [Spring Boot Repository Unit Test with @DataJpaTest](https://bezkoder.com/spring-boot-unit-test-jpa-repo-datajpatest/) + +> [Spring Boot Pagination & Sorting example](https://www.bezkoder.com/spring-boot-pagination-sorting-example/) + +Associations: +> [Spring Boot One To Many example with Spring JPA, Hibernate](https://www.bezkoder.com/jpa-one-to-many/) + +> [Spring Boot Many To Many example with Spring JPA, Hibernate](https://www.bezkoder.com/jpa-many-to-many/) + +> [JPA One To One example with Spring Boot](https://www.bezkoder.com/jpa-one-to-one/) + +Deployment: +> [Deploy Spring Boot App on AWS – Elastic Beanstalk](https://www.bezkoder.com/deploy-spring-boot-aws-eb/) + +> [Docker Compose Spring Boot and MySQL example](https://www.bezkoder.com/docker-compose-spring-boot-mysql/) + ## Fullstack Authentication > [Spring Boot + Vue.js JWT Authentication](https://bezkoder.com/spring-boot-vue-js-authentication-jwt-spring-security/) @@ -33,6 +54,10 @@ For instruction: [Spring Boot Refresh Token with JWT example](https://bezkoder.c > [Spring Boot + Angular 12 JWT Authentication](https://www.bezkoder.com/angular-12-spring-boot-jwt-auth/) +> [Spring Boot + Angular 13 JWT Authentication](https://www.bezkoder.com/angular-13-spring-boot-jwt-auth/) + +> [Spring Boot + Angular 14 JWT Authentication](https://www.bezkoder.com/angular-14-spring-boot-jwt-auth/) + > [Spring Boot + React JWT Authentication](https://bezkoder.com/spring-boot-react-jwt-auth/) ## Fullstack CRUD App @@ -69,6 +94,12 @@ For instruction: [Spring Boot Refresh Token with JWT example](https://bezkoder.c > [Angular 13 + Spring Boot + PostgreSQL example](https://www.bezkoder.com/spring-boot-angular-13-postgresql/) +> [Angular 14 + Spring Boot + H2 Embedded Database example](https://www.bezkoder.com/spring-boot-angular-14-crud/) + +> [Angular 14 + Spring Boot + MySQL example](https://www.bezkoder.com/spring-boot-angular-14-mysql/) + +> [Angular 14 + Spring Boot + PostgreSQL example](https://www.bezkoder.com/spring-boot-angular-14-postgresql/) + > [React + Spring Boot + MySQL example](https://bezkoder.com/react-spring-boot-crud/) > [React + Spring Boot + PostgreSQL example](https://bezkoder.com/spring-boot-react-postgresql/) @@ -82,18 +113,6 @@ Run both Back-end & Front-end in one place: > [Integrate Vue.js with Spring Boot Rest API](https://bezkoder.com/integrate-vue-spring-boot/) -More Practice: -> [Spring Boot File upload example with Multipart File](https://bezkoder.com/spring-boot-file-upload/) - -> [Exception handling: @RestControllerAdvice example in Spring Boot](https://bezkoder.com/spring-boot-restcontrolleradvice/) - -> [Spring Boot Repository Unit Test with @DataJpaTest](https://bezkoder.com/spring-boot-unit-test-jpa-repo-datajpatest/) - -Deployment: -> [Deploy Spring Boot App on AWS – Elastic Beanstalk](https://www.bezkoder.com/deploy-spring-boot-aws-eb/) - -> [Docker Compose Spring Boot and MySQL example](https://www.bezkoder.com/docker-compose-spring-boot-mysql/) - ## Dependency – If you want to use PostgreSQL: ```xml diff --git a/pom.xml b/pom.xml index eed808f..b39061d 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ org.springframework.boot spring-boot-starter-parent - 2.6.1 + 2.7.3 com.bezkoder diff --git a/src/main/java/com/bezkoder/springjwt/security/WebSecurityConfig.java b/src/main/java/com/bezkoder/springjwt/security/WebSecurityConfig.java index b73080d..3114f6b 100644 --- a/src/main/java/com/bezkoder/springjwt/security/WebSecurityConfig.java +++ b/src/main/java/com/bezkoder/springjwt/security/WebSecurityConfig.java @@ -4,14 +4,17 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.authentication.dao.DaoAuthenticationProvider; +//import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +//import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +//import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import com.bezkoder.springjwt.security.jwt.AuthEntryPointJwt; @@ -19,12 +22,11 @@ import com.bezkoder.springjwt.security.services.UserDetailsServiceImpl; @Configuration -@EnableWebSecurity @EnableGlobalMethodSecurity( // securedEnabled = true, // jsr250Enabled = true, prePostEnabled = true) -public class WebSecurityConfig extends WebSecurityConfigurerAdapter { +public class WebSecurityConfig { // extends WebSecurityConfigurerAdapter { @Autowired UserDetailsServiceImpl userDetailsService; @@ -36,15 +38,30 @@ public AuthTokenFilter authenticationJwtTokenFilter() { return new AuthTokenFilter(); } - @Override - public void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception { - authenticationManagerBuilder.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder()); +// @Override +// public void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception { +// authenticationManagerBuilder.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder()); +// } + + @Bean + public DaoAuthenticationProvider authenticationProvider() { + DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider(); + + authProvider.setUserDetailsService(userDetailsService); + authProvider.setPasswordEncoder(passwordEncoder()); + + return authProvider; } +// @Bean +// @Override +// public AuthenticationManager authenticationManagerBean() throws Exception { +// return super.authenticationManagerBean(); +// } + @Bean - @Override - public AuthenticationManager authenticationManagerBean() throws Exception { - return super.authenticationManagerBean(); + public AuthenticationManager authenticationManager(AuthenticationConfiguration authConfig) throws Exception { + return authConfig.getAuthenticationManager(); } @Bean @@ -52,15 +69,31 @@ public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } - @Override - protected void configure(HttpSecurity http) throws Exception { +// @Override +// protected void configure(HttpSecurity http) throws Exception { +// http.cors().and().csrf().disable() +// .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and() +// .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and() +// .authorizeRequests().antMatchers("/api/auth/**").permitAll() +// .antMatchers("/api/test/**").permitAll() +// .anyRequest().authenticated(); +// +// http.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class); +// } + + @Bean + public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http.cors().and().csrf().disable() - .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and() - .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and() - .authorizeRequests().antMatchers("/api/auth/**").permitAll() - .antMatchers("/api/test/**").permitAll() - .anyRequest().authenticated(); + .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and() + .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and() + .authorizeRequests().antMatchers("/api/auth/**").permitAll() + .antMatchers("/api/test/**").permitAll() + .anyRequest().authenticated(); + + http.authenticationProvider(authenticationProvider()); http.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class); + + return http.build(); } } From 5985ea80ec106e226a014bcb89b47c3415c42921 Mon Sep 17 00:00:00 2001 From: tienbku Date: Fri, 26 May 2023 13:52:13 +0700 Subject: [PATCH 7/8] update to Spring Boot 3 --- README.md | 146 ++++++++++-------- pom.xml | 32 ++-- .../springjwt/controllers/AuthController.java | 2 +- .../com/bezkoder/springjwt/models/Role.java | 2 +- .../com/bezkoder/springjwt/models/User.java | 8 +- .../payload/request/LoginRequest.java | 2 +- .../payload/request/SignupRequest.java | 2 +- .../springjwt/security/WebSecurityConfig.java | 24 +-- .../security/jwt/AuthEntryPointJwt.java | 6 +- .../security/jwt/AuthTokenFilter.java | 10 +- .../springjwt/security/jwt/JwtUtils.java | 16 +- src/main/resources/application.properties | 14 +- 12 files changed, 155 insertions(+), 109 deletions(-) diff --git a/README.md b/README.md index d0d856a..269d3aa 100644 --- a/README.md +++ b/README.md @@ -10,8 +10,68 @@ You can have an overview of our Spring Boot Server with the diagram below: ![spring-boot-jwt-authentication-spring-security-architecture](spring-boot-jwt-authentication-spring-security-architecture.png) +## Dependency +– If you want to use PostgreSQL: +```xml + + org.postgresql + postgresql + runtime + +``` +– or MySQL: +```xml + + com.mysql + mysql-connector-j + runtime + +``` +## Configure Spring Datasource, JPA, App properties +Open `src/main/resources/application.properties` +- 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 + +# Hibernate ddl auto (create, create-drop, validate, update) +spring.jpa.hibernate.ddl-auto= update + +# App Properties +bezkoder.app.jwtSecret= bezKoderSecretKey +bezkoder.app.jwtExpirationMs= 86400000 +``` +- For MySQL +``` +spring.datasource.url=jdbc:mysql://localhost:3306/testdb_spring?useSSL=false +spring.datasource.username=root +spring.datasource.password=123456 + +spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQLDialect +spring.jpa.hibernate.ddl-auto=update + +# App Properties +bezkoder.app.jwtSecret= ======================BezKoder=Spring=========================== +bezkoder.app.jwtExpirationMs=86400000 +``` +## Run Spring Boot application +``` +mvn spring-boot:run +``` + +## Run following SQL insert statements +``` +INSERT INTO roles(name) VALUES('ROLE_USER'); +INSERT INTO roles(name) VALUES('ROLE_MODERATOR'); +INSERT INTO roles(name) VALUES('ROLE_ADMIN'); +``` + For more detail, please visit: -> [Secure Spring Boot App with Spring Security & JWT Authentication](https://bezkoder.com/spring-boot-jwt-authentication/) +> [Secure Spring Boot with Spring Security & JWT Authentication](https://bezkoder.com/spring-boot-jwt-authentication/) > [For MongoDB](https://bezkoder.com/spring-boot-jwt-auth-mongodb/) @@ -22,6 +82,8 @@ For more detail, please visit: For instruction: [Spring Boot Refresh Token with JWT example](https://bezkoder.com/spring-boot-refresh-token-jwt/) ## More Practice: +> [Spring Boot JWT Authentication example using HttpOnly Cookie](https://www.bezkoder.com/spring-boot-login-example-mysql/) + > [Spring Boot File upload example with Multipart File](https://bezkoder.com/spring-boot-file-upload/) > [Exception handling: @RestControllerAdvice example in Spring Boot](https://bezkoder.com/spring-boot-restcontrolleradvice/) @@ -30,6 +92,12 @@ For instruction: [Spring Boot Refresh Token with JWT example](https://bezkoder.c > [Spring Boot Pagination & Sorting example](https://www.bezkoder.com/spring-boot-pagination-sorting-example/) +> Validation: [Spring Boot Validate Request Body](https://www.bezkoder.com/spring-boot-validate-request-body/) + +> Documentation: [Spring Boot and Swagger 3 example](https://www.bezkoder.com/spring-boot-swagger-3/) + +> Caching: [Spring Boot Redis Cache example](https://www.bezkoder.com/spring-boot-redis-cache-example/) + Associations: > [Spring Boot One To Many example with Spring JPA, Hibernate](https://www.bezkoder.com/jpa-one-to-many/) @@ -58,6 +126,10 @@ Deployment: > [Spring Boot + Angular 14 JWT Authentication](https://www.bezkoder.com/angular-14-spring-boot-jwt-auth/) +> [Spring Boot + Angular 15 JWT Authentication](https://www.bezkoder.com/angular-15-spring-boot-jwt-auth/) + +> [Spring Boot + Angular 16 JWT Authentication](https://www.bezkoder.com/angular-16-spring-boot-jwt-auth/) + > [Spring Boot + React JWT Authentication](https://bezkoder.com/spring-boot-react-jwt-auth/) ## Fullstack CRUD App @@ -100,6 +172,18 @@ Deployment: > [Angular 14 + Spring Boot + PostgreSQL example](https://www.bezkoder.com/spring-boot-angular-14-postgresql/) +> [Angular 15 + Spring Boot + H2 Embedded Database example](https://www.bezkoder.com/spring-boot-angular-15-crud/) + +> [Angular 15 + Spring Boot + MySQL example](https://www.bezkoder.com/spring-boot-angular-15-mysql/) + +> [Angular 15 + Spring Boot + PostgreSQL example](https://www.bezkoder.com/spring-boot-angular-15-postgresql/) + +> [Angular 16 + Spring Boot + H2 Embedded Database example](https://www.bezkoder.com/spring-boot-angular-16-crud/) + +> [Angular 16 + Spring Boot + MySQL example](https://www.bezkoder.com/spring-boot-angular-16-mysql/) + +> [Angular 16 + Spring Boot + PostgreSQL example](https://www.bezkoder.com/spring-boot-angular-16-postgresql/) + > [React + Spring Boot + MySQL example](https://bezkoder.com/react-spring-boot-crud/) > [React + Spring Boot + PostgreSQL example](https://bezkoder.com/spring-boot-react-postgresql/) @@ -112,63 +196,3 @@ Run both Back-end & Front-end in one place: > [Integrate React.js with Spring Boot Rest API](https://bezkoder.com/integrate-reactjs-spring-boot/) > [Integrate Vue.js with Spring Boot Rest API](https://bezkoder.com/integrate-vue-spring-boot/) - -## Dependency -– If you want to use PostgreSQL: -```xml - - org.postgresql - postgresql - runtime - -``` -– or MySQL: -```xml - - mysql - mysql-connector-java - runtime - -``` -## Configure Spring Datasource, JPA, App properties -Open `src/main/resources/application.properties` -- 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 - -# Hibernate ddl auto (create, create-drop, validate, update) -spring.jpa.hibernate.ddl-auto= update - -# 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 -``` -## Run Spring Boot application -``` -mvn spring-boot:run -``` - -## Run following SQL insert statements -``` -INSERT INTO roles(name) VALUES('ROLE_USER'); -INSERT INTO roles(name) VALUES('ROLE_MODERATOR'); -INSERT INTO roles(name) VALUES('ROLE_ADMIN'); -``` diff --git a/pom.xml b/pom.xml index b39061d..23a0aba 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ org.springframework.boot spring-boot-starter-parent - 2.7.3 + 3.1.0 com.bezkoder @@ -16,7 +16,7 @@ Demo project for Spring Boot Security - JWT - 1.8 + 17 @@ -29,10 +29,10 @@ org.springframework.boot spring-boot-starter-security - + - org.springframework.boot - spring-boot-starter-validation + org.springframework.boot + spring-boot-starter-validation @@ -41,15 +41,29 @@ - mysql - mysql-connector-java + com.mysql + mysql-connector-j + runtime + + + + io.jsonwebtoken + jjwt-api + 0.11.5 + + + + io.jsonwebtoken + jjwt-impl + 0.11.5 runtime io.jsonwebtoken - jjwt - 0.9.1 + jjwt-jackson + 0.11.5 + runtime diff --git a/src/main/java/com/bezkoder/springjwt/controllers/AuthController.java b/src/main/java/com/bezkoder/springjwt/controllers/AuthController.java index 4287ba1..eb54879 100644 --- a/src/main/java/com/bezkoder/springjwt/controllers/AuthController.java +++ b/src/main/java/com/bezkoder/springjwt/controllers/AuthController.java @@ -5,7 +5,7 @@ import java.util.Set; import java.util.stream.Collectors; -import javax.validation.Valid; +import jakarta.validation.Valid; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; diff --git a/src/main/java/com/bezkoder/springjwt/models/Role.java b/src/main/java/com/bezkoder/springjwt/models/Role.java index abf816e..8eb847f 100644 --- a/src/main/java/com/bezkoder/springjwt/models/Role.java +++ b/src/main/java/com/bezkoder/springjwt/models/Role.java @@ -1,6 +1,6 @@ package com.bezkoder.springjwt.models; -import javax.persistence.*; +import jakarta.persistence.*; @Entity @Table(name = "roles") diff --git a/src/main/java/com/bezkoder/springjwt/models/User.java b/src/main/java/com/bezkoder/springjwt/models/User.java index 9f03ab1..2ad0c5d 100644 --- a/src/main/java/com/bezkoder/springjwt/models/User.java +++ b/src/main/java/com/bezkoder/springjwt/models/User.java @@ -3,10 +3,10 @@ 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; +import jakarta.persistence.*; +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; @Entity @Table(name = "users", diff --git a/src/main/java/com/bezkoder/springjwt/payload/request/LoginRequest.java b/src/main/java/com/bezkoder/springjwt/payload/request/LoginRequest.java index 8b6bedf..de32475 100644 --- a/src/main/java/com/bezkoder/springjwt/payload/request/LoginRequest.java +++ b/src/main/java/com/bezkoder/springjwt/payload/request/LoginRequest.java @@ -1,6 +1,6 @@ package com.bezkoder.springjwt.payload.request; -import javax.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotBlank; public class LoginRequest { @NotBlank diff --git a/src/main/java/com/bezkoder/springjwt/payload/request/SignupRequest.java b/src/main/java/com/bezkoder/springjwt/payload/request/SignupRequest.java index 899f199..ccbb562 100644 --- a/src/main/java/com/bezkoder/springjwt/payload/request/SignupRequest.java +++ b/src/main/java/com/bezkoder/springjwt/payload/request/SignupRequest.java @@ -2,7 +2,7 @@ import java.util.Set; -import javax.validation.constraints.*; +import jakarta.validation.constraints.*; public class SignupRequest { @NotBlank diff --git a/src/main/java/com/bezkoder/springjwt/security/WebSecurityConfig.java b/src/main/java/com/bezkoder/springjwt/security/WebSecurityConfig.java index 3114f6b..d7565d1 100644 --- a/src/main/java/com/bezkoder/springjwt/security/WebSecurityConfig.java +++ b/src/main/java/com/bezkoder/springjwt/security/WebSecurityConfig.java @@ -7,7 +7,7 @@ import org.springframework.security.authentication.dao.DaoAuthenticationProvider; //import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; -import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; +import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; //import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; //import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; @@ -22,10 +22,10 @@ import com.bezkoder.springjwt.security.services.UserDetailsServiceImpl; @Configuration -@EnableGlobalMethodSecurity( - // securedEnabled = true, - // jsr250Enabled = true, - prePostEnabled = true) +@EnableMethodSecurity +// (securedEnabled = true, +// jsr250Enabled = true, +// prePostEnabled = true) // by default public class WebSecurityConfig { // extends WebSecurityConfigurerAdapter { @Autowired UserDetailsServiceImpl userDetailsService; @@ -83,12 +83,14 @@ public PasswordEncoder passwordEncoder() { @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - http.cors().and().csrf().disable() - .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and() - .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and() - .authorizeRequests().antMatchers("/api/auth/**").permitAll() - .antMatchers("/api/test/**").permitAll() - .anyRequest().authenticated(); + http.csrf(csrf -> csrf.disable()) + .exceptionHandling(exception -> exception.authenticationEntryPoint(unauthorizedHandler)) + .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) + .authorizeHttpRequests(auth -> + auth.requestMatchers("/api/auth/**").permitAll() + .requestMatchers("/api/test/**").permitAll() + .anyRequest().authenticated() + ); http.authenticationProvider(authenticationProvider()); diff --git a/src/main/java/com/bezkoder/springjwt/security/jwt/AuthEntryPointJwt.java b/src/main/java/com/bezkoder/springjwt/security/jwt/AuthEntryPointJwt.java index 903f6a8..232ba13 100644 --- a/src/main/java/com/bezkoder/springjwt/security/jwt/AuthEntryPointJwt.java +++ b/src/main/java/com/bezkoder/springjwt/security/jwt/AuthEntryPointJwt.java @@ -4,9 +4,9 @@ import java.util.HashMap; import java.util.Map; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/src/main/java/com/bezkoder/springjwt/security/jwt/AuthTokenFilter.java b/src/main/java/com/bezkoder/springjwt/security/jwt/AuthTokenFilter.java index 2b3b6d3..f2e31ef 100644 --- a/src/main/java/com/bezkoder/springjwt/security/jwt/AuthTokenFilter.java +++ b/src/main/java/com/bezkoder/springjwt/security/jwt/AuthTokenFilter.java @@ -2,10 +2,10 @@ import java.io.IOException; -import javax.servlet.FilterChain; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -57,7 +57,7 @@ private String parseJwt(HttpServletRequest request) { String headerAuth = request.getHeader("Authorization"); if (StringUtils.hasText(headerAuth) && headerAuth.startsWith("Bearer ")) { - return headerAuth.substring(7, headerAuth.length()); + return headerAuth.substring(7); } return null; diff --git a/src/main/java/com/bezkoder/springjwt/security/jwt/JwtUtils.java b/src/main/java/com/bezkoder/springjwt/security/jwt/JwtUtils.java index 00c8157..a031392 100644 --- a/src/main/java/com/bezkoder/springjwt/security/jwt/JwtUtils.java +++ b/src/main/java/com/bezkoder/springjwt/security/jwt/JwtUtils.java @@ -1,5 +1,6 @@ package com.bezkoder.springjwt.security.jwt; +import java.security.Key; import java.util.Date; import org.slf4j.Logger; @@ -10,6 +11,8 @@ import com.bezkoder.springjwt.security.services.UserDetailsImpl; import io.jsonwebtoken.*; +import io.jsonwebtoken.io.Decoders; +import io.jsonwebtoken.security.Keys; @Component public class JwtUtils { @@ -29,20 +32,23 @@ public String generateJwtToken(Authentication authentication) { .setSubject((userPrincipal.getUsername())) .setIssuedAt(new Date()) .setExpiration(new Date((new Date()).getTime() + jwtExpirationMs)) - .signWith(SignatureAlgorithm.HS512, jwtSecret) + .signWith(key(), SignatureAlgorithm.HS256) .compact(); } + + private Key key() { + return Keys.hmacShaKeyFor(Decoders.BASE64.decode(jwtSecret)); + } public String getUserNameFromJwtToken(String token) { - return Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(token).getBody().getSubject(); + return Jwts.parserBuilder().setSigningKey(key()).build() + .parseClaimsJws(token).getBody().getSubject(); } public boolean validateJwtToken(String authToken) { try { - Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(authToken); + Jwts.parserBuilder().setSigningKey(key()).build().parse(authToken); return true; - } catch (SignatureException e) { - logger.error("Invalid JWT signature: {}", e.getMessage()); } catch (MalformedJwtException e) { logger.error("Invalid JWT token: {}", e.getMessage()); } catch (ExpiredJwtException e) { diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index d02fe7a..481d5ed 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,10 +1,10 @@ -spring.datasource.url= jdbc:mysql://localhost:3306/testdb?useSSL=false -spring.datasource.username= root -spring.datasource.password= 123456 +spring.datasource.url=jdbc:mysql://localhost:3306/testdb_spring?useSSL=false +spring.datasource.username=root +spring.datasource.password=123456 -spring.jpa.properties.hibernate.dialect= org.hibernate.dialect.MySQL5InnoDBDialect -spring.jpa.hibernate.ddl-auto= update +spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQLDialect +spring.jpa.hibernate.ddl-auto=update # App Properties -bezkoder.app.jwtSecret= bezKoderSecretKey -bezkoder.app.jwtExpirationMs= 86400000 \ No newline at end of file +bezkoder.app.jwtSecret= ======================BezKoder=Spring=========================== +bezkoder.app.jwtExpirationMs=86400000 \ No newline at end of file From 096e106a6d807129e49b8b80af1ffc389b0fe3da Mon Sep 17 00:00:00 2001 From: tienbku Date: Sun, 4 Feb 2024 16:04:33 +0700 Subject: [PATCH 8/8] update references --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index 269d3aa..009045e 100644 --- a/README.md +++ b/README.md @@ -130,6 +130,8 @@ Deployment: > [Spring Boot + Angular 16 JWT Authentication](https://www.bezkoder.com/angular-16-spring-boot-jwt-auth/) +> [Spring Boot + Angular 17 JWT Authentication](https://www.bezkoder.com/angular-17-spring-boot-jwt-auth/) + > [Spring Boot + React JWT Authentication](https://bezkoder.com/spring-boot-react-jwt-auth/) ## Fullstack CRUD App @@ -184,6 +186,12 @@ Deployment: > [Angular 16 + Spring Boot + PostgreSQL example](https://www.bezkoder.com/spring-boot-angular-16-postgresql/) +> [Angular 17 + Spring Boot + H2 Embedded Database example](https://www.bezkoder.com/spring-boot-angular-17-crud/) + +> [Angular 17 + Spring Boot + MySQL example](https://www.bezkoder.com/spring-boot-angular-17-mysql/) + +> [Angular 17 + Spring Boot + PostgreSQL example](https://www.bezkoder.com/spring-boot-angular-17-postgresql/) + > [React + Spring Boot + MySQL example](https://bezkoder.com/react-spring-boot-crud/) > [React + Spring Boot + PostgreSQL example](https://bezkoder.com/spring-boot-react-postgresql/)