Skip to content

Commit 1b883cf

Browse files
authored
Jwt (#9)
* Add files via upload * Adding jwt support * enable csrf
1 parent 30d7348 commit 1b883cf

27 files changed

+897
-17
lines changed

README.md

+31-9
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,16 @@
1515
<img height="300px" src="https://github.com/GouravRusiya30/SpringBootRestAPI/blob/master/docs/spring.png">
1616

1717
## Desciption
18-
#### This project mainly focussed on the Kickstart to the CI/CD using TravisCI. Includes CodeCoverage, Sonarqube integration which can be plugged into any application.
18+
#### This project mainly focused on the Kickstart to the CI/CD using TravisCI. Includes CodeCoverage, Sonarqube integration which can be plugged into any application.
1919

2020

2121
## Task List Progress
2222
- [X] Rest controllers and models using SpringBoot
2323
- [X] MongoDB configuration
2424
- [X] TravisCI build
25-
- [X] SonarQube integration
25+
- [X] SonarQube integration
2626
- [X] Jacoco Test report
27-
- [ ] JWT authentication
27+
- [X] JWT authentication
2828
- [ ] 80% and above Code Coverage (using codecov or coveralls)
2929
- [ ] Cloud deployment
3030

@@ -33,15 +33,15 @@
3333

3434
### Pre-requisite and Installing Steps
3535

36-
* Get a running instance of MongoDB that you can connect to.
36+
* Get a running instance of MongoDB that you can connect to.
3737
For more information on getting started with MongoDB, visit their [online tutorial](https://docs.mongodb.com/manual/).
3838
* Start by creating a test database. I will call mine "rest_tutorial" using the following command in the MongoDB shell, or through a database manager like MongoDB Compass:
3939
```use rest_tutorial;```
4040

4141
* Create a sample collection that will hold data about different types of pets. Let's create the collection with the following command:
4242
```db.createCollection("pets");```
4343

44-
* Once the collection is created, we need to add some data!
44+
* Once the collection is created, we need to add some data!
4545
We can add data to the collection with the below query, you can add any number of data like this :
4646
```db.pets.insertMany([```
4747
```{```
@@ -62,11 +62,29 @@ We can add data to the collection with the below query, you can add any number o
6262
```]);```
6363

6464
* Add the mongodb authentication-database, username & password in [application.properties](https://github.com/GouravRusiya30/SpringBootRestAPI/blob/master/src/main/resources/application.properties)
65-
If there is no authrntication when you are running locally then you can also remove these properties from this file.
65+
If there is no authentication when you are running locally then you can also remove these properties from this file.
6666

67+
* Create the user roles in the database. The user roles can be one of "USER, MODERATOR or ADMIN"
68+
```
69+
db.roles.insertMany([
70+
{ name: "ROLE_USER" },
71+
{ name: "ROLE_MODERATOR" },
72+
{ name: "ROLE_ADMIN" },
73+
])
74+
```
6775

6876
### Running the tests
69-
Once the server starts, you are free to test your API however you choose.
77+
Once the server starts, your first need to register a user and login as that user to get a token.\
78+
79+
##### [user registration](https://github.com/ravening/SpringBootRestAPI/blob/master/docs/UserRegistration.png)
80+
81+
##### [User login](https://github.com/ravening/SpringBootRestAPI/blob/master/docs/UserLogin.png)
82+
83+
Once you get the token, you need to pass that token for every request you make to the backend
84+
In the postman, select the "header" section and enter `Authorization` for the key and\
85+
"Bearer <the token you copied above>" for the value
86+
87+
you are free to test your API however you choose.
7088
Use postman for the below tests :
7189
##### [getAllPets](https://github.com/GouravRusiya30/SpringBootRestAPI/blob/master/docs/getAllPets.png)
7290

@@ -78,6 +96,10 @@ Use postman for the below tests :
7896

7997
##### [modifyPetById](https://github.com/GouravRusiya30/SpringBootRestAPI/blob/master/docs/modifyPetById.png)
8098

99+
Once done with all the testing, you can logout using the endpoint `/api/auth/logout`
100+
101+
##### [logout](https://github.com/ravening/SpringBootRestAPI/blob/master/docs/UserLogout.png)
102+
81103
### Code Coverage
82104
For code coverage reports integration, I have shown example using Codecov and Coveralls as both are pretty popular and easy to integrate with the travis.
83105

@@ -91,10 +113,10 @@ Awesome but please first go through the [ISSUE TEMPLATE.md](https://github.com/G
91113

92114
### Pull Request Template
93115
``Are you up for your first PR for this project !!!``
94-
Awesome but please first go through the [PULL REQUEST TEMPLATE.md](https://github.com/GouravRusiya30/SpringBootRestAPI/blob/master/PULL_REQUEST_TEMPLATE.md) and use this template to submit your PR.
116+
Awesome but please first go through the [PULL REQUEST TEMPLATE.md](https://github.com/GouravRusiya30/SpringBootRestAPI/blob/master/PULL_REQUEST_TEMPLATE) and use this template to submit your PR.
95117

96118
### Contributing
97119
Please read [CONTRIBUTING.md](https://github.com/GouravRusiya30/SpringBootRestAPI/blob/master/CONTRIBUTING.md) and [CODE OF CONDUCT.md](https://github.com/GouravRusiya30/SpringBootRestAPI/blob/master/CODE_OF_CONDUCT.md) for details on our code of conduct, and the process for submitting pull requests to us.
98120

99121
### Authors
100-
* **Gourav Rusiya**
122+
* **Gourav Rusiya**

build.gradle

+2
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ repositories {
3232
dependencies {
3333
compile group: 'org.springframework.boot', name: 'spring-boot-starter-data-mongodb', version:'2.1.1.RELEASE'
3434
compile group: 'org.springframework.boot', name: 'spring-boot-starter-web', version:'2.1.1.RELEASE'
35+
compile group: 'org.springframework.boot', name: 'spring-boot-starter-security', version: '2.1.1.RELEASE'
36+
compile group: 'io.jsonwebtoken', name: 'jjwt', version: '0.9.1'
3537
testCompile group: 'org.springframework.boot', name: 'spring-boot-starter-test', version:'2.1.1.RELEASE'
3638
}
3739

docs/GetRequest.png

75.1 KB
Loading

docs/UserLogin.png

77.3 KB
Loading

docs/UserLogout.png

37.6 KB
Loading

docs/UserRegistration.png

33.3 KB
Loading

gradle/wrapper/gradle-wrapper.jar

1.07 KB
Binary file not shown.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package com.gourav.restapi.config;
2+
3+
import com.gourav.restapi.config.jwt.AuthEntryPointJwt;
4+
import com.gourav.restapi.config.jwt.AuthTokenFilter;
5+
import com.gourav.restapi.config.services.UserDetailsServiceImpl;
6+
import org.springframework.beans.factory.annotation.Autowired;
7+
import org.springframework.context.annotation.Bean;
8+
import org.springframework.context.annotation.Configuration;
9+
import org.springframework.security.authentication.AuthenticationManager;
10+
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
11+
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
12+
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
13+
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
14+
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
15+
import org.springframework.security.config.http.SessionCreationPolicy;
16+
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
17+
import org.springframework.security.crypto.password.PasswordEncoder;
18+
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
19+
20+
@Configuration
21+
@EnableWebSecurity
22+
@EnableGlobalMethodSecurity(prePostEnabled = true)
23+
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
24+
@Autowired
25+
UserDetailsServiceImpl userDetailsService;
26+
27+
@Autowired
28+
AuthEntryPointJwt unauthorizedHandler;
29+
30+
@Bean
31+
public AuthTokenFilter authenticationJwtTokenFilter() {
32+
return new AuthTokenFilter();
33+
}
34+
35+
@Override
36+
public void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
37+
authenticationManagerBuilder.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
38+
}
39+
40+
@Bean
41+
@Override
42+
public AuthenticationManager authenticationManagerBean() throws Exception {
43+
return super.authenticationManagerBean();
44+
}
45+
46+
@Bean
47+
public PasswordEncoder passwordEncoder() {
48+
return new BCryptPasswordEncoder();
49+
}
50+
51+
@Override
52+
protected void configure(HttpSecurity http) throws Exception {
53+
http.cors().and().csrf().and()
54+
.exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
55+
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
56+
.authorizeRequests().antMatchers("/api/auth/**").permitAll()
57+
.anyRequest().authenticated();
58+
59+
http.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class);
60+
}
61+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package com.gourav.restapi.config.jwt;
2+
3+
import org.slf4j.Logger;
4+
import org.slf4j.LoggerFactory;
5+
import org.springframework.security.core.AuthenticationException;
6+
import org.springframework.security.web.AuthenticationEntryPoint;
7+
import org.springframework.stereotype.Component;
8+
9+
import javax.servlet.ServletException;
10+
import javax.servlet.http.HttpServletRequest;
11+
import javax.servlet.http.HttpServletResponse;
12+
import java.io.IOException;
13+
14+
@Component
15+
public class AuthEntryPointJwt implements AuthenticationEntryPoint {
16+
private static final Logger logger = LoggerFactory.getLogger(AuthEntryPointJwt.class);
17+
18+
@Override
19+
public void commence(HttpServletRequest request, HttpServletResponse response,
20+
AuthenticationException authException) throws IOException, ServletException {
21+
logger.error("Unauthorized error: {}", authException.getMessage());
22+
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Error: Unauthorized");
23+
}
24+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package com.gourav.restapi.config.jwt;
2+
3+
import com.gourav.restapi.config.services.UserDetailsServiceImpl;
4+
import org.slf4j.Logger;
5+
import org.slf4j.LoggerFactory;
6+
import org.springframework.beans.factory.annotation.Autowired;
7+
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
8+
import org.springframework.security.core.context.SecurityContextHolder;
9+
import org.springframework.security.core.userdetails.UserDetails;
10+
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
11+
import org.springframework.stereotype.Component;
12+
import org.springframework.util.StringUtils;
13+
import org.springframework.web.filter.OncePerRequestFilter;
14+
15+
import javax.servlet.FilterChain;
16+
import javax.servlet.ServletException;
17+
import javax.servlet.http.HttpServletRequest;
18+
import javax.servlet.http.HttpServletResponse;
19+
import java.io.IOException;
20+
21+
@Component
22+
public class AuthTokenFilter extends OncePerRequestFilter {
23+
@Autowired
24+
private JwtUtils jwtUtils;
25+
26+
@Autowired
27+
private UserDetailsServiceImpl userDetailsService;
28+
29+
private static final Logger logger = LoggerFactory.getLogger(AuthTokenFilter.class);
30+
31+
@Override
32+
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
33+
throws ServletException, IOException {
34+
try {
35+
String jwt = parseJwt(request);
36+
if (jwt != null && jwtUtils.validateJwtToken(jwt)) {
37+
String username = jwtUtils.getUserNameFromJwtToken(jwt);
38+
39+
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
40+
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null,
41+
userDetails.getAuthorities());
42+
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
43+
44+
SecurityContextHolder.getContext().setAuthentication(authentication);
45+
}
46+
} catch (Exception e) {
47+
logger.error("Cannot set user authentication: {}", e.getMessage());
48+
}
49+
50+
filterChain.doFilter(request, response);
51+
}
52+
53+
private String parseJwt(HttpServletRequest request) {
54+
String headerAuth = request.getHeader("Authorization");
55+
56+
if (StringUtils.hasText(headerAuth) && headerAuth.startsWith("Bearer ")) {
57+
return headerAuth.substring(7, headerAuth.length());
58+
}
59+
60+
return null;
61+
}
62+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package com.gourav.restapi.config.jwt;
2+
3+
import com.gourav.restapi.config.services.UserDetailsImpl;
4+
import io.jsonwebtoken.ExpiredJwtException;
5+
import io.jsonwebtoken.Jwts;
6+
import io.jsonwebtoken.MalformedJwtException;
7+
import io.jsonwebtoken.SignatureAlgorithm;
8+
import io.jsonwebtoken.SignatureException;
9+
import io.jsonwebtoken.UnsupportedJwtException;
10+
import org.slf4j.Logger;
11+
import org.slf4j.LoggerFactory;
12+
import org.springframework.beans.factory.annotation.Value;
13+
import org.springframework.security.core.Authentication;
14+
import org.springframework.stereotype.Component;
15+
16+
import java.util.Date;
17+
import java.util.Map;
18+
import java.util.concurrent.ConcurrentHashMap;
19+
20+
@Component
21+
public class JwtUtils {
22+
private static final Logger logger = LoggerFactory.getLogger(JwtUtils.class);
23+
24+
@Value("${springboot.app.jwtSecret}")
25+
private String jwtSecret;
26+
27+
@Value("${springboot.app.jwtExpirationMs}")
28+
private int jwtExpirationMs;
29+
30+
private Map<String, Boolean> jwtMap = new ConcurrentHashMap<>();
31+
32+
public String generateJwtToken(Authentication authentication) {
33+
UserDetailsImpl userPrincipal = (UserDetailsImpl) authentication.getPrincipal();
34+
35+
String token = Jwts.builder()
36+
.setSubject((userPrincipal.getUsername()))
37+
.setIssuedAt(new Date())
38+
.setExpiration(new Date((new Date()).getTime() + jwtExpirationMs))
39+
.signWith(SignatureAlgorithm.HS512, jwtSecret)
40+
.compact();
41+
42+
// store this token
43+
jwtMap.put(token, false);
44+
return token;
45+
}
46+
47+
public String getUserNameFromJwtToken(String token) {
48+
return Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(token).getBody().getSubject();
49+
}
50+
51+
public boolean validateJwtToken(String authToken) {
52+
if (jwtMap.containsKey(authToken)) {
53+
try {
54+
Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(authToken);
55+
return true;
56+
} catch (SignatureException e) {
57+
logger.error("Invalid JWT signature: {}", e.getMessage());
58+
} catch (MalformedJwtException e) {
59+
logger.error("Invalid JWT token: {}", e.getMessage());
60+
} catch (ExpiredJwtException e) {
61+
jwtMap.remove(authToken);
62+
logger.error("JWT token is expired: {}", e.getMessage());
63+
} catch (UnsupportedJwtException e) {
64+
logger.error("JWT token is unsupported: {}", e.getMessage());
65+
} catch (IllegalArgumentException e) {
66+
logger.error("JWT claims string is empty: {}", e.getMessage());
67+
}
68+
}
69+
return false;
70+
}
71+
72+
public void invalidateToken(String token) {
73+
jwtMap.remove(token);
74+
}
75+
}

0 commit comments

Comments
 (0)