├── 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 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 | --------------------------------------------------------------------------------