├── README.md
├── pom.xml
└── src
├── main
├── java
│ └── com
│ │ └── javatechie
│ │ ├── SpringSecurityLatestApplication.java
│ │ ├── config
│ │ ├── SecurityConfig.java
│ │ ├── UserInfoUserDetails.java
│ │ └── UserInfoUserDetailsService.java
│ │ ├── controller
│ │ └── ProductController.java
│ │ ├── dto
│ │ ├── AuthRequest.java
│ │ └── Product.java
│ │ ├── entity
│ │ └── UserInfo.java
│ │ ├── filter
│ │ └── JwtAuthFilter.java
│ │ ├── repository
│ │ └── UserInfoRepository.java
│ │ └── service
│ │ ├── JwtService.java
│ │ └── ProductService.java
└── resources
│ └── application.properties
└── test
└── java
└── com
└── javatechie
└── SpringSecurityLatestApplicationTests.java
/README.md:
--------------------------------------------------------------------------------
1 | # spring-boot3-jwt
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | 4.0.0
5 |
6 | org.springframework.boot
7 | spring-boot-starter-parent
8 | 3.0.1
9 |
10 |
11 | com.javatechie
12 | spring-security-latest
13 | 0.0.1-SNAPSHOT
14 | spring-security-latest
15 | Demo project for Spring Boot
16 |
17 | 17
18 |
19 |
20 |
21 | org.springframework.boot
22 | spring-boot-starter-security
23 |
24 |
25 | org.springframework.boot
26 | spring-boot-starter-web
27 |
28 |
29 | org.springframework.boot
30 | spring-boot-starter-data-jpa
31 |
32 |
33 | com.mysql
34 | mysql-connector-j
35 | runtime
36 |
37 |
38 | org.projectlombok
39 | lombok
40 | true
41 |
42 |
43 | org.springframework.boot
44 | spring-boot-starter-test
45 | test
46 |
47 |
48 | io.jsonwebtoken
49 | jjwt-api
50 | 0.11.5
51 |
52 |
53 | io.jsonwebtoken
54 | jjwt-impl
55 | 0.11.5
56 |
57 |
58 | io.jsonwebtoken
59 | jjwt-jackson
60 | 0.11.5
61 |
62 |
63 | org.springframework.security
64 | spring-security-test
65 | test
66 |
67 |
68 |
69 |
70 |
71 |
72 | org.springframework.boot
73 | spring-boot-maven-plugin
74 |
75 |
76 |
77 | org.projectlombok
78 | lombok
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
--------------------------------------------------------------------------------
/src/main/java/com/javatechie/SpringSecurityLatestApplication.java:
--------------------------------------------------------------------------------
1 | package com.javatechie;
2 |
3 | import org.springframework.boot.SpringApplication;
4 | import org.springframework.boot.autoconfigure.SpringBootApplication;
5 |
6 | @SpringBootApplication
7 | public class SpringSecurityLatestApplication {
8 |
9 | public static void main(String[] args) {
10 | SpringApplication.run(SpringSecurityLatestApplication.class, args);
11 | }
12 |
13 | }
14 |
--------------------------------------------------------------------------------
/src/main/java/com/javatechie/config/SecurityConfig.java:
--------------------------------------------------------------------------------
1 | package com.javatechie.config;
2 |
3 | import com.javatechie.filter.JwtAuthFilter;
4 | import org.springframework.beans.factory.annotation.Autowired;
5 | import org.springframework.context.annotation.Bean;
6 | import org.springframework.context.annotation.Configuration;
7 | import org.springframework.security.authentication.AuthenticationManager;
8 | import org.springframework.security.authentication.AuthenticationProvider;
9 | import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
10 | import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
11 | import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
12 | import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
13 | import org.springframework.security.config.annotation.web.builders.HttpSecurity;
14 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
15 | import org.springframework.security.config.http.SessionCreationPolicy;
16 | import org.springframework.security.core.userdetails.User;
17 | import org.springframework.security.core.userdetails.UserDetails;
18 | import org.springframework.security.core.userdetails.UserDetailsService;
19 | import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
20 | import org.springframework.security.crypto.password.PasswordEncoder;
21 | import org.springframework.security.provisioning.InMemoryUserDetailsManager;
22 | import org.springframework.security.web.SecurityFilterChain;
23 | import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
24 |
25 | @Configuration
26 | @EnableWebSecurity
27 | @EnableMethodSecurity
28 | public class SecurityConfig {
29 | @Autowired
30 | private JwtAuthFilter authFilter;
31 |
32 | @Bean
33 | //authentication
34 | public UserDetailsService userDetailsService() {
35 | // UserDetails admin = User.withUsername("Basant")
36 | // .password(encoder.encode("Pwd1"))
37 | // .roles("ADMIN")
38 | // .build();
39 | // UserDetails user = User.withUsername("John")
40 | // .password(encoder.encode("Pwd2"))
41 | // .roles("USER","ADMIN","HR")
42 | // .build();
43 | // return new InMemoryUserDetailsManager(admin, user);
44 | return new UserInfoUserDetailsService();
45 | }
46 |
47 | @Bean
48 | public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
49 | return http.csrf().disable()
50 | .authorizeHttpRequests()
51 | .requestMatchers("/products/new","/products/authenticate").permitAll()
52 | .and()
53 | .authorizeHttpRequests().requestMatchers("/products/**")
54 | .authenticated().and()
55 | .sessionManagement()
56 | .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
57 | .and()
58 | .authenticationProvider(authenticationProvider())
59 | .addFilterBefore(authFilter, UsernamePasswordAuthenticationFilter.class)
60 | .build();
61 | }
62 |
63 | @Bean
64 | public PasswordEncoder passwordEncoder() {
65 | return new BCryptPasswordEncoder();
66 | }
67 |
68 | @Bean
69 | public AuthenticationProvider authenticationProvider(){
70 | DaoAuthenticationProvider authenticationProvider=new DaoAuthenticationProvider();
71 | authenticationProvider.setUserDetailsService(userDetailsService());
72 | authenticationProvider.setPasswordEncoder(passwordEncoder());
73 | return authenticationProvider;
74 | }
75 | @Bean
76 | public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
77 | return config.getAuthenticationManager();
78 | }
79 |
80 | }
81 |
--------------------------------------------------------------------------------
/src/main/java/com/javatechie/config/UserInfoUserDetails.java:
--------------------------------------------------------------------------------
1 | package com.javatechie.config;
2 |
3 | import com.javatechie.entity.UserInfo;
4 | import org.springframework.security.core.GrantedAuthority;
5 | import org.springframework.security.core.authority.SimpleGrantedAuthority;
6 | import org.springframework.security.core.userdetails.UserDetails;
7 |
8 | import java.util.Arrays;
9 | import java.util.Collection;
10 | import java.util.List;
11 | import java.util.stream.Collectors;
12 |
13 | public class UserInfoUserDetails implements UserDetails {
14 |
15 |
16 | private String name;
17 | private String password;
18 | private List authorities;
19 |
20 | public UserInfoUserDetails(UserInfo userInfo) {
21 | name=userInfo.getName();
22 | password=userInfo.getPassword();
23 | authorities= Arrays.stream(userInfo.getRoles().split(","))
24 | .map(SimpleGrantedAuthority::new)
25 | .collect(Collectors.toList());
26 | }
27 |
28 | @Override
29 | public Collection extends GrantedAuthority> getAuthorities() {
30 | return authorities;
31 | }
32 |
33 | @Override
34 | public String getPassword() {
35 | return password;
36 | }
37 |
38 | @Override
39 | public String getUsername() {
40 | return name;
41 | }
42 |
43 | @Override
44 | public boolean isAccountNonExpired() {
45 | return true;
46 | }
47 |
48 | @Override
49 | public boolean isAccountNonLocked() {
50 | return true;
51 | }
52 |
53 | @Override
54 | public boolean isCredentialsNonExpired() {
55 | return true;
56 | }
57 |
58 | @Override
59 | public boolean isEnabled() {
60 | return true;
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/main/java/com/javatechie/config/UserInfoUserDetailsService.java:
--------------------------------------------------------------------------------
1 | package com.javatechie.config;
2 |
3 | import com.javatechie.entity.UserInfo;
4 | import com.javatechie.repository.UserInfoRepository;
5 | import org.springframework.beans.factory.annotation.Autowired;
6 | import org.springframework.security.core.userdetails.UserDetails;
7 | import org.springframework.security.core.userdetails.UserDetailsService;
8 | import org.springframework.security.core.userdetails.UsernameNotFoundException;
9 | import org.springframework.stereotype.Component;
10 |
11 | import java.util.Optional;
12 |
13 | @Component
14 | public class UserInfoUserDetailsService implements UserDetailsService {
15 |
16 | @Autowired
17 | private UserInfoRepository repository;
18 |
19 | @Override
20 | public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
21 | Optional userInfo = repository.findByName(username);
22 | return userInfo.map(UserInfoUserDetails::new)
23 | .orElseThrow(() -> new UsernameNotFoundException("user not found " + username));
24 |
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/main/java/com/javatechie/controller/ProductController.java:
--------------------------------------------------------------------------------
1 | package com.javatechie.controller;
2 |
3 | import com.javatechie.dto.AuthRequest;
4 | import com.javatechie.dto.Product;
5 | import com.javatechie.entity.UserInfo;
6 | import com.javatechie.service.JwtService;
7 | import com.javatechie.service.ProductService;
8 | import org.springframework.beans.factory.annotation.Autowired;
9 | import org.springframework.security.access.prepost.PreAuthorize;
10 | import org.springframework.security.authentication.AuthenticationManager;
11 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
12 | import org.springframework.security.core.Authentication;
13 | import org.springframework.security.core.userdetails.UsernameNotFoundException;
14 | import org.springframework.web.bind.annotation.*;
15 |
16 | import java.util.List;
17 |
18 | @RestController
19 | @RequestMapping("/products")
20 | public class ProductController {
21 |
22 | @Autowired
23 | private ProductService service;
24 | @Autowired
25 | private JwtService jwtService;
26 |
27 | @Autowired
28 | private AuthenticationManager authenticationManager;
29 |
30 | @GetMapping("/welcome")
31 | public String welcome() {
32 | return "Welcome this endpoint is not secure";
33 | }
34 |
35 | @PostMapping("/new")
36 | public String addNewUser(@RequestBody UserInfo userInfo) {
37 | return service.addUser(userInfo);
38 | }
39 |
40 | @GetMapping("/all")
41 | @PreAuthorize("hasAuthority('ROLE_ADMIN')")
42 | public List getAllTheProducts() {
43 | return service.getProducts();
44 | }
45 |
46 | @GetMapping("/{id}")
47 | @PreAuthorize("hasAuthority('ROLE_USER')")
48 | public Product getProductById(@PathVariable int id) {
49 | return service.getProduct(id);
50 | }
51 |
52 |
53 | @PostMapping("/authenticate")
54 | public String authenticateAndGetToken(@RequestBody AuthRequest authRequest) {
55 | Authentication authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(authRequest.getUsername(), authRequest.getPassword()));
56 | if (authentication.isAuthenticated()) {
57 | return jwtService.generateToken(authRequest.getUsername());
58 | } else {
59 | throw new UsernameNotFoundException("invalid user request !");
60 | }
61 |
62 |
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/src/main/java/com/javatechie/dto/AuthRequest.java:
--------------------------------------------------------------------------------
1 | package com.javatechie.dto;
2 |
3 | import lombok.AllArgsConstructor;
4 | import lombok.Data;
5 | import lombok.NoArgsConstructor;
6 |
7 | @Data
8 | @AllArgsConstructor
9 | @NoArgsConstructor
10 | public class AuthRequest {
11 |
12 | private String username ;
13 | private String password;
14 | }
15 |
--------------------------------------------------------------------------------
/src/main/java/com/javatechie/dto/Product.java:
--------------------------------------------------------------------------------
1 | package com.javatechie.dto;
2 |
3 | import lombok.AllArgsConstructor;
4 | import lombok.Builder;
5 | import lombok.Data;
6 | import lombok.NoArgsConstructor;
7 |
8 | @Data
9 | @AllArgsConstructor
10 | @NoArgsConstructor
11 | @Builder
12 | public class Product {
13 |
14 | private int productId;
15 | private String name;
16 | private int qty;
17 | private double price;
18 | }
19 |
--------------------------------------------------------------------------------
/src/main/java/com/javatechie/entity/UserInfo.java:
--------------------------------------------------------------------------------
1 | package com.javatechie.entity;
2 |
3 | import jakarta.persistence.Entity;
4 | import jakarta.persistence.GeneratedValue;
5 | import jakarta.persistence.GenerationType;
6 | import jakarta.persistence.Id;
7 | import lombok.AllArgsConstructor;
8 | import lombok.Data;
9 | import lombok.NoArgsConstructor;
10 |
11 | @Entity
12 | @Data
13 | @AllArgsConstructor
14 | @NoArgsConstructor
15 | public class UserInfo {
16 | @Id
17 | @GeneratedValue(strategy = GenerationType.IDENTITY)
18 | private int id;
19 | private String name;
20 | private String email;
21 | private String password;
22 | private String roles;
23 | }
24 |
--------------------------------------------------------------------------------
/src/main/java/com/javatechie/filter/JwtAuthFilter.java:
--------------------------------------------------------------------------------
1 | package com.javatechie.filter;
2 |
3 | import com.javatechie.config.UserInfoUserDetailsService;
4 | import com.javatechie.service.JwtService;
5 | import jakarta.servlet.FilterChain;
6 | import jakarta.servlet.ServletException;
7 | import jakarta.servlet.http.HttpServletRequest;
8 | import jakarta.servlet.http.HttpServletResponse;
9 | import org.aspectj.weaver.patterns.IToken;
10 | import org.springframework.beans.factory.annotation.Autowired;
11 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
12 | import org.springframework.security.core.context.SecurityContextHolder;
13 | import org.springframework.security.core.userdetails.UserDetails;
14 | import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
15 | import org.springframework.stereotype.Component;
16 | import org.springframework.web.filter.OncePerRequestFilter;
17 |
18 | import java.io.IOException;
19 |
20 | @Component
21 | public class JwtAuthFilter extends OncePerRequestFilter {
22 |
23 | @Autowired
24 | private JwtService jwtService;
25 |
26 | @Autowired
27 | private UserInfoUserDetailsService userDetailsService;
28 |
29 | @Override
30 | protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
31 | String authHeader = request.getHeader("Authorization");
32 | String token = null;
33 | String username = null;
34 | if (authHeader != null && authHeader.startsWith("Bearer ")) {
35 | token = authHeader.substring(7);
36 | username = jwtService.extractUsername(token);
37 | }
38 |
39 | if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
40 | UserDetails userDetails = userDetailsService.loadUserByUsername(username);
41 | if (jwtService.validateToken(token, userDetails)) {
42 | UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
43 | authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
44 | SecurityContextHolder.getContext().setAuthentication(authToken);
45 | }
46 | }
47 | filterChain.doFilter(request, response);
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/main/java/com/javatechie/repository/UserInfoRepository.java:
--------------------------------------------------------------------------------
1 | package com.javatechie.repository;
2 |
3 | import com.javatechie.entity.UserInfo;
4 | import org.springframework.data.jpa.repository.JpaRepository;
5 |
6 | import java.util.Optional;
7 |
8 | public interface UserInfoRepository extends JpaRepository {
9 | Optional findByName(String username);
10 |
11 | }
12 |
--------------------------------------------------------------------------------
/src/main/java/com/javatechie/service/JwtService.java:
--------------------------------------------------------------------------------
1 | package com.javatechie.service;
2 |
3 | import io.jsonwebtoken.Claims;
4 | import io.jsonwebtoken.Jwts;
5 | import io.jsonwebtoken.SignatureAlgorithm;
6 | import io.jsonwebtoken.io.Decoder;
7 | import io.jsonwebtoken.io.Decoders;
8 | import io.jsonwebtoken.security.Keys;
9 | import org.springframework.security.core.userdetails.UserDetails;
10 | import org.springframework.stereotype.Component;
11 |
12 | import java.security.Key;
13 | import java.util.Date;
14 | import java.util.HashMap;
15 | import java.util.Map;
16 | import java.util.function.Function;
17 |
18 | @Component
19 | public class JwtService {
20 |
21 |
22 | public static final String SECRET = "5367566B59703373367639792F423F4528482B4D6251655468576D5A71347437";
23 |
24 |
25 | public String extractUsername(String token) {
26 | return extractClaim(token, Claims::getSubject);
27 | }
28 |
29 | public Date extractExpiration(String token) {
30 | return extractClaim(token, Claims::getExpiration);
31 | }
32 |
33 | public T extractClaim(String token, Function claimsResolver) {
34 | final Claims claims = extractAllClaims(token);
35 | return claimsResolver.apply(claims);
36 | }
37 |
38 | private Claims extractAllClaims(String token) {
39 | return Jwts
40 | .parserBuilder()
41 | .setSigningKey(getSignKey())
42 | .build()
43 | .parseClaimsJws(token)
44 | .getBody();
45 | }
46 |
47 | private Boolean isTokenExpired(String token) {
48 | return extractExpiration(token).before(new Date());
49 | }
50 |
51 | public Boolean validateToken(String token, UserDetails userDetails) {
52 | final String username = extractUsername(token);
53 | return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
54 | }
55 |
56 |
57 | public String generateToken(String userName){
58 | Map claims=new HashMap<>();
59 | return createToken(claims,userName);
60 | }
61 |
62 | private String createToken(Map claims, String userName) {
63 | return Jwts.builder()
64 | .setClaims(claims)
65 | .setSubject(userName)
66 | .setIssuedAt(new Date(System.currentTimeMillis()))
67 | .setExpiration(new Date(System.currentTimeMillis()+1000*60*30))
68 | .signWith(getSignKey(), SignatureAlgorithm.HS256).compact();
69 | }
70 |
71 | private Key getSignKey() {
72 | byte[] keyBytes= Decoders.BASE64.decode(SECRET);
73 | return Keys.hmacShaKeyFor(keyBytes);
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/src/main/java/com/javatechie/service/ProductService.java:
--------------------------------------------------------------------------------
1 | package com.javatechie.service;
2 |
3 | import com.javatechie.dto.Product;
4 | import com.javatechie.entity.UserInfo;
5 | import com.javatechie.repository.UserInfoRepository;
6 | import jakarta.annotation.PostConstruct;
7 | import org.springframework.beans.factory.annotation.Autowired;
8 | import org.springframework.security.crypto.password.PasswordEncoder;
9 | import org.springframework.stereotype.Service;
10 |
11 | import java.util.List;
12 | import java.util.Random;
13 | import java.util.stream.Collectors;
14 | import java.util.stream.IntStream;
15 |
16 | @Service
17 | public class ProductService {
18 |
19 | List productList = null;
20 |
21 | @Autowired
22 | private UserInfoRepository repository;
23 |
24 | @Autowired
25 | private PasswordEncoder passwordEncoder;
26 |
27 | @PostConstruct
28 | public void loadProductsFromDB() {
29 | productList = IntStream.rangeClosed(1, 100)
30 | .mapToObj(i -> Product.builder()
31 | .productId(i)
32 | .name("product " + i)
33 | .qty(new Random().nextInt(10))
34 | .price(new Random().nextInt(5000)).build()
35 | ).collect(Collectors.toList());
36 | }
37 |
38 |
39 | public List getProducts() {
40 | return productList;
41 | }
42 |
43 | public Product getProduct(int id) {
44 | return productList.stream()
45 | .filter(product -> product.getProductId() == id)
46 | .findAny()
47 | .orElseThrow(() -> new RuntimeException("product " + id + " not found"));
48 | }
49 |
50 |
51 | public String addUser(UserInfo userInfo) {
52 | userInfo.setPassword(passwordEncoder.encode(userInfo.getPassword()));
53 | repository.save(userInfo);
54 | return "user added to system ";
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/main/resources/application.properties:
--------------------------------------------------------------------------------
1 | #spring.security.user.name= Basant
2 | #spring.security.user.password= Pwd1
3 |
4 |
5 | spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
6 | spring.datasource.url = jdbc:mysql://localhost:3306/javatechie
7 | spring.datasource.username = root
8 | spring.datasource.password = Password
9 | spring.jpa.hibernate.ddl-auto = update
10 | spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5Dialect
11 | spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
12 |
13 |
--------------------------------------------------------------------------------
/src/test/java/com/javatechie/SpringSecurityLatestApplicationTests.java:
--------------------------------------------------------------------------------
1 | package com.javatechie;
2 |
3 | import org.junit.jupiter.api.Test;
4 | import org.springframework.boot.test.context.SpringBootTest;
5 |
6 | @SpringBootTest
7 | class SpringSecurityLatestApplicationTests {
8 |
9 | @Test
10 | void contextLoads() {
11 | }
12 |
13 | }
14 |
--------------------------------------------------------------------------------