├── README.md ├── pom.xml └── src ├── main ├── java │ └── com │ │ └── javachinna │ │ ├── SpringBootRestCrudApplication.java │ │ ├── config │ │ ├── AddApiToSwagger.java │ │ ├── AddMethodToSwagger.java │ │ ├── BasicAuthSecurityConfig.java │ │ ├── JwtAuthSecurityConfig.java │ │ ├── JwtAuthenticationEntryPoint.java │ │ ├── JwtRequestFilter.java │ │ ├── MethodSecurityConfig.java │ │ ├── Profiles.java │ │ ├── SetupDataLoader.java │ │ └── SwaggerConfig.java │ │ ├── controller │ │ ├── JwtAuthenticationController.java │ │ ├── ProductApi.java │ │ └── ProductController.java │ │ ├── exception │ │ └── ResourceNotFoundException.java │ │ ├── model │ │ ├── JwtRequest.java │ │ ├── JwtResponse.java │ │ ├── Product.java │ │ ├── Role.java │ │ └── UserEntity.java │ │ ├── repo │ │ ├── ProductRepository.java │ │ ├── RoleRepository.java │ │ └── UserRepository.java │ │ ├── service │ │ ├── ProductService.java │ │ ├── UserDetailsServiceImpl.java │ │ └── impl │ │ │ └── ProductServiceImpl.java │ │ └── util │ │ └── JwtTokenUtil.java └── resources │ └── application.properties └── test └── java └── com └── javachinna └── rest └── SpringBootRestCrudApplicationTests.java /README.md: -------------------------------------------------------------------------------- 1 | # spring-boot-rest-jwt-auth 2 | Secure Spring Boot 2 REST API with Spring Security 5 JWT Authentication, Role based Authorization and MySQL Database 3 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 2.2.6.RELEASE 9 | 10 | 11 | com.javachinna 12 | spring-boot-rest-crud 13 | 0.0.1-SNAPSHOT 14 | spring-boot-rest-crud 15 | Demo project for Spring Boot REST API CRUD Operations with Swagger Documentation 16 | 17 | 18 | 11 19 | 20 | 21 | 22 | 23 | org.springframework.boot 24 | spring-boot-starter-data-jpa 25 | 26 | 27 | org.springframework.boot 28 | spring-boot-starter-web 29 | 30 | 31 | org.springframework.boot 32 | spring-boot-starter-security 33 | 34 | 35 | org.springframework.boot 36 | spring-boot-devtools 37 | runtime 38 | true 39 | 40 | 41 | mysql 42 | mysql-connector-java 43 | runtime 44 | 45 | 46 | org.projectlombok 47 | lombok 48 | true 49 | 50 | 51 | org.springframework.boot 52 | spring-boot-starter-test 53 | test 54 | 55 | 56 | org.junit.vintage 57 | junit-vintage-engine 58 | 59 | 60 | 61 | 62 | org.springframework.security 63 | spring-security-test 64 | test 65 | 66 | 67 | io.springfox 68 | springfox-swagger2 69 | 2.9.2 70 | 71 | 72 | io.springfox 73 | springfox-swagger-ui 74 | 2.9.2 75 | 76 | 77 | io.jsonwebtoken 78 | jjwt 79 | 0.9.1 80 | 81 | 82 | 83 | 84 | 85 | 86 | org.springframework.boot 87 | spring-boot-maven-plugin 88 | 89 | 90 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /src/main/java/com/javachinna/SpringBootRestCrudApplication.java: -------------------------------------------------------------------------------- 1 | package com.javachinna; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class SpringBootRestCrudApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(SpringBootRestCrudApplication.class, args); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/javachinna/config/AddApiToSwagger.java: -------------------------------------------------------------------------------- 1 | package com.javachinna.config; 2 | 3 | import java.lang.annotation.Documented; 4 | import java.lang.annotation.ElementType; 5 | import java.lang.annotation.Retention; 6 | import java.lang.annotation.RetentionPolicy; 7 | import java.lang.annotation.Target; 8 | 9 | @Documented 10 | @Retention(RetentionPolicy.RUNTIME) 11 | @Target(ElementType.TYPE) 12 | public @interface AddApiToSwagger { 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/javachinna/config/AddMethodToSwagger.java: -------------------------------------------------------------------------------- 1 | package com.javachinna.config; 2 | 3 | import java.lang.annotation.Documented; 4 | import java.lang.annotation.ElementType; 5 | import java.lang.annotation.Retention; 6 | import java.lang.annotation.RetentionPolicy; 7 | import java.lang.annotation.Target; 8 | 9 | @Documented 10 | @Retention(RetentionPolicy.RUNTIME) 11 | @Target(ElementType.METHOD) 12 | public @interface AddMethodToSwagger { 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/javachinna/config/BasicAuthSecurityConfig.java: -------------------------------------------------------------------------------- 1 | package com.javachinna.config; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | import org.springframework.context.annotation.Profile; 7 | import org.springframework.http.HttpMethod; 8 | import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; 9 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 10 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; 11 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; 12 | import org.springframework.security.config.http.SessionCreationPolicy; 13 | import org.springframework.security.core.userdetails.UserDetailsService; 14 | import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; 15 | import org.springframework.security.crypto.password.PasswordEncoder; 16 | 17 | import com.javachinna.model.Role; 18 | 19 | @Profile(Profiles.BASIC_AUTH) 20 | @Configuration 21 | @EnableWebSecurity 22 | public class BasicAuthSecurityConfig extends WebSecurityConfigurerAdapter { 23 | 24 | @Autowired 25 | private UserDetailsService userDetailsService; 26 | 27 | @Autowired 28 | public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { 29 | auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder()); 30 | } 31 | 32 | @Override 33 | protected void configure(HttpSecurity http) throws Exception { 34 | // Disable CSRF 35 | http.csrf().disable() 36 | // Only admin can perform HTTP delete operation 37 | .authorizeRequests().antMatchers(HttpMethod.DELETE).hasRole(Role.ADMIN) 38 | // any authenticated user can perform all other operations 39 | .antMatchers("/products/**").hasAnyRole(Role.ADMIN, Role.USER).and().httpBasic() 40 | // Permit all other request without authentication 41 | .and().authorizeRequests().anyRequest().permitAll() 42 | // We don't need sessions to be created. 43 | .and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); 44 | } 45 | 46 | @Override 47 | public UserDetailsService userDetailsService() { 48 | return userDetailsService; 49 | } 50 | 51 | @Bean 52 | public PasswordEncoder passwordEncoder() { 53 | return new BCryptPasswordEncoder(10); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/com/javachinna/config/JwtAuthSecurityConfig.java: -------------------------------------------------------------------------------- 1 | package com.javachinna.config; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | import org.springframework.context.annotation.Profile; 7 | import org.springframework.http.HttpMethod; 8 | import org.springframework.security.authentication.AuthenticationManager; 9 | import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; 10 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 11 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; 12 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; 13 | import org.springframework.security.config.http.SessionCreationPolicy; 14 | import org.springframework.security.core.userdetails.UserDetailsService; 15 | import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; 16 | import org.springframework.security.crypto.password.PasswordEncoder; 17 | import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; 18 | 19 | import com.javachinna.model.Role; 20 | 21 | import lombok.RequiredArgsConstructor; 22 | 23 | @Profile(Profiles.JWT_AUTH) 24 | @Configuration 25 | @EnableWebSecurity 26 | @RequiredArgsConstructor 27 | public class JwtAuthSecurityConfig extends WebSecurityConfigurerAdapter { 28 | 29 | private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint; 30 | private final UserDetailsService jwtUserDetailsService; 31 | private final JwtRequestFilter jwtRequestFilter; 32 | 33 | @Autowired 34 | public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { 35 | // configure AuthenticationManager so that it knows from where to load 36 | // user for matching credentials 37 | // Use BCryptPasswordEncoder 38 | auth.userDetailsService(jwtUserDetailsService).passwordEncoder(passwordEncoder()); 39 | } 40 | 41 | @Bean 42 | public PasswordEncoder passwordEncoder() { 43 | return new BCryptPasswordEncoder(); 44 | } 45 | 46 | @Bean 47 | @Override 48 | public AuthenticationManager authenticationManagerBean() throws Exception { 49 | return super.authenticationManagerBean(); 50 | } 51 | 52 | @Override 53 | protected void configure(HttpSecurity httpSecurity) throws Exception { 54 | // Disable CSRF 55 | httpSecurity.csrf().disable() 56 | // Only admin can perform HTTP delete operation 57 | .authorizeRequests().antMatchers(HttpMethod.DELETE).hasRole(Role.ADMIN) 58 | // any authenticated user can perform all other operations 59 | .antMatchers("/products/**").hasAnyRole(Role.ADMIN, Role.USER) 60 | // Permit all other request without authentication 61 | .and().authorizeRequests().anyRequest().permitAll() 62 | // Reject every unauthenticated request and send error code 401. 63 | .and().exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint) 64 | // We don't need sessions to be created. 65 | .and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); 66 | 67 | // Add a filter to validate the tokens with every request 68 | httpSecurity.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class); 69 | } 70 | } -------------------------------------------------------------------------------- /src/main/java/com/javachinna/config/JwtAuthenticationEntryPoint.java: -------------------------------------------------------------------------------- 1 | package com.javachinna.config; 2 | 3 | import java.io.IOException; 4 | import java.io.Serializable; 5 | 6 | import javax.servlet.http.HttpServletRequest; 7 | import javax.servlet.http.HttpServletResponse; 8 | 9 | import org.springframework.security.core.AuthenticationException; 10 | import org.springframework.security.web.AuthenticationEntryPoint; 11 | import org.springframework.stereotype.Component; 12 | 13 | @Component 14 | public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint, Serializable { 15 | private static final long serialVersionUID = -7858869558953243875L; 16 | 17 | @Override 18 | public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException { 19 | response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized"); 20 | } 21 | } -------------------------------------------------------------------------------- /src/main/java/com/javachinna/config/JwtRequestFilter.java: -------------------------------------------------------------------------------- 1 | package com.javachinna.config; 2 | 3 | import java.io.IOException; 4 | 5 | import javax.servlet.FilterChain; 6 | import javax.servlet.ServletException; 7 | import javax.servlet.http.HttpServletRequest; 8 | import javax.servlet.http.HttpServletResponse; 9 | 10 | import org.springframework.context.annotation.Profile; 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 com.javachinna.service.UserDetailsServiceImpl; 19 | import com.javachinna.util.JwtTokenUtil; 20 | 21 | import io.jsonwebtoken.ExpiredJwtException; 22 | import lombok.RequiredArgsConstructor; 23 | 24 | @Component 25 | @Profile(Profiles.JWT_AUTH) 26 | @RequiredArgsConstructor 27 | public class JwtRequestFilter extends OncePerRequestFilter { 28 | private final UserDetailsServiceImpl jwtUserDetailsService; 29 | private final JwtTokenUtil jwtTokenUtil; 30 | 31 | @Override 32 | protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException { 33 | final String requestTokenHeader = request.getHeader("Authorization"); 34 | String username = null; 35 | String jwtToken = null; 36 | // JWT Token is in the form "Bearer token". Remove Bearer word and get 37 | // only the Token 38 | if (requestTokenHeader != null) { 39 | if (requestTokenHeader.startsWith("Bearer ")) { 40 | jwtToken = requestTokenHeader.substring(7); 41 | try { 42 | username = jwtTokenUtil.getUsernameFromToken(jwtToken); 43 | } catch (IllegalArgumentException e) { 44 | System.out.println("Unable to get JWT Token"); 45 | } catch (ExpiredJwtException e) { 46 | System.out.println("JWT Token has expired"); 47 | } 48 | } else { 49 | logger.warn("JWT Token does not begin with Bearer String"); 50 | } 51 | } 52 | // Once we get the token validate it. 53 | if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) { 54 | UserDetails userDetails = this.jwtUserDetailsService.loadUserByUsername(username); 55 | // if token is valid configure Spring Security to manually set 56 | // authentication 57 | if (jwtTokenUtil.validateToken(jwtToken, userDetails)) { 58 | UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); 59 | usernamePasswordAuthenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); 60 | // After setting the Authentication in the context, we specify 61 | // that the current user is authenticated. So it passes the 62 | // Spring Security Configurations successfully. 63 | SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken); 64 | } 65 | } 66 | chain.doFilter(request, response); 67 | } 68 | } -------------------------------------------------------------------------------- /src/main/java/com/javachinna/config/MethodSecurityConfig.java: -------------------------------------------------------------------------------- 1 | package com.javachinna.config; 2 | 3 | import org.springframework.context.annotation.Configuration; 4 | import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; 5 | import org.springframework.security.config.annotation.method.configuration.GlobalMethodSecurityConfiguration; 6 | 7 | @Configuration 8 | @EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true) 9 | public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration { 10 | } -------------------------------------------------------------------------------- /src/main/java/com/javachinna/config/Profiles.java: -------------------------------------------------------------------------------- 1 | package com.javachinna.config; 2 | 3 | public class Profiles { 4 | 5 | private Profiles() { 6 | } 7 | 8 | public static final String BASIC_AUTH = "basicauth"; 9 | public static final String JWT_AUTH = "jwtauth"; 10 | 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/javachinna/config/SetupDataLoader.java: -------------------------------------------------------------------------------- 1 | package com.javachinna.config; 2 | 3 | import java.util.Set; 4 | 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.context.ApplicationListener; 7 | import org.springframework.context.event.ContextRefreshedEvent; 8 | import org.springframework.stereotype.Component; 9 | import org.springframework.transaction.annotation.Transactional; 10 | 11 | import com.javachinna.model.Role; 12 | import com.javachinna.model.UserEntity; 13 | import com.javachinna.repo.RoleRepository; 14 | import com.javachinna.repo.UserRepository; 15 | 16 | @Component 17 | public class SetupDataLoader implements ApplicationListener { 18 | 19 | private boolean alreadySetup = false; 20 | 21 | @Autowired 22 | private UserRepository userRepository; 23 | 24 | @Autowired 25 | private RoleRepository roleRepository; 26 | 27 | @Override 28 | @Transactional 29 | public void onApplicationEvent(final ContextRefreshedEvent event) { 30 | if (alreadySetup) { 31 | return; 32 | } 33 | 34 | // Create user roles 35 | var userRole = createRoleIfNotFound(Role.ROLE_USER); 36 | var adminRole = createRoleIfNotFound(Role.ROLE_ADMIN); 37 | 38 | // Create users 39 | createUserIfNotFound("user", userRole); 40 | createUserIfNotFound("admin", adminRole); 41 | 42 | alreadySetup = true; 43 | } 44 | 45 | @Transactional 46 | private final Role createRoleIfNotFound(final String name) { 47 | Role role = roleRepository.findByName(name); 48 | if (role == null) { 49 | role = new Role(name); 50 | role = roleRepository.save(role); 51 | } 52 | return role; 53 | } 54 | 55 | @Transactional 56 | private final UserEntity createUserIfNotFound(final String name, final Role role) { 57 | UserEntity user = userRepository.findByUsername(name); 58 | if (user == null) { 59 | user = new UserEntity(name, "$2a$10$slYQmyNdGzTn7ZLBXBChFOC9f6kFjAqPhccnP6DxlWXx2lPk1C3G6"); 60 | user.setRoles(Set.of(role)); 61 | user = userRepository.save(user); 62 | } 63 | return user; 64 | } 65 | } -------------------------------------------------------------------------------- /src/main/java/com/javachinna/config/SwaggerConfig.java: -------------------------------------------------------------------------------- 1 | package com.javachinna.config; 2 | 3 | import java.util.Collections; 4 | import java.util.List; 5 | 6 | import org.springframework.context.annotation.Bean; 7 | import org.springframework.context.annotation.Configuration; 8 | 9 | import springfox.documentation.builders.PathSelectors; 10 | import springfox.documentation.builders.RequestHandlerSelectors; 11 | import springfox.documentation.service.ApiInfo; 12 | import springfox.documentation.service.ApiKey; 13 | import springfox.documentation.service.AuthorizationScope; 14 | import springfox.documentation.service.BasicAuth; 15 | import springfox.documentation.service.Contact; 16 | import springfox.documentation.service.SecurityReference; 17 | import springfox.documentation.service.SecurityScheme; 18 | import springfox.documentation.spi.DocumentationType; 19 | import springfox.documentation.spi.service.contexts.SecurityContext; 20 | import springfox.documentation.spring.web.plugins.Docket; 21 | import springfox.documentation.swagger2.annotations.EnableSwagger2; 22 | 23 | @Configuration 24 | @EnableSwagger2 25 | public class SwaggerConfig { 26 | private static final String BASIC_AUTH = "basicAuth"; 27 | private static final String BEARER_AUTH = "Bearer"; 28 | 29 | @Bean 30 | public Docket api() { 31 | return new Docket(DocumentationType.SWAGGER_2).select().apis(RequestHandlerSelectors.basePackage("com.javachinna")).paths(PathSelectors.any()).build().apiInfo(apiInfo()) 32 | .securitySchemes(securitySchemes()).securityContexts(List.of(securityContext())); 33 | } 34 | 35 | private ApiInfo apiInfo() { 36 | return new ApiInfo("Product REST API", "Product API to perform CRUD opertations", "1.0", "Terms of service", 37 | new Contact("Java Chinna", "www.javachinna.com", "java4chinna@gmail.com"), "License of API", "API license URL", Collections.emptyList()); 38 | } 39 | 40 | private List securitySchemes() { 41 | return List.of(new BasicAuth(BASIC_AUTH), new ApiKey(BEARER_AUTH, "Authorization", "header")); 42 | } 43 | 44 | private SecurityContext securityContext() { 45 | return SecurityContext.builder().securityReferences(List.of(basicAuthReference(), bearerAuthReference())).forPaths(PathSelectors.ant("/products/**")).build(); 46 | } 47 | 48 | private SecurityReference basicAuthReference() { 49 | return new SecurityReference(BASIC_AUTH, new AuthorizationScope[0]); 50 | } 51 | 52 | private SecurityReference bearerAuthReference() { 53 | return new SecurityReference(BEARER_AUTH, new AuthorizationScope[0]); 54 | } 55 | } -------------------------------------------------------------------------------- /src/main/java/com/javachinna/controller/JwtAuthenticationController.java: -------------------------------------------------------------------------------- 1 | package com.javachinna.controller; 2 | 3 | import org.springframework.context.annotation.Profile; 4 | import org.springframework.http.ResponseEntity; 5 | import org.springframework.security.authentication.AuthenticationManager; 6 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 7 | import org.springframework.security.core.Authentication; 8 | import org.springframework.security.core.userdetails.UserDetails; 9 | import org.springframework.web.bind.annotation.CrossOrigin; 10 | import org.springframework.web.bind.annotation.PostMapping; 11 | import org.springframework.web.bind.annotation.RequestBody; 12 | import org.springframework.web.bind.annotation.RestController; 13 | 14 | import com.javachinna.config.Profiles; 15 | import com.javachinna.model.JwtRequest; 16 | import com.javachinna.model.JwtResponse; 17 | import com.javachinna.util.JwtTokenUtil; 18 | 19 | import lombok.RequiredArgsConstructor; 20 | 21 | @Profile(Profiles.JWT_AUTH) 22 | @RestController 23 | @CrossOrigin 24 | @RequiredArgsConstructor 25 | public class JwtAuthenticationController { 26 | private final AuthenticationManager authenticationManager; 27 | private final JwtTokenUtil jwtTokenUtil; 28 | 29 | @PostMapping("/authenticate") 30 | public ResponseEntity createAuthenticationToken(@RequestBody JwtRequest authenticationRequest) throws Exception { 31 | Authentication authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(authenticationRequest.getUsername(), authenticationRequest.getPassword())); 32 | final UserDetails userDetails = (UserDetails) authentication.getPrincipal(); 33 | final String token = jwtTokenUtil.generateToken(userDetails); 34 | return ResponseEntity.ok(new JwtResponse(token)); 35 | } 36 | } -------------------------------------------------------------------------------- /src/main/java/com/javachinna/controller/ProductApi.java: -------------------------------------------------------------------------------- 1 | package com.javachinna.controller; 2 | 3 | import java.util.List; 4 | 5 | import com.javachinna.model.Product; 6 | 7 | import io.swagger.annotations.Api; 8 | import io.swagger.annotations.ApiOperation; 9 | import io.swagger.annotations.ApiResponse; 10 | import io.swagger.annotations.ApiResponses; 11 | 12 | @Api 13 | public interface ProductApi { 14 | 15 | @ApiOperation(value = "Get list of products in the System ", response = Iterable.class) 16 | @ApiResponses(value = { @ApiResponse(code = 200, message = "Suceess"), @ApiResponse(code = 401, message = "Not authorized!"), @ApiResponse(code = 403, message = "Forbidden!"), 17 | @ApiResponse(code = 404, message = "Not found!") }) 18 | public List getProductList(String consumerKey); 19 | 20 | @ApiOperation(value = "Get product by product ID", response = Product.class) 21 | public Product getProduct(Long productId); 22 | 23 | @ApiOperation(value = "Create product", response = String.class) 24 | public String createProduct(Product product); 25 | 26 | @ApiOperation(value = "Update product by product ID", response = String.class) 27 | public String updateProduct(Long productId, Product product); 28 | 29 | @ApiOperation(value = "Delete product by product ID", response = String.class) 30 | public String deleteProduct(Long productId); 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/javachinna/controller/ProductController.java: -------------------------------------------------------------------------------- 1 | package com.javachinna.controller; 2 | 3 | import java.util.List; 4 | 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.security.access.prepost.PreAuthorize; 7 | import org.springframework.web.bind.annotation.DeleteMapping; 8 | import org.springframework.web.bind.annotation.GetMapping; 9 | import org.springframework.web.bind.annotation.PathVariable; 10 | import org.springframework.web.bind.annotation.PostMapping; 11 | import org.springframework.web.bind.annotation.PutMapping; 12 | import org.springframework.web.bind.annotation.RequestBody; 13 | import org.springframework.web.bind.annotation.RequestParam; 14 | import org.springframework.web.bind.annotation.RestController; 15 | 16 | import com.javachinna.exception.ResourceNotFoundException; 17 | import com.javachinna.model.Product; 18 | import com.javachinna.service.ProductService; 19 | 20 | import lombok.extern.slf4j.Slf4j; 21 | 22 | @Slf4j 23 | @RestController 24 | public class ProductController implements ProductApi { 25 | 26 | @Autowired 27 | private ProductService productService; 28 | 29 | @Override 30 | @GetMapping("/products") 31 | public List getProductList(@RequestParam String consumerKey) { 32 | log.info("Consumer: {}", consumerKey); 33 | return productService.findAll(); 34 | } 35 | 36 | @Override 37 | @GetMapping("/products/{productId}") 38 | public Product getProduct(@PathVariable(value = "productId") Long productId) { 39 | return productService.findById(productId).orElseThrow(() -> new ResourceNotFoundException("productId " + productId + " not found")); 40 | } 41 | 42 | @Override 43 | @PostMapping("/products") 44 | public String createProduct(@RequestBody Product product) { 45 | productService.save(product); 46 | return "Product added"; 47 | } 48 | 49 | @Override 50 | @PutMapping("/products/{productId}") 51 | @PreAuthorize("hasRole('ROLE_ADMIN')") 52 | public String updateProduct(@PathVariable(value = "productId") Long productId, @RequestBody Product product) { 53 | return productService.findById(productId).map(p -> { 54 | p.setName(product.getName()); 55 | p.setPrice(product.getPrice()); 56 | productService.save(p); 57 | return "Product updated"; 58 | }).orElseThrow(() -> new ResourceNotFoundException("productId " + productId + " not found")); 59 | } 60 | 61 | @Override 62 | @DeleteMapping("/products/{productId}") 63 | public String deleteProduct(@PathVariable(value = "productId") Long productId) { 64 | return productService.findById(productId).map(p -> { 65 | productService.deleteById(productId); 66 | return "Product deleted"; 67 | }).orElseThrow(() -> new ResourceNotFoundException("productId " + productId + " not found")); 68 | } 69 | } -------------------------------------------------------------------------------- /src/main/java/com/javachinna/exception/ResourceNotFoundException.java: -------------------------------------------------------------------------------- 1 | package com.javachinna.exception; 2 | 3 | import org.springframework.http.HttpStatus; 4 | import org.springframework.web.bind.annotation.ResponseStatus; 5 | 6 | @ResponseStatus(HttpStatus.NOT_FOUND) 7 | public class ResourceNotFoundException extends RuntimeException { 8 | /** 9 | * 10 | */ 11 | private static final long serialVersionUID = 1L; 12 | 13 | public ResourceNotFoundException() { 14 | super(); 15 | } 16 | 17 | public ResourceNotFoundException(String message) { 18 | super(message); 19 | } 20 | 21 | public ResourceNotFoundException(String message, Throwable cause) { 22 | super(message, cause); 23 | } 24 | } -------------------------------------------------------------------------------- /src/main/java/com/javachinna/model/JwtRequest.java: -------------------------------------------------------------------------------- 1 | package com.javachinna.model; 2 | 3 | import java.io.Serializable; 4 | 5 | import io.swagger.annotations.ApiModelProperty; 6 | import lombok.Data; 7 | import lombok.NoArgsConstructor; 8 | 9 | @Data 10 | @NoArgsConstructor 11 | public class JwtRequest implements Serializable { 12 | private static final long serialVersionUID = 5926468583005150707L; 13 | @ApiModelProperty(example = "user") 14 | private String username; 15 | @ApiModelProperty(example = "password") 16 | private String password; 17 | } -------------------------------------------------------------------------------- /src/main/java/com/javachinna/model/JwtResponse.java: -------------------------------------------------------------------------------- 1 | package com.javachinna.model; 2 | 3 | import java.io.Serializable; 4 | 5 | import lombok.Getter; 6 | import lombok.RequiredArgsConstructor; 7 | 8 | @Getter 9 | @RequiredArgsConstructor 10 | public class JwtResponse implements Serializable { 11 | private static final long serialVersionUID = -8091879091924046844L; 12 | private final String jwttoken; 13 | } -------------------------------------------------------------------------------- /src/main/java/com/javachinna/model/Product.java: -------------------------------------------------------------------------------- 1 | package com.javachinna.model; 2 | 3 | import javax.persistence.Entity; 4 | import javax.persistence.GeneratedValue; 5 | import javax.persistence.GenerationType; 6 | import javax.persistence.Id; 7 | 8 | import io.swagger.annotations.ApiModel; 9 | import io.swagger.annotations.ApiModelProperty; 10 | import lombok.AllArgsConstructor; 11 | import lombok.Data; 12 | import lombok.NoArgsConstructor; 13 | 14 | @ApiModel 15 | @NoArgsConstructor 16 | @AllArgsConstructor 17 | @Data 18 | @Entity 19 | public class Product { 20 | @Id 21 | @GeneratedValue(strategy = GenerationType.IDENTITY) 22 | @ApiModelProperty(hidden = true) 23 | private Long id; 24 | @ApiModelProperty(example = "Headset", required = true) 25 | private String name; 26 | @ApiModelProperty(example = "100", required = true) 27 | private String price; 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/javachinna/model/Role.java: -------------------------------------------------------------------------------- 1 | package com.javachinna.model; 2 | 3 | import java.io.Serializable; 4 | import java.util.Objects; 5 | import java.util.Set; 6 | 7 | import javax.persistence.Column; 8 | import javax.persistence.Entity; 9 | import javax.persistence.GeneratedValue; 10 | import javax.persistence.GenerationType; 11 | import javax.persistence.Id; 12 | import javax.persistence.ManyToMany; 13 | 14 | import lombok.Getter; 15 | import lombok.NoArgsConstructor; 16 | import lombok.Setter; 17 | 18 | /** 19 | * The persistent class for the role database table. 20 | * 21 | */ 22 | @Entity 23 | @Getter 24 | @Setter 25 | @NoArgsConstructor 26 | public class Role implements Serializable { 27 | private static final long serialVersionUID = 1L; 28 | public static final String USER = "USER"; 29 | public static final String ADMIN = "ADMIN"; 30 | public static final String ROLE_USER = "ROLE_USER"; 31 | public static final String ROLE_ADMIN = "ROLE_ADMIN"; 32 | 33 | @Id 34 | @Column(name = "ROLE_ID") 35 | @GeneratedValue(strategy = GenerationType.IDENTITY) 36 | private int roleId; 37 | 38 | private String name; 39 | 40 | // bi-directional many-to-many association to User 41 | @ManyToMany(mappedBy = "roles") 42 | private Set users; 43 | 44 | public Role(String name) { 45 | this.name = name; 46 | } 47 | 48 | @Override 49 | public int hashCode() { 50 | final int prime = 31; 51 | int result = 1; 52 | result = prime * result + ((name == null) ? 0 : name.hashCode()); 53 | return result; 54 | } 55 | 56 | @Override 57 | public boolean equals(final Object obj) { 58 | if (this == obj) { 59 | return true; 60 | } 61 | if (obj == null) { 62 | return false; 63 | } 64 | if (getClass() != obj.getClass()) { 65 | return false; 66 | } 67 | return Objects.equals(name, ((Role) obj).getName()); 68 | } 69 | 70 | @Override 71 | public String toString() { 72 | final StringBuilder builder = new StringBuilder(); 73 | builder.append("Role [name=").append(name).append("]").append("[id=").append(roleId).append("]"); 74 | return builder.toString(); 75 | } 76 | } -------------------------------------------------------------------------------- /src/main/java/com/javachinna/model/UserEntity.java: -------------------------------------------------------------------------------- 1 | package com.javachinna.model; 2 | 3 | import java.util.Set; 4 | 5 | import javax.persistence.Column; 6 | import javax.persistence.Entity; 7 | import javax.persistence.GeneratedValue; 8 | import javax.persistence.GenerationType; 9 | import javax.persistence.Id; 10 | import javax.persistence.JoinColumn; 11 | import javax.persistence.JoinTable; 12 | import javax.persistence.ManyToMany; 13 | 14 | import lombok.Data; 15 | import lombok.NoArgsConstructor; 16 | 17 | @Entity 18 | @Data 19 | @NoArgsConstructor 20 | public class UserEntity { 21 | 22 | @Id 23 | @GeneratedValue(strategy = GenerationType.IDENTITY) 24 | @Column(name = "USER_ID") 25 | private Long id; 26 | private String username; 27 | private String password; 28 | // bi-directional many-to-many association to Role 29 | @ManyToMany 30 | @JoinTable(name = "user_role", joinColumns = { @JoinColumn(name = "USER_ID") }, inverseJoinColumns = { @JoinColumn(name = "ROLE_ID") }) 31 | private Set roles; 32 | 33 | /** 34 | * @param username 35 | * @param password 36 | */ 37 | public UserEntity(String username, String password) { 38 | this.username = username; 39 | this.password = password; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/com/javachinna/repo/ProductRepository.java: -------------------------------------------------------------------------------- 1 | package com.javachinna.repo; 2 | 3 | import org.springframework.data.jpa.repository.JpaRepository; 4 | import org.springframework.stereotype.Repository; 5 | 6 | import com.javachinna.model.Product; 7 | 8 | @Repository 9 | public interface ProductRepository extends JpaRepository { 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/javachinna/repo/RoleRepository.java: -------------------------------------------------------------------------------- 1 | package com.javachinna.repo; 2 | 3 | import org.springframework.data.jpa.repository.JpaRepository; 4 | 5 | import com.javachinna.model.Role; 6 | 7 | public interface RoleRepository extends JpaRepository { 8 | Role findByName(String name); 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/com/javachinna/repo/UserRepository.java: -------------------------------------------------------------------------------- 1 | package com.javachinna.repo; 2 | 3 | import org.springframework.data.jpa.repository.JpaRepository; 4 | import org.springframework.stereotype.Repository; 5 | 6 | import com.javachinna.model.UserEntity; 7 | 8 | @Repository 9 | public interface UserRepository extends JpaRepository { 10 | 11 | UserEntity findByUsername(String userName); 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/javachinna/service/ProductService.java: -------------------------------------------------------------------------------- 1 | package com.javachinna.service; 2 | 3 | import java.util.List; 4 | import java.util.Optional; 5 | 6 | import com.javachinna.model.Product; 7 | 8 | public interface ProductService { 9 | Product save(Product product); 10 | 11 | void deleteById(Long id); 12 | 13 | Optional findById(Long id); 14 | 15 | List findAll(); 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/javachinna/service/UserDetailsServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.javachinna.service; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | import java.util.Set; 6 | 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.security.core.authority.SimpleGrantedAuthority; 9 | import org.springframework.security.core.userdetails.User; 10 | import org.springframework.security.core.userdetails.UserDetails; 11 | import org.springframework.security.core.userdetails.UserDetailsService; 12 | import org.springframework.security.core.userdetails.UsernameNotFoundException; 13 | import org.springframework.stereotype.Service; 14 | import org.springframework.transaction.annotation.Transactional; 15 | 16 | import com.javachinna.model.Role; 17 | import com.javachinna.model.UserEntity; 18 | import com.javachinna.repo.UserRepository; 19 | 20 | @Service 21 | public class UserDetailsServiceImpl implements UserDetailsService { 22 | 23 | @Autowired 24 | private UserRepository repo; 25 | 26 | @Override 27 | @Transactional 28 | public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { 29 | UserEntity user = repo.findByUsername(username); 30 | if (user != null) { 31 | return new User(user.getUsername(), user.getPassword(), buildSimpleGrantedAuthorities(user.getRoles())); 32 | } else { 33 | throw new UsernameNotFoundException("User not found with username: " + username); 34 | } 35 | } 36 | 37 | private static List buildSimpleGrantedAuthorities(final Set roles) { 38 | List authorities = new ArrayList<>(); 39 | for (Role role : roles) { 40 | authorities.add(new SimpleGrantedAuthority(role.getName())); 41 | } 42 | return authorities; 43 | } 44 | } -------------------------------------------------------------------------------- /src/main/java/com/javachinna/service/impl/ProductServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.javachinna.service.impl; 2 | 3 | import java.util.List; 4 | import java.util.Optional; 5 | 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.stereotype.Service; 8 | 9 | import com.javachinna.model.Product; 10 | import com.javachinna.repo.ProductRepository; 11 | import com.javachinna.service.ProductService;; 12 | 13 | @Service 14 | public class ProductServiceImpl implements ProductService { 15 | 16 | @Autowired 17 | private ProductRepository productRepository; 18 | 19 | @Override 20 | public Product save(Product product) { 21 | return productRepository.save(product); 22 | } 23 | 24 | @Override 25 | public void deleteById(Long id) { 26 | productRepository.deleteById(id); 27 | } 28 | 29 | @Override 30 | public Optional findById(Long id) { 31 | return productRepository.findById(id); 32 | } 33 | 34 | @Override 35 | public List findAll() { 36 | return productRepository.findAll(); 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/com/javachinna/util/JwtTokenUtil.java: -------------------------------------------------------------------------------- 1 | package com.javachinna.util; 2 | 3 | import java.io.Serializable; 4 | import java.util.Date; 5 | import java.util.HashMap; 6 | import java.util.Map; 7 | import java.util.function.Function; 8 | 9 | import org.springframework.beans.factory.annotation.Value; 10 | import org.springframework.security.core.userdetails.UserDetails; 11 | import org.springframework.stereotype.Component; 12 | 13 | import io.jsonwebtoken.Claims; 14 | import io.jsonwebtoken.Jwts; 15 | import io.jsonwebtoken.SignatureAlgorithm; 16 | 17 | @Component 18 | public class JwtTokenUtil implements Serializable { 19 | private static final long serialVersionUID = -2550185165626007488L; 20 | public static final long JWT_TOKEN_VALIDITY = 5 * 60 * 60; 21 | @Value("${jwt.secret}") 22 | private String secret; 23 | 24 | // retrieve username from jwt token 25 | public String getUsernameFromToken(String token) { 26 | return getClaimFromToken(token, Claims::getSubject); 27 | } 28 | 29 | // retrieve expiration date from jwt token 30 | public Date getExpirationDateFromToken(String token) { 31 | return getClaimFromToken(token, Claims::getExpiration); 32 | } 33 | 34 | public T getClaimFromToken(String token, Function claimsResolver) { 35 | final Claims claims = getAllClaimsFromToken(token); 36 | return claimsResolver.apply(claims); 37 | } 38 | 39 | // for retrieveing any information from token we will need the secret key 40 | private Claims getAllClaimsFromToken(String token) { 41 | return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody(); 42 | } 43 | 44 | // check if the token has expired 45 | private Boolean isTokenExpired(String token) { 46 | final Date expiration = getExpirationDateFromToken(token); 47 | return expiration.before(new Date()); 48 | } 49 | 50 | // generate token for user 51 | public String generateToken(UserDetails userDetails) { 52 | Map claims = new HashMap<>(); 53 | return doGenerateToken(claims, userDetails.getUsername()); 54 | } 55 | 56 | // while creating the token - 57 | // 1. Define claims of the token, like Issuer, Expiration, Subject, and the ID 58 | // 2. Sign the JWT using the HS512 algorithm and secret key. 59 | // 3. According to JWS Compact 60 | // Serialization(https://tools.ietf.org/html/draft-ietf-jose-json-web-signature-41#section-3.1) 61 | // compaction of the JWT to a URL-safe string 62 | private String doGenerateToken(Map claims, String subject) { 63 | return Jwts.builder().setClaims(claims).setSubject(subject).setIssuedAt(new Date(System.currentTimeMillis())) 64 | .setExpiration(new Date(System.currentTimeMillis() + JWT_TOKEN_VALIDITY * 1000)).signWith(SignatureAlgorithm.HS512, secret).compact(); 65 | } 66 | 67 | // validate token 68 | public Boolean validateToken(String token, UserDetails userDetails) { 69 | final String username = getUsernameFromToken(token); 70 | return (username.equals(userDetails.getUsername()) && !isTokenExpired(token)); 71 | } 72 | } -------------------------------------------------------------------------------- /src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | # Database configuration props 2 | spring.datasource.url=jdbc:mysql://localhost:3306/rest?createDatabaseIfNotExist=true 3 | spring.datasource.username=root 4 | spring.datasource.password=secret 5 | #spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver 6 | 7 | # hibernate props 8 | spring.jpa.show-sql=true 9 | spring.jpa.hibernate.ddl-auto=create 10 | spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5InnoDBDialect 11 | logging.level.root=info 12 | logging.level.org.springframwork=debug 13 | logging.file.path=D:/logs 14 | 15 | ## JWT Properties 16 | jwt.secret=javachinna 17 | -------------------------------------------------------------------------------- /src/test/java/com/javachinna/rest/SpringBootRestCrudApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.javachinna.rest; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class SpringBootRestCrudApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | --------------------------------------------------------------------------------