Skip to content

Commit ad2af00

Browse files
committed
Initial commit
0 parents  commit ad2af00

31 files changed

+1071
-0
lines changed

Diff for: README.md

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
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

Diff for: pom.xml

+92
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
4+
<modelVersion>4.0.0</modelVersion>
5+
<parent>
6+
<groupId>org.springframework.boot</groupId>
7+
<artifactId>spring-boot-starter-parent</artifactId>
8+
<version>2.2.6.RELEASE</version>
9+
<relativePath /> <!-- lookup parent from repository -->
10+
</parent>
11+
<groupId>com.javachinna</groupId>
12+
<artifactId>spring-boot-rest-crud</artifactId>
13+
<version>0.0.1-SNAPSHOT</version>
14+
<name>spring-boot-rest-crud</name>
15+
<description>Demo project for Spring Boot REST API CRUD Operations with Swagger Documentation</description>
16+
17+
<properties>
18+
<java.version>11</java.version>
19+
</properties>
20+
21+
<dependencies>
22+
<dependency>
23+
<groupId>org.springframework.boot</groupId>
24+
<artifactId>spring-boot-starter-data-jpa</artifactId>
25+
</dependency>
26+
<dependency>
27+
<groupId>org.springframework.boot</groupId>
28+
<artifactId>spring-boot-starter-web</artifactId>
29+
</dependency>
30+
<dependency>
31+
<groupId>org.springframework.boot</groupId>
32+
<artifactId>spring-boot-starter-security</artifactId>
33+
</dependency>
34+
<dependency>
35+
<groupId>org.springframework.boot</groupId>
36+
<artifactId>spring-boot-devtools</artifactId>
37+
<scope>runtime</scope>
38+
<optional>true</optional>
39+
</dependency>
40+
<dependency>
41+
<groupId>mysql</groupId>
42+
<artifactId>mysql-connector-java</artifactId>
43+
<scope>runtime</scope>
44+
</dependency>
45+
<dependency>
46+
<groupId>org.projectlombok</groupId>
47+
<artifactId>lombok</artifactId>
48+
<optional>true</optional>
49+
</dependency>
50+
<dependency>
51+
<groupId>org.springframework.boot</groupId>
52+
<artifactId>spring-boot-starter-test</artifactId>
53+
<scope>test</scope>
54+
<exclusions>
55+
<exclusion>
56+
<groupId>org.junit.vintage</groupId>
57+
<artifactId>junit-vintage-engine</artifactId>
58+
</exclusion>
59+
</exclusions>
60+
</dependency>
61+
<dependency>
62+
<groupId>org.springframework.security</groupId>
63+
<artifactId>spring-security-test</artifactId>
64+
<scope>test</scope>
65+
</dependency>
66+
<dependency>
67+
<groupId>io.springfox</groupId>
68+
<artifactId>springfox-swagger2</artifactId>
69+
<version>2.9.2</version>
70+
</dependency>
71+
<dependency>
72+
<groupId>io.springfox</groupId>
73+
<artifactId>springfox-swagger-ui</artifactId>
74+
<version>2.9.2</version>
75+
</dependency>
76+
<dependency>
77+
<groupId>io.jsonwebtoken</groupId>
78+
<artifactId>jjwt</artifactId>
79+
<version>0.9.1</version>
80+
</dependency>
81+
</dependencies>
82+
83+
<build>
84+
<plugins>
85+
<plugin>
86+
<groupId>org.springframework.boot</groupId>
87+
<artifactId>spring-boot-maven-plugin</artifactId>
88+
</plugin>
89+
</plugins>
90+
</build>
91+
92+
</project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
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+
}
+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
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+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
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+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package com.javachinna.config;
2+
3+
import javax.sql.DataSource;
4+
5+
import org.springframework.beans.factory.annotation.Autowired;
6+
import org.springframework.context.annotation.Bean;
7+
import org.springframework.context.annotation.Configuration;
8+
import org.springframework.context.annotation.Profile;
9+
import org.springframework.http.HttpMethod;
10+
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
11+
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
12+
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
13+
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
14+
import org.springframework.security.config.http.SessionCreationPolicy;
15+
import org.springframework.security.core.userdetails.UserDetailsService;
16+
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
17+
import org.springframework.security.crypto.password.PasswordEncoder;
18+
19+
import com.javachinna.model.Role;
20+
21+
@Profile(Profiles.BASIC_AUTH)
22+
@Configuration
23+
@EnableWebSecurity
24+
public class BasicAuthSecurityConfig extends WebSecurityConfigurerAdapter {
25+
26+
@Autowired
27+
private UserDetailsService userDetailsService;
28+
29+
@Autowired
30+
DataSource dataSource;
31+
32+
@Autowired
33+
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
34+
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
35+
}
36+
37+
@Override
38+
protected void configure(HttpSecurity http) throws Exception {
39+
// Disable CSRF
40+
http.csrf().disable()
41+
// Only admin can perform HTTP delete operation
42+
.authorizeRequests().antMatchers(HttpMethod.DELETE).hasRole(Role.ADMIN)
43+
// any authenticated user can perform all other operations
44+
.antMatchers("/products/**").hasAnyRole(Role.ADMIN, Role.USER).and().httpBasic()
45+
// Permit all other request without authentication
46+
.and().authorizeRequests().anyRequest().permitAll()
47+
// We don't need sessions to be created.
48+
.and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
49+
}
50+
51+
@Override
52+
public UserDetailsService userDetailsService() {
53+
return userDetailsService;
54+
}
55+
56+
@Bean
57+
public PasswordEncoder passwordEncoder() {
58+
return new BCryptPasswordEncoder(10);
59+
}
60+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
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).and().httpBasic()
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+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
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+
}
+66
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
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.security.authentication.UsernamePasswordAuthenticationToken;
11+
import org.springframework.security.core.context.SecurityContextHolder;
12+
import org.springframework.security.core.userdetails.UserDetails;
13+
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
14+
import org.springframework.stereotype.Component;
15+
import org.springframework.web.filter.OncePerRequestFilter;
16+
17+
import com.javachinna.service.UserDetailsServiceImpl;
18+
import com.javachinna.util.JwtTokenUtil;
19+
20+
import io.jsonwebtoken.ExpiredJwtException;
21+
import lombok.RequiredArgsConstructor;
22+
23+
@Component
24+
@RequiredArgsConstructor
25+
public class JwtRequestFilter extends OncePerRequestFilter {
26+
private final UserDetailsServiceImpl jwtUserDetailsService;
27+
private final JwtTokenUtil jwtTokenUtil;
28+
29+
@Override
30+
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
31+
final String requestTokenHeader = request.getHeader("Authorization");
32+
String username = null;
33+
String jwtToken = null;
34+
// JWT Token is in the form "Bearer token". Remove Bearer word and get
35+
// only the Token
36+
if (requestTokenHeader != null) {
37+
if (requestTokenHeader.startsWith("Bearer ")) {
38+
jwtToken = requestTokenHeader.substring(7);
39+
try {
40+
username = jwtTokenUtil.getUsernameFromToken(jwtToken);
41+
} catch (IllegalArgumentException e) {
42+
System.out.println("Unable to get JWT Token");
43+
} catch (ExpiredJwtException e) {
44+
System.out.println("JWT Token has expired");
45+
}
46+
} else {
47+
logger.warn("JWT Token does not begin with Bearer String");
48+
}
49+
}
50+
// Once we get the token validate it.
51+
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
52+
UserDetails userDetails = this.jwtUserDetailsService.loadUserByUsername(username);
53+
// if token is valid configure Spring Security to manually set
54+
// authentication
55+
if (jwtTokenUtil.validateToken(jwtToken, userDetails)) {
56+
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
57+
usernamePasswordAuthenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
58+
// After setting the Authentication in the context, we specify
59+
// that the current user is authenticated. So it passes the
60+
// Spring Security Configurations successfully.
61+
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
62+
}
63+
}
64+
chain.doFilter(request, response);
65+
}
66+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
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+
}

Diff for: src/main/java/com/javachinna/config/Profiles.java

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
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+
}

0 commit comments

Comments
 (0)