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