├── src ├── main │ └── java │ │ └── org │ │ └── backendstory │ │ └── App.java └── test │ └── java │ └── org │ └── backendstory │ └── AppTest.java ├── README.md ├── .gitignore ├── spring-security-exception-handling ├── src │ └── main │ │ └── java │ │ └── authorization │ │ ├── JwtUtil.java │ │ └── ExceptionHandlingApplication.java ├── README.md └── pom.xml ├── spring-security-upgrade ├── src │ └── main │ │ └── java │ │ └── com │ │ └── backendstory │ │ └── upgrade │ │ ├── JwtUtil.java │ │ └── SpringSecurityUpgrade.java ├── pom.xml └── README.md ├── spring-security-authorization ├── src │ └── main │ │ └── java │ │ └── com │ │ └── example │ │ └── authorization │ │ ├── JwtUtil.java │ │ └── SpringSecurityAuthorizationApplication.java ├── README.md └── pom.xml ├── spring-security-authentication-scenario-3 ├── src │ └── main │ │ └── java │ │ └── com │ │ └── backendstory │ │ └── authentication │ │ ├── JwtUtil.java │ │ └── Scenario3.java ├── README.md └── pom.xml ├── spring-security-authentication-scenario-4 ├── src │ └── main │ │ └── java │ │ └── com │ │ └── backendstory │ │ └── authentication │ │ ├── JwtUtil.java │ │ └── Scenario4.java ├── README.md └── pom.xml ├── spring-security-authentication-scenario-5 ├── src │ └── main │ │ └── java │ │ └── com │ │ └── backendstory │ │ └── authentication │ │ ├── JwtUtil.java │ │ └── Scenario5.java ├── README.md └── pom.xml ├── pom.xml └── LICENSE /src/main/java/org/backendstory/App.java: -------------------------------------------------------------------------------- 1 | package org.backendstory; 2 | 3 | /** 4 | * Hello world! 5 | * 6 | */ 7 | public class App 8 | { 9 | public static void main( String[] args ) 10 | { 11 | System.out.println( "Hello World!" ); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # BackendStory Code Samples 2 | 3 | This repository is where I store code samples for [backendstory.com](https://backendstory.com) articles. 4 | 5 | [backendstory.com](https://backendstory.com) is a blog which focuses on application security in Java applications. 6 | 7 | Hope you find these samples useful. 8 | -------------------------------------------------------------------------------- /src/test/java/org/backendstory/AppTest.java: -------------------------------------------------------------------------------- 1 | package org.backendstory; 2 | 3 | import static org.junit.Assert.assertTrue; 4 | 5 | import org.junit.Test; 6 | 7 | /** 8 | * Unit test for simple App. 9 | */ 10 | public class AppTest 11 | { 12 | /** 13 | * Rigorous Test :-) 14 | */ 15 | @Test 16 | public void shouldAnswerWithTrue() 17 | { 18 | assertTrue( true ); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/**/target/ 5 | !**/src/test/**/target/ 6 | .DS_Store 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | 16 | ### IntelliJ IDEA ### 17 | .idea 18 | *.iws 19 | *.iml 20 | *.ipr 21 | 22 | ### NetBeans ### 23 | /nbproject/private/ 24 | /nbbuild/ 25 | /dist/ 26 | /nbdist/ 27 | /.nb-gradle/ 28 | build/ 29 | !**/src/main/**/build/ 30 | !**/src/test/**/build/ 31 | 32 | ### VS Code ### 33 | .vscode/ 34 | .mvn 35 | -------------------------------------------------------------------------------- /spring-security-exception-handling/src/main/java/authorization/JwtUtil.java: -------------------------------------------------------------------------------- 1 | package authorization; 2 | 3 | import io.jsonwebtoken.Claims; 4 | import io.jsonwebtoken.Jwts; 5 | import io.jsonwebtoken.SignatureAlgorithm; 6 | import io.jsonwebtoken.security.Keys; 7 | import org.springframework.stereotype.Service; 8 | 9 | import java.security.Key; 10 | import java.util.Date; 11 | 12 | @Service 13 | public class JwtUtil { 14 | 15 | private static final int expireInMs = 300 * 10000; 16 | 17 | private final static Key key = Keys.secretKeyFor(SignatureAlgorithm.HS256); 18 | 19 | public String generate(String username) { 20 | return Jwts.builder() 21 | .setSubject(username) 22 | .setIssuer("backendstory.com") 23 | .setIssuedAt(new Date(System.currentTimeMillis())) 24 | .setExpiration(new Date(System.currentTimeMillis() + expireInMs)) 25 | .signWith(key) 26 | .compact(); 27 | } 28 | public boolean validate(String token) { 29 | if (getUsername(token) != null && isExpired(token)) { 30 | return true; 31 | } 32 | return false; 33 | } 34 | 35 | public String getUsername(String token) { 36 | Claims claims = getClaims(token); 37 | return claims.getSubject(); 38 | } 39 | 40 | public boolean isExpired(String token) { 41 | Claims claims = getClaims(token); 42 | return claims.getExpiration().after(new Date(System.currentTimeMillis())); 43 | } 44 | 45 | private Claims getClaims(String token) { 46 | return Jwts.parser().setSigningKey(key).parseClaimsJws(token).getBody(); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /spring-security-upgrade/src/main/java/com/backendstory/upgrade/JwtUtil.java: -------------------------------------------------------------------------------- 1 | package com.backendstory.upgrade; 2 | 3 | import io.jsonwebtoken.Claims; 4 | import io.jsonwebtoken.Jwts; 5 | import io.jsonwebtoken.SignatureAlgorithm; 6 | import io.jsonwebtoken.security.Keys; 7 | import org.springframework.stereotype.Service; 8 | 9 | import java.security.Key; 10 | import java.util.Date; 11 | 12 | @Service 13 | public class JwtUtil { 14 | 15 | private static final int expireInMs = 300 * 1000; 16 | 17 | private final static Key key = Keys.secretKeyFor(SignatureAlgorithm.HS256); 18 | 19 | public String generate(String username) { 20 | return Jwts.builder() 21 | .setSubject(username) 22 | .setIssuer("backendstory.com") 23 | .setIssuedAt(new Date(System.currentTimeMillis())) 24 | .setExpiration(new Date(System.currentTimeMillis() + expireInMs)) 25 | .signWith(key) 26 | .compact(); 27 | } 28 | public boolean validate(String token) { 29 | if (getUsername(token) != null && isExpired(token)) { 30 | return true; 31 | } 32 | return false; 33 | } 34 | 35 | public String getUsername(String token) { 36 | Claims claims = getClaims(token); 37 | return claims.getSubject(); 38 | } 39 | 40 | public boolean isExpired(String token) { 41 | Claims claims = getClaims(token); 42 | return claims.getExpiration().after(new Date(System.currentTimeMillis())); 43 | } 44 | 45 | private Claims getClaims(String token) { 46 | return Jwts.parser().setSigningKey(key).parseClaimsJws(token).getBody(); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /spring-security-authorization/src/main/java/com/example/authorization/JwtUtil.java: -------------------------------------------------------------------------------- 1 | package com.example.authorization; 2 | 3 | import io.jsonwebtoken.Claims; 4 | import io.jsonwebtoken.Jwts; 5 | import io.jsonwebtoken.SignatureAlgorithm; 6 | import io.jsonwebtoken.security.Keys; 7 | import org.springframework.stereotype.Service; 8 | 9 | import java.security.Key; 10 | import java.util.Date; 11 | 12 | @Service 13 | public class JwtUtil { 14 | 15 | private static final int expireInMs = 300 * 10000; 16 | 17 | private final static Key key = Keys.secretKeyFor(SignatureAlgorithm.HS256); 18 | 19 | public String generate(String username) { 20 | return Jwts.builder() 21 | .setSubject(username) 22 | .setIssuer("backendstory.com") 23 | .setIssuedAt(new Date(System.currentTimeMillis())) 24 | .setExpiration(new Date(System.currentTimeMillis() + expireInMs)) 25 | .signWith(key) 26 | .compact(); 27 | } 28 | public boolean validate(String token) { 29 | if (getUsername(token) != null && isExpired(token)) { 30 | return true; 31 | } 32 | return false; 33 | } 34 | 35 | public String getUsername(String token) { 36 | Claims claims = getClaims(token); 37 | return claims.getSubject(); 38 | } 39 | 40 | public boolean isExpired(String token) { 41 | Claims claims = getClaims(token); 42 | return claims.getExpiration().after(new Date(System.currentTimeMillis())); 43 | } 44 | 45 | private Claims getClaims(String token) { 46 | return Jwts.parser().setSigningKey(key).parseClaimsJws(token).getBody(); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /spring-security-authentication-scenario-3/src/main/java/com/backendstory/authentication/JwtUtil.java: -------------------------------------------------------------------------------- 1 | package com.backendstory.authentication; 2 | 3 | import io.jsonwebtoken.Claims; 4 | import io.jsonwebtoken.Jwts; 5 | import io.jsonwebtoken.SignatureAlgorithm; 6 | import io.jsonwebtoken.security.Keys; 7 | import org.springframework.stereotype.Service; 8 | 9 | import java.security.Key; 10 | import java.util.Date; 11 | 12 | @Service 13 | public class JwtUtil { 14 | 15 | private static final int expireInMs = 300 * 1000; 16 | 17 | private final static Key key = Keys.secretKeyFor(SignatureAlgorithm.HS256); 18 | 19 | public String generate(String username) { 20 | return Jwts.builder() 21 | .setSubject(username) 22 | .setIssuer("backendstory.com") 23 | .setIssuedAt(new Date(System.currentTimeMillis())) 24 | .setExpiration(new Date(System.currentTimeMillis() + expireInMs)) 25 | .signWith(key) 26 | .compact(); 27 | } 28 | public boolean validate(String token) { 29 | if (getUsername(token) != null && isExpired(token)) { 30 | return true; 31 | } 32 | return false; 33 | } 34 | 35 | public String getUsername(String token) { 36 | Claims claims = getClaims(token); 37 | return claims.getSubject(); 38 | } 39 | 40 | public boolean isExpired(String token) { 41 | Claims claims = getClaims(token); 42 | return claims.getExpiration().after(new Date(System.currentTimeMillis())); 43 | } 44 | 45 | private Claims getClaims(String token) { 46 | return Jwts.parser().setSigningKey(key).parseClaimsJws(token).getBody(); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /spring-security-authentication-scenario-4/src/main/java/com/backendstory/authentication/JwtUtil.java: -------------------------------------------------------------------------------- 1 | package com.backendstory.authentication; 2 | 3 | import io.jsonwebtoken.Claims; 4 | import io.jsonwebtoken.Jwts; 5 | import io.jsonwebtoken.SignatureAlgorithm; 6 | import io.jsonwebtoken.security.Keys; 7 | import org.springframework.stereotype.Service; 8 | 9 | import java.security.Key; 10 | import java.util.Date; 11 | 12 | @Service 13 | public class JwtUtil { 14 | 15 | private static final int expireInMs = 300 * 1000; 16 | 17 | private final static Key key = Keys.secretKeyFor(SignatureAlgorithm.HS256); 18 | 19 | public String generate(String username) { 20 | return Jwts.builder() 21 | .setSubject(username) 22 | .setIssuer("backendstory.com") 23 | .setIssuedAt(new Date(System.currentTimeMillis())) 24 | .setExpiration(new Date(System.currentTimeMillis() + expireInMs)) 25 | .signWith(key) 26 | .compact(); 27 | } 28 | public boolean validate(String token) { 29 | if (getUsername(token) != null && isExpired(token)) { 30 | return true; 31 | } 32 | return false; 33 | } 34 | 35 | public String getUsername(String token) { 36 | Claims claims = getClaims(token); 37 | return claims.getSubject(); 38 | } 39 | 40 | public boolean isExpired(String token) { 41 | Claims claims = getClaims(token); 42 | return claims.getExpiration().after(new Date(System.currentTimeMillis())); 43 | } 44 | 45 | private Claims getClaims(String token) { 46 | return Jwts.parser().setSigningKey(key).parseClaimsJws(token).getBody(); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /spring-security-authentication-scenario-5/src/main/java/com/backendstory/authentication/JwtUtil.java: -------------------------------------------------------------------------------- 1 | package com.backendstory.authentication; 2 | 3 | import io.jsonwebtoken.Claims; 4 | import io.jsonwebtoken.Jwts; 5 | import io.jsonwebtoken.SignatureAlgorithm; 6 | import io.jsonwebtoken.security.Keys; 7 | import org.springframework.stereotype.Service; 8 | 9 | import java.security.Key; 10 | import java.util.Date; 11 | 12 | @Service 13 | public class JwtUtil { 14 | 15 | private static final int expireInMs = 300 * 1000; 16 | 17 | private final static Key key = Keys.secretKeyFor(SignatureAlgorithm.HS256); 18 | 19 | public String generate(String username) { 20 | return Jwts.builder() 21 | .setSubject(username) 22 | .setIssuer("backendstory.com") 23 | .setIssuedAt(new Date(System.currentTimeMillis())) 24 | .setExpiration(new Date(System.currentTimeMillis() + expireInMs)) 25 | .signWith(key) 26 | .compact(); 27 | } 28 | public boolean validate(String token) { 29 | if (getUsername(token) != null && isExpired(token)) { 30 | return true; 31 | } 32 | return false; 33 | } 34 | 35 | public String getUsername(String token) { 36 | Claims claims = getClaims(token); 37 | return claims.getSubject(); 38 | } 39 | 40 | public boolean isExpired(String token) { 41 | Claims claims = getClaims(token); 42 | return claims.getExpiration().after(new Date(System.currentTimeMillis())); 43 | } 44 | 45 | private Claims getClaims(String token) { 46 | return Jwts.parser().setSigningKey(key).parseClaimsJws(token).getBody(); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /spring-security-authentication-scenario-4/README.md: -------------------------------------------------------------------------------- 1 | # Spring Security Authentication Scenario 2 | 3 | This project is used to explain how authentication architecture works in Spring Security. 4 | 5 | Please visit this blog post if you want to read. 6 | https://backendstory.com/spring-security-authentication-architecture-explained-in-depth/ 7 | 8 | or you can visit this Youtube video if you prefer watching. 9 | https://youtu.be/ElY3rjtukig 10 | 11 | ### How to run: 12 | This project does not use any database, so you do not need to set up anything specific. Just run as it is. 13 | 14 | **Method 1:** Right-click on the `Scenario4.java` and then click `Run`. 15 | 16 | **Method 2:** Run mvn `spring-boot:run` in your terminal. 17 | 18 | ### Why are all classes in one file? 19 | I think it is easier to follow the flow when all of the classes are in the same file. It is just educational purpose, not a best practice to do. 20 | 21 | ### Endpoints: 22 | There are two endpoints available in this project. One for fetching authentication token and one for accessing protected endpoint. 23 | 24 | **Step 1:** You will need to use the following cURL to fetch the authentication token. 25 | ```console 26 | curl --location --request POST 'http://localhost:8080/login' \ 27 | --header 'Content-Type: application/json' \ 28 | --data-raw '{ 29 | "username": "martin", 30 | "password": "123" 31 | }' 32 | ``` 33 | 34 | This request will give you a JWT token that will be used for authentication. An example of the response is below. 35 | ```console 36 | eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJtYXJ0aW4iLCJpc3MiOiJiYWNrZW5kc3RvcnkuY29tIiwiaWF0IjoxNjYxNjcxNTM2LCJleHAiOjE2NjE2NzE1OTZ9.BKRX9eGNzfbqNJ6yNgZjgC6x2Y7aVcZyWM48bsxB9aE 37 | ``` 38 | 39 | **Step 2:** Then, you can hit `/hello` endpoint with the token you got from the cURL request above. 40 | ```console 41 | curl --location --request GET 'http://localhost:8080/hello' \ 42 | --header 'Content-Type: application/json' \ 43 | --header 'Authorization: Bearer ' 44 | ``` 45 | 46 | -------------------------------------------------------------------------------- /spring-security-authentication-scenario-5/README.md: -------------------------------------------------------------------------------- 1 | # Spring Security Authentication Scenario 2 | 3 | This project is used to explain how authentication architecture works in Spring Security. 4 | 5 | Please visit this blog post if you want to read. 6 | https://backendstory.com/spring-security-authentication-architecture-explained-in-depth/ 7 | 8 | or you can visit this Youtube video if you prefer watching. 9 | https://youtu.be/ElY3rjtukig 10 | 11 | ### How to run: 12 | This project does not use any database, so you do not need to set up anything specific. Just run as it is. 13 | 14 | **Method 1:** Right-click on the `Scenario5.java` and then click `Run`. 15 | 16 | **Method 2:** Run mvn `spring-boot:run` in your terminal. 17 | 18 | ### Why are all classes in one file? 19 | I think it is easier to follow the flow when all of the classes are in the same file. It is just educational purpose, not a best practice to do. 20 | 21 | ### Endpoints: 22 | There are two endpoints available in this project. One for fetching authentication token and one for accessing protected endpoint. 23 | 24 | **Step 1:** You will need to use the following cURL to fetch the authentication token. 25 | ```console 26 | curl --location --request POST 'http://localhost:8080/login' \ 27 | --header 'Content-Type: application/json' \ 28 | --data-raw '{ 29 | "username": "martin", 30 | "password": "123" 31 | }' 32 | ``` 33 | 34 | This request will give you a JWT token that will be used for authentication. An example of the response is below. 35 | ```console 36 | eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJtYXJ0aW4iLCJpc3MiOiJiYWNrZW5kc3RvcnkuY29tIiwiaWF0IjoxNjYxNjcxNTM2LCJleHAiOjE2NjE2NzE1OTZ9.BKRX9eGNzfbqNJ6yNgZjgC6x2Y7aVcZyWM48bsxB9aE 37 | ``` 38 | 39 | **Step 2:** Then, you can hit `/hello` endpoint with the token you got from the cURL request above. 40 | ```console 41 | curl --location --request GET 'http://localhost:8080/hello' \ 42 | --header 'Content-Type: application/json' \ 43 | --header 'Authorization: Bearer ' 44 | ``` 45 | 46 | -------------------------------------------------------------------------------- /spring-security-authentication-scenario-3/README.md: -------------------------------------------------------------------------------- 1 | # Spring Security Authentication Scenario 2 | 3 | This project is used to explain how authentication architecture works in Spring Security. 4 | 5 | Please visit the link below if you want to read the blog post. 6 | https://backendstory.com/spring-security-authentication-architecture-explained-in-depth/ 7 | 8 | You can also visit this Youtube video if you prefer watching. 9 | https://youtu.be/ElY3rjtukig 10 | 11 | ### How to run: 12 | This project does not use any database, so you do not need to set up anything specific. Just run as it is. 13 | 14 | **Method 1:** Right-click on the `Scenario3.java` and then click `Run`. 15 | 16 | **Method 2:** Run mvn `spring-boot:run` in your terminal. 17 | 18 | ### Why are all classes in one file? 19 | I think it is easier to follow the flow when all of the classes are in the same file. It is just educational purpose, not a best practice to do. 20 | 21 | ### Endpoints: 22 | There are two endpoints available in this project. One for fetching authentication token and one for accessing protected endpoint. 23 | 24 | **Step 1:** You will need to use the following cURL to fetch the authentication token. 25 | ```console 26 | curl --location --request POST 'http://localhost:8080/login' \ 27 | --header 'Content-Type: application/json' \ 28 | --data-raw '{ 29 | "username": "martin", 30 | "password": "123" 31 | }' 32 | ``` 33 | 34 | 35 | 36 | This request will give you a JWT token that will be used for authentication. An example of the response is below. 37 | ```console 38 | eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJtYXJ0aW4iLCJpc3MiOiJiYWNrZW5kc3RvcnkuY29tIiwiaWF0IjoxNjYxNjcxNTM2LCJleHAiOjE2NjE2NzE1OTZ9.BKRX9eGNzfbqNJ6yNgZjgC6x2Y7aVcZyWM48bsxB9aE 39 | ``` 40 | 41 | **Step 2:** Then, you can hit `/hello` endpoint with the token you got from the cURL request above. 42 | ```console 43 | curl --location --request GET 'http://localhost:8080/hello' \ 44 | --header 'Content-Type: application/json' \ 45 | --header 'Authorization: Bearer ' 46 | ``` 47 | 48 | -------------------------------------------------------------------------------- /spring-security-upgrade/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 2.7.5 9 | 10 | 11 | 4.0.0 12 | 13 | spring-security-upgrade 14 | 15 | 16 | 11 17 | 11 18 | 19 | 20 | 21 | org.springframework.boot 22 | spring-boot-starter-web 23 | 24 | 25 | org.springframework.boot 26 | spring-boot-starter-security 27 | 28 | 29 | org.projectlombok 30 | lombok 31 | 1.18.22 32 | provided 33 | 34 | 35 | io.jsonwebtoken 36 | jjwt-api 37 | 0.11.2 38 | 39 | 40 | io.jsonwebtoken 41 | jjwt-impl 42 | 0.11.2 43 | runtime 44 | 45 | 46 | io.jsonwebtoken 47 | jjwt-jackson 48 | 0.11.2 49 | runtime 50 | 51 | 52 | -------------------------------------------------------------------------------- /spring-security-authentication-scenario-5/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 2.6.3 9 | 10 | 11 | 4.0.0 12 | 13 | spring-security-authentication-scenario-5 14 | 15 | 16 | 11 17 | 11 18 | 19 | 20 | 21 | org.springframework.boot 22 | spring-boot-starter-web 23 | 24 | 25 | org.springframework.boot 26 | spring-boot-starter-security 27 | 28 | 29 | org.projectlombok 30 | lombok 31 | 1.18.22 32 | provided 33 | 34 | 35 | io.jsonwebtoken 36 | jjwt-api 37 | 0.11.2 38 | 39 | 40 | io.jsonwebtoken 41 | jjwt-impl 42 | 0.11.2 43 | runtime 44 | 45 | 46 | io.jsonwebtoken 47 | jjwt-jackson 48 | 0.11.2 49 | runtime 50 | 51 | 52 | -------------------------------------------------------------------------------- /spring-security-authentication-scenario-4/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 2.6.3 9 | 10 | 11 | 4.0.0 12 | 13 | spring-security-authentication-scenario-4 14 | 15 | 16 | 11 17 | 11 18 | 19 | 20 | 21 | 22 | org.springframework.boot 23 | spring-boot-starter-web 24 | 25 | 26 | org.springframework.boot 27 | spring-boot-starter-security 28 | 29 | 30 | org.projectlombok 31 | lombok 32 | 1.18.22 33 | provided 34 | 35 | 36 | io.jsonwebtoken 37 | jjwt-api 38 | 0.11.2 39 | 40 | 41 | io.jsonwebtoken 42 | jjwt-impl 43 | 0.11.2 44 | runtime 45 | 46 | 47 | io.jsonwebtoken 48 | jjwt-jackson 49 | 0.11.2 50 | runtime 51 | 52 | 53 | -------------------------------------------------------------------------------- /spring-security-authentication-scenario-3/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 2.6.3 9 | 10 | 11 | 4.0.0 12 | 13 | spring-security-authentication-scenario-3 14 | 15 | 16 | 11 17 | 11 18 | 19 | 20 | 21 | 22 | org.springframework.boot 23 | spring-boot-starter-web 24 | 25 | 26 | org.springframework.boot 27 | spring-boot-starter-security 28 | 29 | 30 | org.projectlombok 31 | lombok 32 | 1.18.22 33 | provided 34 | 35 | 36 | io.jsonwebtoken 37 | jjwt-api 38 | 0.11.2 39 | 40 | 41 | io.jsonwebtoken 42 | jjwt-impl 43 | 0.11.2 44 | runtime 45 | 46 | 47 | io.jsonwebtoken 48 | jjwt-jackson 49 | 0.11.2 50 | runtime 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /spring-security-upgrade/README.md: -------------------------------------------------------------------------------- 1 | # Spring Security Configuration without WebSecurityConfigurerAdapter 2 | 3 | This project is used to explain how authentication architecture works in Spring Security. It is actually copy of `spring-security-authentication-scenario-5` project in the same repo. 4 | However, Spring Security deprecated WebSecurityConfigurerAdapter since 5.7. This repo removes `WebSecurityConfigurerAdapter` and use up-to-date configuration. 5 | 6 | Please visit this blog post if you want to read. 7 | https://backendstory.com/spring-security-authentication-architecture-explained-in-depth/ 8 | 9 | or you can visit this Youtube video if you prefer watching. 10 | https://youtu.be/ElY3rjtukig 11 | 12 | ### How to run: 13 | This project does not use any database, so you do not need to set up anything specific. Just run as it is. 14 | 15 | **Method 1:** Right-click on the `SpringSecurityUpgrade.java` and then click `Run`. 16 | 17 | **Method 2:** Run mvn `spring-boot:run` in your terminal. 18 | 19 | ### Why are all classes in one file? 20 | I think it is easier to follow the flow when all of the classes are in the same file. It is just educational purpose, not a best practice to do. 21 | 22 | ### Endpoints: 23 | There are two endpoints available in this project. One for fetching authentication token and one for accessing protected endpoint. 24 | 25 | **Step 1:** You will need to use the following cURL to fetch the authentication token. 26 | ```console 27 | curl --location --request POST 'http://localhost:8080/login' \ 28 | --header 'Content-Type: application/json' \ 29 | --data-raw '{ 30 | "username": "martin", 31 | "password": "123" 32 | }' 33 | ``` 34 | 35 | This request will give you a JWT token that will be used for authentication. An example of the response is below. 36 | ```console 37 | eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJtYXJ0aW4iLCJpc3MiOiJiYWNrZW5kc3RvcnkuY29tIiwiaWF0IjoxNjYxNjcxNTM2LCJleHAiOjE2NjE2NzE1OTZ9.BKRX9eGNzfbqNJ6yNgZjgC6x2Y7aVcZyWM48bsxB9aE 38 | ``` 39 | 40 | **Step 2:** Then, you can hit `/hello` endpoint with the token you got from the cURL request above. 41 | ```console 42 | curl --location --request GET 'http://localhost:8080/hello' \ 43 | --header 'Content-Type: application/json' \ 44 | --header 'Authorization: Bearer ' 45 | ``` 46 | 47 | -------------------------------------------------------------------------------- /spring-security-exception-handling/README.md: -------------------------------------------------------------------------------- 1 | # Spring Security Exception Handling Scenario 2 | 3 | This project is used to explain how exception handling mechanism works in Spring Security. 4 | 5 | Please visit this blog post if you want to read. 6 | https://backendstory.com 7 | 8 | ### How to run: 9 | This project does not use any database, so you do not need to set up anything specific. Just run as it is. 10 | 11 | **Method 1:** Right-click on the `ExceptionHandlingApplication.java` and then click `Run`. 12 | 13 | **Method 2:** Run mvn `spring-boot:run` in your terminal. 14 | 15 | ### Why are all classes in one file? 16 | I think it is easier to follow the flow when all of the classes are in the same file. It is just educational purpose, not a best practice to do. 17 | 18 | ### Endpoints: 19 | There are three endpoints available in this project. One for fetching authentication token and and other two for accessing protected endpoint with different roles. 20 | 21 | **Step 1:** You will need to use the following cURL to fetch the authentication token with `ROLE_USER`, so you will be able to access `/user/messages` endpoint. 22 | ```console 23 | curl --location --request POST 'http://localhost:8080/login' \ 24 | --header 'Content-Type: application/json' \ 25 | --data-raw '{ 26 | "username": "martin", 27 | "password": "123" 28 | }' 29 | ``` 30 | 31 | This request will give you a JWT token that will be used for authentication. An example of the response is below. 32 | ```console 33 | eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJtYXJ0aW4iLCJpc3MiOiJiYWNrZW5kc3RvcnkuY29tIiwiaWF0IjoxNjYxNjcxNTM2LCJleHAiOjE2NjE2NzE1OTZ9.BKRX9eGNzfbqNJ6yNgZjgC6x2Y7aVcZyWM48bsxB9aE 34 | ``` 35 | 36 | **Step 2:** Then, you can hit `/user/messages` endpoint with the token you got from the cURL request above. 37 | ```console 38 | curl --location --request GET 'http://localhost:8080/user/messages' \ 39 | --header 'Content-Type: application/json' \ 40 | --header 'Authorization: Bearer ' 41 | ``` 42 | 43 | **Step 3:** If you try to access `/admin/messages` endpoint, you will get Access Denied response. 44 | ```console 45 | curl --location --request GET 'http://localhost:8080/user/messages' \ 46 | --header 'Content-Type: application/json' \ 47 | --header 'Authorization: Bearer ' 48 | ``` 49 | There is no `ROLE_ADMIN` user defined in this project. This is because of showing unauthorized access to an endpoint. 50 | 51 | -------------------------------------------------------------------------------- /spring-security-authorization/README.md: -------------------------------------------------------------------------------- 1 | # Spring Security Authorization Scenario 2 | 3 | This project is used to explain how exception handling mechanism works in Spring Security. 4 | 5 | Please visit this blog post if you want to read. 6 | https://backendstory.com/spring-security-authorization-mechanism/ 7 | 8 | ### How to run: 9 | This project does not use any database, so you do not need to set up anything specific. Just run as it is. 10 | 11 | **Method 1:** Right-click on the `SpringSecurityAuthorizationApplication.java` and then click `Run`. 12 | 13 | **Method 2:** Run mvn `spring-boot:run` in your terminal. 14 | 15 | ### Why are all classes in one file? 16 | I think it is easier to follow the flow when all of the classes are in the same file. It is just educational purpose, not a best practice to do. 17 | 18 | ### Endpoints: 19 | There are three endpoints available in this project. One for fetching authentication token and and other two for accessing protected endpoint with different roles. 20 | 21 | **Step 1:** You will need to use the following cURL to fetch the authentication token with `ROLE_USER`, so you will be able to access `/user/messages` endpoint. 22 | ```console 23 | curl --location --request POST 'http://localhost:8080/login' \ 24 | --header 'Content-Type: application/json' \ 25 | --data-raw '{ 26 | "username": "martin", 27 | "password": "123" 28 | }' 29 | ``` 30 | 31 | This request will give you a JWT token that will be used for authentication. An example of the response is below. 32 | ```console 33 | eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJtYXJ0aW4iLCJpc3MiOiJiYWNrZW5kc3RvcnkuY29tIiwiaWF0IjoxNjYxNjcxNTM2LCJleHAiOjE2NjE2NzE1OTZ9.BKRX9eGNzfbqNJ6yNgZjgC6x2Y7aVcZyWM48bsxB9aE 34 | ``` 35 | 36 | **Step 2:** Then, you can hit `/user/messages` endpoint with the token you got from the cURL request above. 37 | ```console 38 | curl --location --request GET 'http://localhost:8080/user/messages' \ 39 | --header 'Content-Type: application/json' \ 40 | --header 'Authorization: Bearer ' 41 | ``` 42 | 43 | **Step 3:** If you try to access `/admin/messages` endpoint, you will get Access Denied response. 44 | ```console 45 | curl --location --request GET 'http://localhost:8080/user/messages' \ 46 | --header 'Content-Type: application/json' \ 47 | --header 'Authorization: Bearer ' 48 | ``` 49 | There is no `ROLE_ADMIN` user defined in this project. This is because of showing unauthorized access to an endpoint. 50 | 51 | -------------------------------------------------------------------------------- /spring-security-authorization/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 2.6.3 9 | 10 | 11 | 4.0.0 12 | 13 | spring-security-authorization 14 | 15 | 16 | 11 17 | 11 18 | 19 | 20 | 21 | org.springframework.boot 22 | spring-boot-starter-security 23 | 24 | 25 | org.springframework.boot 26 | spring-boot-starter-web 27 | 28 | 29 | 30 | org.projectlombok 31 | lombok 32 | true 33 | 34 | 35 | io.jsonwebtoken 36 | jjwt-api 37 | 0.11.2 38 | 39 | 40 | io.jsonwebtoken 41 | jjwt-impl 42 | 0.11.2 43 | runtime 44 | 45 | 46 | io.jsonwebtoken 47 | jjwt-jackson 48 | 0.11.2 49 | runtime 50 | 51 | 52 | org.springframework.boot 53 | spring-boot-starter-test 54 | test 55 | 56 | 57 | 58 | 59 | 60 | org.springframework.boot 61 | spring-boot-maven-plugin 62 | 63 | 64 | 65 | org.projectlombok 66 | lombok 67 | 68 | 69 | 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /spring-security-exception-handling/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 2.6.3 9 | 10 | 11 | 4.0.0 12 | 13 | spring-security-exception-handling 14 | 15 | 16 | 11 17 | 11 18 | 19 | 20 | 21 | org.springframework.boot 22 | spring-boot-starter-security 23 | 24 | 25 | org.springframework.boot 26 | spring-boot-starter-web 27 | 28 | 29 | 30 | org.projectlombok 31 | lombok 32 | true 33 | 34 | 35 | io.jsonwebtoken 36 | jjwt-api 37 | 0.11.2 38 | 39 | 40 | io.jsonwebtoken 41 | jjwt-impl 42 | 0.11.2 43 | runtime 44 | 45 | 46 | io.jsonwebtoken 47 | jjwt-jackson 48 | 0.11.2 49 | runtime 50 | 51 | 52 | org.springframework.boot 53 | spring-boot-starter-test 54 | test 55 | 56 | 57 | 58 | 59 | 60 | org.springframework.boot 61 | spring-boot-maven-plugin 62 | 63 | 64 | 65 | org.projectlombok 66 | lombok 67 | 68 | 69 | 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 4.0.0 6 | 7 | org.backendstory 8 | parent-project 9 | pom 10 | 1.0-SNAPSHOT 11 | 12 | spring-security-authentication-scenario-3 13 | spring-security-authentication-scenario-4 14 | spring-security-authentication-scenario-5 15 | spring-security-authorization 16 | spring-security-exception-handling 17 | spring-security-upgrade 18 | 19 | 20 | parent-project 21 | 22 | http://www.example.com 23 | 24 | 25 | UTF-8 26 | 1.7 27 | 1.7 28 | 29 | 30 | 31 | 32 | junit 33 | junit 34 | 4.13.1 35 | test 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | maven-clean-plugin 45 | 3.1.0 46 | 47 | 48 | 49 | maven-resources-plugin 50 | 3.0.2 51 | 52 | 53 | maven-compiler-plugin 54 | 3.8.0 55 | 56 | 57 | maven-surefire-plugin 58 | 2.22.1 59 | 60 | 61 | maven-jar-plugin 62 | 3.0.2 63 | 64 | 65 | maven-install-plugin 66 | 2.5.2 67 | 68 | 69 | maven-deploy-plugin 70 | 2.8.2 71 | 72 | 73 | 74 | maven-site-plugin 75 | 3.7.1 76 | 77 | 78 | maven-project-info-reports-plugin 79 | 3.0.0 80 | 81 | 82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /spring-security-authentication-scenario-3/src/main/java/com/backendstory/authentication/Scenario3.java: -------------------------------------------------------------------------------- 1 | package com.backendstory.authentication; 2 | 3 | import lombok.Data; 4 | import lombok.NoArgsConstructor; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.boot.SpringApplication; 7 | import org.springframework.boot.autoconfigure.SpringBootApplication; 8 | import org.springframework.context.annotation.Bean; 9 | import org.springframework.context.annotation.Configuration; 10 | import org.springframework.http.ResponseEntity; 11 | import org.springframework.security.authentication.AuthenticationManager; 12 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 13 | import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; 14 | import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; 15 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 16 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; 17 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; 18 | import org.springframework.security.config.http.SessionCreationPolicy; 19 | import org.springframework.security.core.context.SecurityContextHolder; 20 | import org.springframework.security.core.userdetails.User; 21 | import org.springframework.security.core.userdetails.UserDetails; 22 | import org.springframework.security.core.userdetails.UsernameNotFoundException; 23 | import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; 24 | import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; 25 | import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; 26 | import org.springframework.stereotype.Component; 27 | import org.springframework.stereotype.Service; 28 | import org.springframework.web.bind.annotation.GetMapping; 29 | import org.springframework.web.bind.annotation.PostMapping; 30 | import org.springframework.web.bind.annotation.RequestBody; 31 | import org.springframework.web.bind.annotation.RestController; 32 | import org.springframework.web.filter.OncePerRequestFilter; 33 | 34 | import javax.servlet.FilterChain; 35 | import javax.servlet.ServletException; 36 | import javax.servlet.http.HttpServletRequest; 37 | import javax.servlet.http.HttpServletResponse; 38 | import java.io.IOException; 39 | import java.util.ArrayList; 40 | import java.util.HashMap; 41 | import java.util.Map; 42 | 43 | @SpringBootApplication 44 | public class Scenario3 { 45 | 46 | public static void main(String[] args) { 47 | SpringApplication.run(Scenario3.class, args); 48 | } 49 | 50 | // Creating a bean for password encryption 51 | @Bean 52 | public BCryptPasswordEncoder getBCryptPasswordEncoder() { 53 | return new BCryptPasswordEncoder(); 54 | } 55 | } 56 | 57 | @RestController 58 | class BasicController { 59 | @Autowired 60 | private JwtUtil jwtUtil; 61 | 62 | // injecting authentication manager 63 | @Autowired 64 | private AuthenticationManager authenticationManager; 65 | 66 | @PostMapping("login") 67 | public ResponseEntity login(@RequestBody LoginRequestDTO request) { 68 | // Creating UsernamePasswordAuthenticationToken object 69 | // to send it to authentication manager. 70 | // Attention! We used two parameters constructor. 71 | // It sets authentication false by doing this.setAuthenticated(false); 72 | UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(request.getUsername(), request.getPassword()); 73 | // we let the manager do its job. 74 | authenticationManager.authenticate(token); 75 | // if there is no exception thrown from authentication manager, 76 | // we can generate a JWT token and give it to user. 77 | String jwt = jwtUtil.generate(request.getUsername()); 78 | return ResponseEntity.ok(jwt); 79 | } 80 | 81 | @GetMapping("/hello") 82 | public ResponseEntity get(){ 83 | return ResponseEntity.ok("Hello"); 84 | } 85 | } 86 | 87 | @Data 88 | @NoArgsConstructor 89 | class LoginRequestDTO { 90 | private String username; 91 | private String password; 92 | } 93 | 94 | /* 95 | * This is Spring Security configuration step 96 | */ 97 | @Configuration 98 | @EnableWebSecurity 99 | @EnableGlobalMethodSecurity(prePostEnabled = true) 100 | class WebSecurityConfiguration extends WebSecurityConfigurerAdapter { 101 | 102 | // Custom filter 103 | @Autowired 104 | private JwtTokenFilter jwtTokenFilter; 105 | 106 | // Custom UserDetailsService 107 | @Autowired 108 | private UserDetailsService userDetailsService; 109 | 110 | @Autowired 111 | BCryptPasswordEncoder passwordEncoder; 112 | 113 | @Autowired 114 | public void configurePasswordEncoder(AuthenticationManagerBuilder builder) throws Exception { 115 | // adding custom UserDetailsService and encryption bean to Authentication Manager 116 | builder.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder); 117 | } 118 | 119 | @Bean 120 | public AuthenticationManager getAuthenticationManager() throws Exception { 121 | return super.authenticationManagerBean(); 122 | } 123 | 124 | @Override 125 | protected void configure(HttpSecurity http) throws Exception { 126 | http 127 | // disabling csrf since we won't use form login 128 | .csrf().disable() 129 | 130 | // giving every permission to every request for /login endpoint 131 | .authorizeRequests().antMatchers("/login").permitAll() 132 | // for everything else, the user has to be authenticated 133 | .anyRequest().authenticated() 134 | // setting stateless session, because we choose to implement Rest API 135 | .and().sessionManagement() 136 | .sessionCreationPolicy(SessionCreationPolicy.STATELESS); 137 | 138 | // adding the custom filter before UsernamePasswordAuthenticationFilter in the filter chain 139 | http.addFilterBefore(jwtTokenFilter, UsernamePasswordAuthenticationFilter.class); 140 | } 141 | } 142 | 143 | /* 144 | * Custom filter will run once per request. We add this to Filter Chain 145 | */ 146 | @Component 147 | class JwtTokenFilter extends OncePerRequestFilter { 148 | // Simple JWT implementation 149 | @Autowired 150 | private JwtUtil jwtUtil; 151 | 152 | // Spring Security will call this method during filter chain execution 153 | @Override 154 | protected void doFilterInternal(HttpServletRequest httpServletRequest, 155 | HttpServletResponse httpServletResponse, 156 | FilterChain filterChain) throws ServletException, IOException { 157 | 158 | // trying to find Authorization header 159 | final String authorizationHeader = httpServletRequest.getHeader("Authorization"); 160 | if (authorizationHeader == null || authorizationHeader.isEmpty() || !authorizationHeader.startsWith("Bearer")){ 161 | // if Authorization header does not exist, then skip this filter 162 | // and continue to execute next filter class 163 | filterChain.doFilter(httpServletRequest, httpServletResponse); 164 | return; 165 | } 166 | 167 | final String token = authorizationHeader.split(" ")[1].trim(); 168 | if (!jwtUtil.validate(token)) { 169 | // if token is not valid, then skip this filter 170 | // and continue to execute next filter class. 171 | // This means authentication is not successful since token is invalid. 172 | filterChain.doFilter(httpServletRequest, httpServletResponse); 173 | return; 174 | } 175 | 176 | // Authorization header exists, token is valid. So, we can authenticate. 177 | String username = jwtUtil.getUsername(token); 178 | // initializing UsernamePasswordAuthenticationToken with its 3 parameter constructor 179 | // because it sets super.setAuthenticated(true); in that constructor. 180 | UsernamePasswordAuthenticationToken upassToken = new UsernamePasswordAuthenticationToken(username, null, new ArrayList<>()); 181 | upassToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(httpServletRequest)); 182 | // finally, give the authentication token to Spring Security Context 183 | SecurityContextHolder.getContext().setAuthentication(upassToken); 184 | 185 | // end of the method, so go for next filter class 186 | filterChain.doFilter(httpServletRequest, httpServletResponse); 187 | } 188 | } 189 | 190 | /* 191 | * Custom UserDetailsService implementation 192 | */ 193 | @Service 194 | class UserDetailsService implements org.springframework.security.core.userdetails.UserDetailsService { 195 | @Autowired 196 | BCryptPasswordEncoder passwordEncoder; 197 | 198 | @Override 199 | public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { 200 | // This is where you should fetch the user from database. 201 | // We keep it simple to focus on authentication flow. 202 | Map users = new HashMap<>(); 203 | users.put("martin", passwordEncoder.encode("123")); 204 | if (users.containsKey(username)) 205 | return new User(username, users.get(username), new ArrayList<>()); 206 | // if this is thrown, then we won't generate JWT token. 207 | throw new UsernameNotFoundException(username); 208 | } 209 | } -------------------------------------------------------------------------------- /spring-security-authorization/src/main/java/com/example/authorization/SpringSecurityAuthorizationApplication.java: -------------------------------------------------------------------------------- 1 | package com.example.authorization; 2 | 3 | import com.example.authorization.JwtUtil; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.boot.SpringApplication; 9 | import org.springframework.boot.autoconfigure.SpringBootApplication; 10 | import org.springframework.context.annotation.Bean; 11 | import org.springframework.context.annotation.Configuration; 12 | import org.springframework.http.ResponseEntity; 13 | import org.springframework.security.authentication.AuthenticationManager; 14 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 15 | import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; 16 | import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; 17 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 18 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; 19 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; 20 | import org.springframework.security.config.http.SessionCreationPolicy; 21 | import org.springframework.security.core.authority.SimpleGrantedAuthority; 22 | import org.springframework.security.core.context.SecurityContextHolder; 23 | import org.springframework.security.core.userdetails.User; 24 | import org.springframework.security.core.userdetails.UserDetails; 25 | import org.springframework.security.core.userdetails.UsernameNotFoundException; 26 | import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; 27 | import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; 28 | import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; 29 | import org.springframework.stereotype.Component; 30 | import org.springframework.stereotype.Service; 31 | import org.springframework.web.bind.annotation.GetMapping; 32 | import org.springframework.web.bind.annotation.PostMapping; 33 | import org.springframework.web.bind.annotation.RequestBody; 34 | import org.springframework.web.bind.annotation.RestController; 35 | import org.springframework.web.filter.OncePerRequestFilter; 36 | 37 | import javax.annotation.PostConstruct; 38 | import javax.annotation.security.RolesAllowed; 39 | import javax.servlet.FilterChain; 40 | import javax.servlet.ServletException; 41 | import javax.servlet.http.HttpServletRequest; 42 | import javax.servlet.http.HttpServletResponse; 43 | import java.io.IOException; 44 | import java.util.*; 45 | 46 | @SpringBootApplication 47 | public class SpringSecurityAuthorizationApplication { 48 | 49 | public static void main(String[] args) { 50 | SpringApplication.run(SpringSecurityAuthorizationApplication.class, args); 51 | } 52 | 53 | // Creating a bean for password encryption 54 | @Bean 55 | public BCryptPasswordEncoder getBCryptPasswordEncoder() { 56 | return new BCryptPasswordEncoder(); 57 | } 58 | } 59 | 60 | @RestController 61 | class BasicController { 62 | @Autowired 63 | private JwtUtil jwtUtil; 64 | 65 | // injecting authentication manager 66 | @Autowired 67 | private AuthenticationManager authenticationManager; 68 | 69 | @PostMapping("login") 70 | public ResponseEntity login(@RequestBody LoginRequestDTO request) { 71 | // Creating UsernamePasswordAuthenticationToken object 72 | // to send it to authentication manager. 73 | // Attention! We used two parameters constructor. 74 | // It sets authentication false by doing this.setAuthenticated(false); 75 | UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(request.getUsername(), request.getPassword()); 76 | // we let the manager do its job. 77 | authenticationManager.authenticate(token); 78 | // if there is no exception thrown from authentication manager, 79 | // we can generate a JWT token and give it to user. 80 | String jwt = jwtUtil.generate(request.getUsername()); 81 | return ResponseEntity.ok(jwt); 82 | } 83 | 84 | @GetMapping("/user/messages") 85 | @RolesAllowed("ROLE_USER") 86 | public ResponseEntity getUserMessages(){ 87 | List messages = new ArrayList<>(List.of("Hello", "World")); 88 | return ResponseEntity.ok(messages.toString()); 89 | } 90 | 91 | @GetMapping("/admin/messages") 92 | @RolesAllowed("ROLE_ADMIN") 93 | public ResponseEntity getAdminMessages(){ 94 | List messages = new ArrayList<>(List.of("Top", "Secret")); 95 | return ResponseEntity.ok(messages.toString()); 96 | } 97 | } 98 | 99 | @Data 100 | @NoArgsConstructor 101 | class LoginRequestDTO { 102 | private String username; 103 | private String password; 104 | } 105 | 106 | /* 107 | * This is Spring Security configuration step 108 | */ 109 | @Configuration 110 | @EnableWebSecurity 111 | @EnableGlobalMethodSecurity( 112 | securedEnabled = false, // enables Spring's Secured annotation. 113 | jsr250Enabled = true, // enables the JSR-250 standard java security annotations, like @RolesAllowed 114 | prePostEnabled = false) // enables Spring's PreAuthorize and PostAuthorize annotations 115 | class WebSecurityConfiguration extends WebSecurityConfigurerAdapter { 116 | 117 | // Custom filter 118 | @Autowired 119 | private JwtTokenFilter jwtTokenFilter; 120 | 121 | // Custom UserDetailsService 122 | @Autowired 123 | private UserDetailsService userDetailsService; 124 | 125 | @Autowired 126 | BCryptPasswordEncoder passwordEncoder; 127 | 128 | @Autowired 129 | public void configurePasswordEncoder(AuthenticationManagerBuilder builder) throws Exception { 130 | // adding custom UserDetailsService and encryption bean to Authentication Manager 131 | builder.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder); 132 | } 133 | 134 | @Bean 135 | public AuthenticationManager getAuthenticationManager() throws Exception { 136 | return super.authenticationManagerBean(); 137 | } 138 | 139 | @Override 140 | protected void configure(HttpSecurity http) throws Exception { 141 | http 142 | // disabling csrf since we won't use form login 143 | .csrf().disable() 144 | // giving permission to every request for /login endpoint 145 | .authorizeRequests().antMatchers("/login").permitAll() 146 | // only ROLE_USER can access to /user/** endpoint pattern 147 | .and().authorizeRequests().antMatchers("/user/**").hasRole("USER") 148 | // only ROLE_ADMIN can access to /admin/** endpoint pattern 149 | .and().authorizeRequests().antMatchers("/admin/**").hasRole("ADMIN") 150 | // for everything else, the user has to be authenticated 151 | .anyRequest().authenticated() 152 | // setting stateless session, because we choose to implement Rest API 153 | .and().sessionManagement() 154 | .sessionCreationPolicy(SessionCreationPolicy.STATELESS); 155 | 156 | // adding the custom filter before UsernamePasswordAuthenticationFilter in the filter chain 157 | http.addFilterBefore(jwtTokenFilter, UsernamePasswordAuthenticationFilter.class); 158 | } 159 | } 160 | 161 | /* 162 | * Custom filter will run once per request. We add this to Filter Chain 163 | */ 164 | @Component 165 | class JwtTokenFilter extends OncePerRequestFilter { 166 | // Simple JWT implementation 167 | @Autowired 168 | private JwtUtil jwtUtil; 169 | @Autowired 170 | private UserDetailsService userDetailsService; 171 | 172 | // Spring Security will call this method during filter chain execution 173 | @Override 174 | protected void doFilterInternal(HttpServletRequest httpServletRequest, 175 | HttpServletResponse httpServletResponse, 176 | FilterChain filterChain) throws ServletException, IOException { 177 | 178 | // trying to find Authorization header 179 | final String authorizationHeader = httpServletRequest.getHeader("Authorization"); 180 | if (authorizationHeader == null || authorizationHeader.isEmpty() || !authorizationHeader.startsWith("Bearer")){ 181 | // if Authorization header does not exist, then skip this filter 182 | // and continue to execute next filter class 183 | filterChain.doFilter(httpServletRequest, httpServletResponse); 184 | return; 185 | } 186 | 187 | final String token = authorizationHeader.split(" ")[1].trim(); 188 | if (!jwtUtil.validate(token)) { 189 | // if token is not valid, then skip this filter 190 | // and continue to execute next filter class. 191 | // This means authentication is not successful since token is invalid. 192 | filterChain.doFilter(httpServletRequest, httpServletResponse); 193 | return; 194 | } 195 | 196 | // Authorization header exists, token is valid. So, we can authenticate. 197 | // We will get username from token and use it to get user details from UserDetailsService 198 | String username = jwtUtil.getUsername(token); 199 | UserDetails user = userDetailsService.loadUserByUsername(username); 200 | 201 | // initializing UsernamePasswordAuthenticationToken with its 3 parameter constructor 202 | // because it sets super.setAuthenticated(true); in that constructor. 203 | UsernamePasswordAuthenticationToken upassToken = new UsernamePasswordAuthenticationToken(user.getUsername(), null, user.getAuthorities()); 204 | upassToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(httpServletRequest)); 205 | 206 | // finally, give the authentication token to Spring Security Context 207 | SecurityContextHolder.getContext().setAuthentication(upassToken); 208 | 209 | // end of the method, so go for next filter class 210 | filterChain.doFilter(httpServletRequest, httpServletResponse); 211 | } 212 | } 213 | 214 | @Data 215 | @Builder 216 | class UserDTO { 217 | private String username; 218 | private String password; 219 | private String role; 220 | } 221 | 222 | /* 223 | * Custom UserDetailsService implementation 224 | */ 225 | @Service 226 | class UserDetailsService implements org.springframework.security.core.userdetails.UserDetailsService { 227 | // In memory user database 228 | private static final Map users = new HashMap<>(); 229 | 230 | @Autowired 231 | BCryptPasswordEncoder passwordEncoder; 232 | 233 | // Initializing user database 234 | @PostConstruct 235 | public void init() { 236 | UserDTO userDTO = UserDTO.builder() 237 | .username("martin") 238 | .password(passwordEncoder.encode("123")) 239 | .role("ROLE_USER") 240 | .build(); 241 | users.put("martin", userDTO); 242 | } 243 | 244 | @Override 245 | public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { 246 | // This is where you should fetch the user from database. 247 | if (users.containsKey(username)) { 248 | UserDTO userDTO = users.get(username); 249 | return new User(userDTO.getUsername(), userDTO.getPassword(), Arrays.asList(new SimpleGrantedAuthority(userDTO.getRole()))); 250 | } 251 | // if this is thrown, then we won't generate JWT token. 252 | throw new UsernameNotFoundException(username); 253 | } 254 | } 255 | 256 | 257 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /spring-security-authentication-scenario-4/src/main/java/com/backendstory/authentication/Scenario4.java: -------------------------------------------------------------------------------- 1 | package com.backendstory.authentication; 2 | 3 | import lombok.Data; 4 | import lombok.NoArgsConstructor; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.boot.SpringApplication; 7 | import org.springframework.boot.autoconfigure.SpringBootApplication; 8 | import org.springframework.context.annotation.Bean; 9 | import org.springframework.context.annotation.Configuration; 10 | import org.springframework.http.ResponseEntity; 11 | import org.springframework.security.authentication.AuthenticationManager; 12 | import org.springframework.security.authentication.AuthenticationProvider; 13 | import org.springframework.security.authentication.BadCredentialsException; 14 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 15 | import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; 16 | import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; 17 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 18 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; 19 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; 20 | import org.springframework.security.config.http.SessionCreationPolicy; 21 | import org.springframework.security.core.Authentication; 22 | import org.springframework.security.core.AuthenticationException; 23 | import org.springframework.security.core.context.SecurityContextHolder; 24 | import org.springframework.security.core.userdetails.User; 25 | import org.springframework.security.core.userdetails.UserDetails; 26 | import org.springframework.security.core.userdetails.UsernameNotFoundException; 27 | import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; 28 | import org.springframework.security.crypto.password.PasswordEncoder; 29 | import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; 30 | import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; 31 | import org.springframework.stereotype.Component; 32 | import org.springframework.stereotype.Service; 33 | import org.springframework.web.bind.annotation.GetMapping; 34 | import org.springframework.web.bind.annotation.PostMapping; 35 | import org.springframework.web.bind.annotation.RequestBody; 36 | import org.springframework.web.bind.annotation.RestController; 37 | import org.springframework.web.filter.OncePerRequestFilter; 38 | 39 | import javax.servlet.FilterChain; 40 | import javax.servlet.ServletException; 41 | import javax.servlet.http.HttpServletRequest; 42 | import javax.servlet.http.HttpServletResponse; 43 | import java.io.IOException; 44 | import java.util.ArrayList; 45 | import java.util.HashMap; 46 | import java.util.Map; 47 | 48 | @SpringBootApplication 49 | public class Scenario4 { 50 | 51 | public static void main(String[] args) { 52 | SpringApplication.run(Scenario4.class, args); 53 | } 54 | 55 | // Creating a bean for password encryption 56 | @Bean 57 | public BCryptPasswordEncoder getBCryptPasswordEncoder() { 58 | return new BCryptPasswordEncoder(); 59 | } 60 | } 61 | 62 | @RestController 63 | class BasicController { 64 | @Autowired 65 | private JwtUtil jwtUtil; 66 | 67 | // injecting authentication manager 68 | @Autowired 69 | private AuthenticationManager authenticationManager; 70 | 71 | @PostMapping("login") 72 | public ResponseEntity login(@RequestBody LoginRequestDTO request) { 73 | // Creating UsernamePasswordAuthenticationToken object 74 | // to send it to authentication manager. 75 | // Attention! We used two parameters constructor. 76 | // It sets authentication false by doing this.setAuthenticated(false); 77 | UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(request.getUsername(), request.getPassword()); 78 | // we let the manager do its job. 79 | authenticationManager.authenticate(token); 80 | // if there is no exception thrown from authentication manager, 81 | // we can generate a JWT token and give it to user. 82 | String jwt = jwtUtil.generate(request.getUsername()); 83 | return ResponseEntity.ok(jwt); 84 | } 85 | 86 | @GetMapping("/hello") 87 | public ResponseEntity get(){ 88 | return ResponseEntity.ok("Hello"); 89 | } 90 | } 91 | 92 | @Data 93 | @NoArgsConstructor 94 | class LoginRequestDTO { 95 | private String username; 96 | private String password; 97 | } 98 | 99 | @Configuration 100 | @EnableWebSecurity 101 | @EnableGlobalMethodSecurity(prePostEnabled = true) 102 | class WebSecurityConfiguration extends WebSecurityConfigurerAdapter { 103 | 104 | @Autowired 105 | private JwtTokenFilter jwtTokenFilter; 106 | 107 | // @Autowired 108 | // private UserDetailsService userDetailsService; 109 | 110 | @Autowired 111 | BCryptPasswordEncoder passwordEncoder; 112 | 113 | @Autowired 114 | JwtAuthenticationProvider customAuthenticationProvider; 115 | 116 | // we are not using default authentication provider, 117 | // so we do not need to set these here. 118 | // we set these things in the our custom authentication provider. 119 | // @Autowired 120 | // public void configurePasswordEncoder(AuthenticationManagerBuilder builder) throws Exception { 121 | // builder.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder); 122 | // } 123 | 124 | @Bean 125 | public AuthenticationManager getAuthenticationManager() throws Exception { 126 | return super.authenticationManagerBean(); 127 | } 128 | 129 | // adding our custom authentication provider 130 | // authentication manager will call this customer provider's 131 | // authenticate method from now on. 132 | @Override 133 | protected void configure(AuthenticationManagerBuilder auth) { 134 | auth.authenticationProvider(customAuthenticationProvider); 135 | } 136 | 137 | @Override 138 | protected void configure(HttpSecurity http) throws Exception { 139 | http 140 | // disabling csrf since we won't use form login 141 | .csrf().disable() 142 | 143 | // giving every permission to every request for /login endpoint 144 | .authorizeRequests().antMatchers("/login").permitAll() 145 | // for everything else, the user has to be authenticated 146 | .anyRequest().authenticated() 147 | // setting stateless session, because we choose to implement Rest API 148 | .and().sessionManagement() 149 | .sessionCreationPolicy(SessionCreationPolicy.STATELESS); 150 | 151 | // adding the custom filter before UsernamePasswordAuthenticationFilter in the filter chain 152 | http.addFilterBefore(jwtTokenFilter, UsernamePasswordAuthenticationFilter.class); 153 | } 154 | } 155 | 156 | 157 | /* 158 | * Custom filter will run once per request. We add this to Filter Chain 159 | */ 160 | @Component 161 | class JwtTokenFilter extends OncePerRequestFilter { 162 | // Simple JWT implementation 163 | @Autowired 164 | private JwtUtil jwtUtil; 165 | 166 | // Spring Security will call this method during filter chain execution 167 | @Override 168 | protected void doFilterInternal(HttpServletRequest httpServletRequest, 169 | HttpServletResponse httpServletResponse, 170 | FilterChain filterChain) throws ServletException, IOException { 171 | 172 | // trying to find Authorization header 173 | final String authorizationHeader = httpServletRequest.getHeader("Authorization"); 174 | if (authorizationHeader == null || authorizationHeader.isEmpty() || !authorizationHeader.startsWith("Bearer")){ 175 | // if Authorization header does not exist, then skip this filter 176 | // and continue to execute next filter class 177 | filterChain.doFilter(httpServletRequest, httpServletResponse); 178 | return; 179 | } 180 | 181 | final String token = authorizationHeader.split(" ")[1].trim(); 182 | if (!jwtUtil.validate(token)) { 183 | // if token is not valid, then skip this filter 184 | // and continue to execute next filter class. 185 | // This means authentication is not successful since token is invalid. 186 | filterChain.doFilter(httpServletRequest, httpServletResponse); 187 | return; 188 | } 189 | 190 | // Authorization header exists, token is valid. So, we can authenticate. 191 | String username = jwtUtil.getUsername(token); 192 | // initializing UsernamePasswordAuthenticationToken with its 3 parameter constructor 193 | // because it sets super.setAuthenticated(true); in that constructor. 194 | UsernamePasswordAuthenticationToken upassToken = new UsernamePasswordAuthenticationToken(username, null, new ArrayList<>()); 195 | upassToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(httpServletRequest)); 196 | // finally, give the authentication token to Spring Security Context 197 | SecurityContextHolder.getContext().setAuthentication(upassToken); 198 | 199 | // end of the method, so go for next filter class 200 | filterChain.doFilter(httpServletRequest, httpServletResponse); 201 | } 202 | } 203 | 204 | @Component 205 | class JwtAuthenticationProvider implements AuthenticationProvider { 206 | // Injecting available encryption bean 207 | @Autowired 208 | private PasswordEncoder passwordEncoder; 209 | 210 | // Injecting our custom UserDetailsService implementation 211 | @Autowired 212 | private UserDetailsService userDetailsService; 213 | 214 | @Override 215 | public Authentication authenticate(Authentication authentication) throws AuthenticationException { 216 | // In BasicController.login() method, we call authenticationManager.authenticate(token) 217 | // Then, Authentication Manager calls AuthenticationProvider's authenticate method. 218 | // Since JwtAuthenticationProvider is our custom authentication provider, 219 | // this method will be executed. 220 | String username = authentication.getName(); 221 | String password = String.valueOf(authentication.getCredentials()); 222 | 223 | // Fetching user as wrapped with UserDetails object 224 | UserDetails userDetails = userDetailsService.loadUserByUsername(username); 225 | 226 | // If user is not null, then we check if password matches 227 | if (userDetails != null){ 228 | if (passwordEncoder.matches(password, userDetails.getPassword())){ 229 | // if it matches, then we can initialize UsernamePasswordAuthenticationToken. 230 | // Attention! We used its 3 parameters constructor. 231 | UsernamePasswordAuthenticationToken authenticationToken = 232 | new UsernamePasswordAuthenticationToken(username, password, userDetails.getAuthorities()); 233 | return authenticationToken; 234 | } 235 | } 236 | throw new BadCredentialsException("Error!!"); 237 | } 238 | 239 | // Authentication Manager checks if the token is supported by this filter 240 | // to avoid unnecessary checks. 241 | @Override 242 | public boolean supports(Class authenticationType) { 243 | return UsernamePasswordAuthenticationToken.class.equals(authenticationType); 244 | } 245 | } 246 | 247 | /* 248 | * Custom UserDetailsService implementation 249 | */ 250 | @Service 251 | class UserDetailsService implements org.springframework.security.core.userdetails.UserDetailsService { 252 | @Autowired 253 | BCryptPasswordEncoder passwordEncoder; 254 | 255 | @Override 256 | public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { 257 | // This is where you should fetch the user from database. 258 | // We keep it simple to focus on authentication flow. 259 | Map users = new HashMap<>(); 260 | users.put("martin", passwordEncoder.encode("123")); 261 | if (users.containsKey(username)) 262 | return new User(username, users.get(username), new ArrayList<>()); 263 | // if this is thrown, then we won't generate JWT token. 264 | throw new UsernameNotFoundException(username); 265 | } 266 | } -------------------------------------------------------------------------------- /spring-security-exception-handling/src/main/java/authorization/ExceptionHandlingApplication.java: -------------------------------------------------------------------------------- 1 | package authorization; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.boot.SpringApplication; 9 | import org.springframework.boot.autoconfigure.SpringBootApplication; 10 | import org.springframework.context.annotation.Bean; 11 | import org.springframework.context.annotation.Configuration; 12 | import org.springframework.http.ResponseEntity; 13 | import org.springframework.security.access.AccessDeniedException; 14 | import org.springframework.security.authentication.AuthenticationManager; 15 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 16 | import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; 17 | import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; 18 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 19 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; 20 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; 21 | import org.springframework.security.config.http.SessionCreationPolicy; 22 | import org.springframework.security.core.AuthenticationException; 23 | import org.springframework.security.core.authority.SimpleGrantedAuthority; 24 | import org.springframework.security.core.context.SecurityContextHolder; 25 | import org.springframework.security.core.userdetails.User; 26 | import org.springframework.security.core.userdetails.UserDetails; 27 | import org.springframework.security.core.userdetails.UsernameNotFoundException; 28 | import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; 29 | import org.springframework.security.web.AuthenticationEntryPoint; 30 | import org.springframework.security.web.access.AccessDeniedHandler; 31 | import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; 32 | import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; 33 | import org.springframework.stereotype.Component; 34 | import org.springframework.stereotype.Service; 35 | import org.springframework.web.bind.annotation.GetMapping; 36 | import org.springframework.web.bind.annotation.PostMapping; 37 | import org.springframework.web.bind.annotation.RequestBody; 38 | import org.springframework.web.bind.annotation.RestController; 39 | import org.springframework.web.filter.OncePerRequestFilter; 40 | 41 | import javax.annotation.PostConstruct; 42 | import javax.annotation.security.RolesAllowed; 43 | import javax.servlet.FilterChain; 44 | import javax.servlet.ServletException; 45 | import javax.servlet.http.HttpServletRequest; 46 | import javax.servlet.http.HttpServletResponse; 47 | import java.io.IOException; 48 | import java.util.*; 49 | 50 | @SpringBootApplication 51 | public class ExceptionHandlingApplication { 52 | 53 | public static void main(String[] args) { 54 | SpringApplication.run(ExceptionHandlingApplication.class, args); 55 | } 56 | 57 | // Creating a bean for password encryption 58 | @Bean 59 | public BCryptPasswordEncoder getBCryptPasswordEncoder() { 60 | return new BCryptPasswordEncoder(); 61 | } 62 | } 63 | 64 | @RestController 65 | class BasicController { 66 | @Autowired 67 | private JwtUtil jwtUtil; 68 | 69 | // injecting authentication manager 70 | @Autowired 71 | private AuthenticationManager authenticationManager; 72 | 73 | @PostMapping("login") 74 | public ResponseEntity login(@RequestBody LoginRequestDTO request) { 75 | // Creating UsernamePasswordAuthenticationToken object 76 | // to send it to authentication manager. 77 | // Attention! We used two parameters constructor. 78 | // It sets authentication false by doing this.setAuthenticated(false); 79 | UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(request.getUsername(), request.getPassword()); 80 | // we let the manager do its job. 81 | authenticationManager.authenticate(token); 82 | // if there is no exception thrown from authentication manager, 83 | // we can generate a JWT token and give it to user. 84 | String jwt = jwtUtil.generate(request.getUsername()); 85 | return ResponseEntity.ok(jwt); 86 | } 87 | 88 | @GetMapping("/user/messages") 89 | @RolesAllowed("ROLE_USER") 90 | public ResponseEntity getUserMessages(){ 91 | List messages = new ArrayList<>(List.of("Hello", "World")); 92 | return ResponseEntity.ok(messages.toString()); 93 | } 94 | 95 | @GetMapping("/admin/messages") 96 | @RolesAllowed("ROLE_ADMIN") 97 | public ResponseEntity getAdminMessages(){ 98 | List messages = new ArrayList<>(List.of("Top", "Secret")); 99 | return ResponseEntity.ok(messages.toString()); 100 | } 101 | } 102 | 103 | @Data 104 | @NoArgsConstructor 105 | class LoginRequestDTO { 106 | private String username; 107 | private String password; 108 | } 109 | 110 | /* 111 | * This is Spring Security configuration step 112 | */ 113 | @Configuration 114 | @EnableWebSecurity 115 | @EnableGlobalMethodSecurity( 116 | securedEnabled = false, // enables Spring's Secured annotation. 117 | jsr250Enabled = true, // enables the JSR-250 standard java security annotations, like @RolesAllowed 118 | prePostEnabled = false) // enables Spring's PreAuthorize and PostAuthorize annotations 119 | class WebSecurityConfiguration extends WebSecurityConfigurerAdapter { 120 | 121 | // Custom filter 122 | @Autowired 123 | private JwtTokenFilter jwtTokenFilter; 124 | 125 | // Custom UserDetailsService 126 | @Autowired 127 | private UserDetailsService userDetailsService; 128 | 129 | @Autowired 130 | BCryptPasswordEncoder passwordEncoder; 131 | 132 | @Autowired 133 | public void configurePasswordEncoder(AuthenticationManagerBuilder builder) throws Exception { 134 | // adding custom UserDetailsService and encryption bean to Authentication Manager 135 | builder.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder); 136 | } 137 | 138 | @Bean 139 | public AuthenticationManager getAuthenticationManager() throws Exception { 140 | return super.authenticationManagerBean(); 141 | } 142 | 143 | @Override 144 | protected void configure(HttpSecurity http) throws Exception { 145 | http 146 | // disabling csrf since we won't use form login 147 | .csrf().disable() 148 | // giving permission to every request for /login endpoint 149 | .authorizeRequests().antMatchers("/login").permitAll() 150 | // only ROLE_USER can access to /user/** endpoint pattern 151 | .and().authorizeRequests().antMatchers("/user/**").hasRole("USER") 152 | // only ROLE_ADMIN can access to /admin/** endpoint pattern 153 | .and().authorizeRequests().antMatchers("/admin/**").hasRole("ADMIN") 154 | // for everything else, the user has to be authenticated 155 | .anyRequest().authenticated() 156 | .and() 157 | // defining exception handling 158 | .exceptionHandling() 159 | // setting custom access denied handler for not authorized request 160 | .accessDeniedHandler(new CustomAccessDeniedHandler()) 161 | // setting custom entry point for unauthenticated request 162 | .authenticationEntryPoint(new CustomAuthenticationEntryPoint()) 163 | // setting stateless session, because we choose to implement Rest API 164 | .and().sessionManagement() 165 | .sessionCreationPolicy(SessionCreationPolicy.STATELESS); 166 | 167 | // adding the custom filter before UsernamePasswordAuthenticationFilter in the filter chain 168 | http.addFilterBefore(jwtTokenFilter, UsernamePasswordAuthenticationFilter.class); 169 | } 170 | } 171 | 172 | @AllArgsConstructor 173 | class AccessDenied{ 174 | String reason; 175 | } 176 | 177 | @Component 178 | class CustomAccessDeniedHandler implements AccessDeniedHandler { 179 | 180 | @Override 181 | public void handle(HttpServletRequest request, HttpServletResponse response, 182 | AccessDeniedException exc) throws IOException { 183 | response.addHeader("access_denied_reason", "not_authorized"); 184 | response.sendError(403, "Access Denied"); 185 | } 186 | } 187 | 188 | @Component 189 | class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint { 190 | @Override 191 | public void commence(HttpServletRequest request, HttpServletResponse response, 192 | AuthenticationException authException) throws IOException { 193 | response.addHeader("access_denied_reason", "authentication_required"); 194 | response.sendError(403, "Access Denied"); 195 | } 196 | } 197 | 198 | /* 199 | * Custom filter will run once per request. We add this to Filter Chain 200 | */ 201 | @Component 202 | class JwtTokenFilter extends OncePerRequestFilter { 203 | // Simple JWT implementation 204 | @Autowired 205 | private JwtUtil jwtUtil; 206 | @Autowired 207 | private UserDetailsService userDetailsService; 208 | 209 | // Spring Security will call this method during filter chain execution 210 | @Override 211 | protected void doFilterInternal(HttpServletRequest httpServletRequest, 212 | HttpServletResponse httpServletResponse, 213 | FilterChain filterChain) throws ServletException, IOException { 214 | 215 | // trying to find Authorization header 216 | final String authorizationHeader = httpServletRequest.getHeader("Authorization"); 217 | if (authorizationHeader == null || authorizationHeader.isEmpty() || !authorizationHeader.startsWith("Bearer")){ 218 | // if Authorization header does not exist, then skip this filter 219 | // and continue to execute next filter class 220 | filterChain.doFilter(httpServletRequest, httpServletResponse); 221 | return; 222 | } 223 | 224 | final String token = authorizationHeader.split(" ")[1].trim(); 225 | if (!jwtUtil.validate(token)) { 226 | // if token is not valid, then skip this filter 227 | // and continue to execute next filter class. 228 | // This means authentication is not successful since token is invalid. 229 | filterChain.doFilter(httpServletRequest, httpServletResponse); 230 | return; 231 | } 232 | 233 | // Authorization header exists, token is valid. So, we can authenticate. 234 | // We will get username from token and use it to get user details from UserDetailsService 235 | String username = jwtUtil.getUsername(token); 236 | UserDetails user = userDetailsService.loadUserByUsername(username); 237 | 238 | // initializing UsernamePasswordAuthenticationToken with its 3 parameter constructor 239 | // because it sets super.setAuthenticated(true); in that constructor. 240 | UsernamePasswordAuthenticationToken upassToken = new UsernamePasswordAuthenticationToken(user.getUsername(), null, user.getAuthorities()); 241 | upassToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(httpServletRequest)); 242 | 243 | // finally, give the authentication token to Spring Security Context 244 | SecurityContextHolder.getContext().setAuthentication(upassToken); 245 | 246 | // end of the method, so go for next filter class 247 | filterChain.doFilter(httpServletRequest, httpServletResponse); 248 | } 249 | } 250 | 251 | @Data 252 | @Builder 253 | class UserDTO { 254 | private String username; 255 | private String password; 256 | private String role; 257 | } 258 | 259 | /* 260 | * Custom UserDetailsService implementation 261 | */ 262 | @Service 263 | class UserDetailsService implements org.springframework.security.core.userdetails.UserDetailsService { 264 | // In memory user database 265 | private static final Map users = new HashMap<>(); 266 | 267 | @Autowired 268 | BCryptPasswordEncoder passwordEncoder; 269 | 270 | // Initializing user database 271 | @PostConstruct 272 | public void init() { 273 | UserDTO userDTO = UserDTO.builder() 274 | .username("martin") 275 | .password(passwordEncoder.encode("123")) 276 | .role("ROLE_USER") 277 | .build(); 278 | users.put("martin", userDTO); 279 | } 280 | 281 | @Override 282 | public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { 283 | // This is where you should fetch the user from database. 284 | if (users.containsKey(username)) { 285 | UserDTO userDTO = users.get(username); 286 | return new User(userDTO.getUsername(), userDTO.getPassword(), Arrays.asList(new SimpleGrantedAuthority(userDTO.getRole()))); 287 | } 288 | // if this is thrown, then we won't generate JWT token. 289 | throw new UsernameNotFoundException(username); 290 | } 291 | } 292 | 293 | 294 | -------------------------------------------------------------------------------- /spring-security-authentication-scenario-5/src/main/java/com/backendstory/authentication/Scenario5.java: -------------------------------------------------------------------------------- 1 | package com.backendstory.authentication; 2 | 3 | import lombok.Data; 4 | import lombok.NoArgsConstructor; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.boot.SpringApplication; 7 | import org.springframework.boot.autoconfigure.SpringBootApplication; 8 | import org.springframework.context.annotation.Bean; 9 | import org.springframework.context.annotation.Configuration; 10 | import org.springframework.http.ResponseEntity; 11 | import org.springframework.security.authentication.AuthenticationManager; 12 | import org.springframework.security.authentication.AuthenticationProvider; 13 | import org.springframework.security.authentication.BadCredentialsException; 14 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 15 | import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; 16 | import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; 17 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 18 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; 19 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; 20 | import org.springframework.security.config.http.SessionCreationPolicy; 21 | import org.springframework.security.core.Authentication; 22 | import org.springframework.security.core.AuthenticationException; 23 | import org.springframework.security.core.context.SecurityContextHolder; 24 | import org.springframework.security.core.userdetails.User; 25 | import org.springframework.security.core.userdetails.UserDetails; 26 | import org.springframework.security.core.userdetails.UsernameNotFoundException; 27 | import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; 28 | import org.springframework.security.crypto.password.PasswordEncoder; 29 | import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; 30 | import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; 31 | import org.springframework.stereotype.Component; 32 | import org.springframework.stereotype.Service; 33 | import org.springframework.web.bind.annotation.GetMapping; 34 | import org.springframework.web.bind.annotation.PostMapping; 35 | import org.springframework.web.bind.annotation.RequestBody; 36 | import org.springframework.web.bind.annotation.RestController; 37 | import org.springframework.web.filter.OncePerRequestFilter; 38 | 39 | import javax.servlet.FilterChain; 40 | import javax.servlet.ServletException; 41 | import javax.servlet.http.HttpServletRequest; 42 | import javax.servlet.http.HttpServletResponse; 43 | import java.io.IOException; 44 | import java.util.ArrayList; 45 | import java.util.Collections; 46 | import java.util.HashMap; 47 | import java.util.Map; 48 | 49 | @SpringBootApplication 50 | public class Scenario5 { 51 | 52 | public static void main(String[] args) { 53 | SpringApplication.run(Scenario5.class, args); 54 | } 55 | 56 | // Creating a bean for password encryption 57 | @Bean 58 | public BCryptPasswordEncoder getBCryptPasswordEncoder() { 59 | return new BCryptPasswordEncoder(); 60 | } 61 | } 62 | 63 | @RestController 64 | class BasicController { 65 | @Autowired 66 | private JwtUtil jwtUtil; 67 | 68 | // injecting authentication manager 69 | @Autowired 70 | private AuthenticationManager authenticationManager; 71 | 72 | @PostMapping("login") 73 | public ResponseEntity login(@RequestBody LoginRequestDTO request) { 74 | // Creating UsernamePasswordAuthenticationToken object 75 | // to send it to authentication manager. 76 | // Attention! We used two parameters constructor. 77 | // It sets authentication false by doing this.setAuthenticated(false); 78 | UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(request.getUsername(), request.getPassword()); 79 | // we let the manager do its job. 80 | authenticationManager.authenticate(token); 81 | // if there is no exception thrown from authentication manager, 82 | // we can generate a JWT token and give it to user. 83 | String jwt = jwtUtil.generate(request.getUsername()); 84 | return ResponseEntity.ok(jwt); 85 | } 86 | 87 | @GetMapping("/hello") 88 | public ResponseEntity get(){ 89 | return ResponseEntity.ok("Hello"); 90 | } 91 | } 92 | 93 | @Data 94 | @NoArgsConstructor 95 | class LoginRequestDTO { 96 | private String username; 97 | private String password; 98 | } 99 | 100 | @Configuration 101 | @EnableWebSecurity 102 | @EnableGlobalMethodSecurity(prePostEnabled = true) 103 | class WebSecurityConfiguration extends WebSecurityConfigurerAdapter { 104 | 105 | @Autowired 106 | private JwtTokenFilter jwtTokenFilter; 107 | 108 | // Injecting JWT custom authentication provider 109 | @Autowired 110 | JwtAuthenticationProvider customAuthenticationProvider; 111 | 112 | // Injecting Google custom authentication provider 113 | @Autowired 114 | GoogleCloudAuthenticationProvider googleCloudAuthenticationProvider; 115 | 116 | @Bean 117 | public AuthenticationManager getAuthenticationManager() throws Exception { 118 | return super.authenticationManagerBean(); 119 | } 120 | 121 | // adding our custom authentication providers 122 | // authentication manager will call these custom provider's 123 | // authenticate methods from now on. 124 | @Override 125 | protected void configure(AuthenticationManagerBuilder auth) { 126 | auth.authenticationProvider(customAuthenticationProvider) 127 | .authenticationProvider(googleCloudAuthenticationProvider); 128 | } 129 | 130 | @Override 131 | protected void configure(HttpSecurity http) throws Exception { 132 | http 133 | // disabling csrf since we won't use form login 134 | .csrf().disable() 135 | 136 | // giving every permission to every request for /login endpoint 137 | .authorizeRequests().antMatchers("/login").permitAll() 138 | // for everything else, the user has to be authenticated 139 | .anyRequest().authenticated() 140 | // setting stateless session, because we choose to implement Rest API 141 | .and().sessionManagement() 142 | .sessionCreationPolicy(SessionCreationPolicy.STATELESS); 143 | 144 | // adding the custom filter before UsernamePasswordAuthenticationFilter in the filter chain 145 | http.addFilterBefore(jwtTokenFilter, UsernamePasswordAuthenticationFilter.class); 146 | } 147 | } 148 | 149 | 150 | /* 151 | * Custom filter will run once per request. We add this to Filter Chain 152 | */ 153 | @Component 154 | class JwtTokenFilter extends OncePerRequestFilter { 155 | // Simple JWT implementation 156 | @Autowired 157 | private JwtUtil jwtUtil; 158 | 159 | // Spring Security will call this method during filter chain execution 160 | @Override 161 | protected void doFilterInternal(HttpServletRequest httpServletRequest, 162 | HttpServletResponse httpServletResponse, 163 | FilterChain filterChain) throws ServletException, IOException { 164 | 165 | // trying to find Authorization header 166 | final String authorizationHeader = httpServletRequest.getHeader("Authorization"); 167 | if (authorizationHeader == null || authorizationHeader.isEmpty() || !authorizationHeader.startsWith("Bearer")){ 168 | // if Authorization header does not exist, then skip this filter 169 | // and continue to execute next filter class 170 | filterChain.doFilter(httpServletRequest, httpServletResponse); 171 | return; 172 | } 173 | 174 | final String token = authorizationHeader.split(" ")[1].trim(); 175 | if (!jwtUtil.validate(token)) { 176 | // if token is not valid, then skip this filter 177 | // and continue to execute next filter class. 178 | // This means authentication is not successful since token is invalid. 179 | filterChain.doFilter(httpServletRequest, httpServletResponse); 180 | return; 181 | } 182 | 183 | // Authorization header exists, token is valid. So, we can authenticate. 184 | String username = jwtUtil.getUsername(token); 185 | // initializing UsernamePasswordAuthenticationToken with its 3 parameter constructor 186 | // because it sets super.setAuthenticated(true); in that constructor. 187 | UsernamePasswordAuthenticationToken upassToken = new UsernamePasswordAuthenticationToken(username, null, new ArrayList<>()); 188 | upassToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(httpServletRequest)); 189 | // finally, give the authentication token to Spring Security Context 190 | SecurityContextHolder.getContext().setAuthentication(upassToken); 191 | 192 | // end of the method, so go for next filter class 193 | filterChain.doFilter(httpServletRequest, httpServletResponse); 194 | } 195 | } 196 | 197 | @Component 198 | class JwtAuthenticationProvider implements AuthenticationProvider { 199 | // Injecting available encryption bean 200 | @Autowired 201 | private PasswordEncoder passwordEncoder; 202 | 203 | // Injecting our custom UserDetailsService implementation 204 | @Autowired 205 | private UserDetailsService userDetailsService; 206 | 207 | @Override 208 | public Authentication authenticate(Authentication authentication) throws AuthenticationException { 209 | // In BasicController.login() method, we call authenticationManager.authenticate(token) 210 | // Then, Authentication Manager calls AuthenticationProvider's authenticate method. 211 | // Since JwtAuthenticationProvider is our custom authentication provider, 212 | // this method will be executed. 213 | String username = authentication.getName(); 214 | String password = String.valueOf(authentication.getCredentials()); 215 | 216 | // Fetching user as wrapped with UserDetails object 217 | UserDetails userDetails = userDetailsService.loadUserByUsername(username); 218 | 219 | // If user is not null, then we check if password matches 220 | if (userDetails != null){ 221 | if (passwordEncoder.matches(password, userDetails.getPassword())){ 222 | // if it matches, then we can initialize UsernamePasswordAuthenticationToken. 223 | // Attention! We used its 3 parameters constructor. 224 | UsernamePasswordAuthenticationToken authenticationToken = 225 | new UsernamePasswordAuthenticationToken(username, password, userDetails.getAuthorities()); 226 | return authenticationToken; 227 | } 228 | } 229 | throw new BadCredentialsException("Error!!"); 230 | } 231 | 232 | // Authentication Manager checks if the token is supported by this filter 233 | // to avoid unnecessary checks. 234 | @Override 235 | public boolean supports(Class authenticationType) { 236 | return UsernamePasswordAuthenticationToken.class.equals(authenticationType); 237 | } 238 | } 239 | 240 | /* 241 | * Custom UserDetailsService implementation 242 | */ 243 | @Service 244 | class UserDetailsService implements org.springframework.security.core.userdetails.UserDetailsService { 245 | @Autowired 246 | BCryptPasswordEncoder passwordEncoder; 247 | 248 | @Override 249 | public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { 250 | // This is where you should fetch the user from database. 251 | // We keep it simple to focus on authentication flow. 252 | Map users = new HashMap<>(); 253 | users.put("martin", passwordEncoder.encode("123")); 254 | if (users.containsKey(username)) 255 | return new User(username, users.get(username), new ArrayList<>()); 256 | // if this is thrown, then we won't generate JWT token. 257 | throw new UsernameNotFoundException(username); 258 | } 259 | } 260 | 261 | @Component 262 | class GoogleCloudAuthenticationProvider implements AuthenticationProvider { 263 | @Override 264 | public Authentication authenticate(Authentication authentication) throws AuthenticationException { 265 | String username = authentication.getName(); 266 | String password = String.valueOf(authentication.getCredentials()); 267 | 268 | // We fetch user from Google API "in theory" 269 | User user = getUserFromGoogleCloud(username, password); 270 | if (user != null) { 271 | UsernamePasswordAuthenticationToken authenticationToken = 272 | new UsernamePasswordAuthenticationToken(username, password, user.getAuthorities()); 273 | return authenticationToken; 274 | } 275 | throw new BadCredentialsException("Error!!"); 276 | } 277 | 278 | // Let's assume Google API will return the user in this method. 279 | private User getUserFromGoogleCloud(String username, String password) { 280 | Map users = new HashMap<>(); 281 | users.put("ugur", "123"); 282 | if (users.get(username) != null){ 283 | return new User(username, password, Collections.emptyList()); 284 | } 285 | return null; 286 | } 287 | 288 | @Override 289 | public boolean supports(Class authenticationType) { 290 | return UsernamePasswordAuthenticationToken.class.equals(authenticationType); 291 | } 292 | 293 | } -------------------------------------------------------------------------------- /spring-security-upgrade/src/main/java/com/backendstory/upgrade/SpringSecurityUpgrade.java: -------------------------------------------------------------------------------- 1 | package com.backendstory.upgrade; 2 | 3 | import lombok.Data; 4 | import lombok.NoArgsConstructor; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.boot.SpringApplication; 7 | import org.springframework.boot.autoconfigure.SpringBootApplication; 8 | import org.springframework.context.annotation.Bean; 9 | import org.springframework.context.annotation.Configuration; 10 | import org.springframework.http.ResponseEntity; 11 | import org.springframework.security.authentication.AuthenticationManager; 12 | import org.springframework.security.authentication.AuthenticationProvider; 13 | import org.springframework.security.authentication.BadCredentialsException; 14 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 15 | import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; 16 | import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; 17 | import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; 18 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 19 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; 20 | import org.springframework.security.config.http.SessionCreationPolicy; 21 | import org.springframework.security.core.Authentication; 22 | import org.springframework.security.core.AuthenticationException; 23 | import org.springframework.security.core.context.SecurityContextHolder; 24 | import org.springframework.security.core.userdetails.User; 25 | import org.springframework.security.core.userdetails.UserDetails; 26 | import org.springframework.security.core.userdetails.UsernameNotFoundException; 27 | import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; 28 | import org.springframework.security.crypto.password.PasswordEncoder; 29 | import org.springframework.security.web.SecurityFilterChain; 30 | import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; 31 | import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; 32 | import org.springframework.stereotype.Component; 33 | import org.springframework.stereotype.Service; 34 | import org.springframework.web.bind.annotation.GetMapping; 35 | import org.springframework.web.bind.annotation.PostMapping; 36 | import org.springframework.web.bind.annotation.RequestBody; 37 | import org.springframework.web.bind.annotation.RestController; 38 | import org.springframework.web.filter.OncePerRequestFilter; 39 | 40 | import javax.servlet.FilterChain; 41 | import javax.servlet.ServletException; 42 | import javax.servlet.http.HttpServletRequest; 43 | import javax.servlet.http.HttpServletResponse; 44 | import java.io.IOException; 45 | import java.util.ArrayList; 46 | import java.util.Collections; 47 | import java.util.HashMap; 48 | import java.util.Map; 49 | 50 | @SpringBootApplication 51 | public class SpringSecurityUpgrade { 52 | 53 | public static void main(String[] args) { 54 | SpringApplication.run(SpringSecurityUpgrade.class, args); 55 | } 56 | 57 | // Creating a bean for password encryption 58 | @Bean 59 | public BCryptPasswordEncoder getBCryptPasswordEncoder() { 60 | return new BCryptPasswordEncoder(); 61 | } 62 | } 63 | 64 | @RestController 65 | class BasicController { 66 | @Autowired 67 | private JwtUtil jwtUtil; 68 | 69 | // injecting authentication manager 70 | @Autowired 71 | private AuthenticationManager authenticationManager; 72 | 73 | @PostMapping("login") 74 | public ResponseEntity login(@RequestBody LoginRequestDTO request) { 75 | // Creating UsernamePasswordAuthenticationToken object 76 | // to send it to authentication manager. 77 | // Attention! We used two parameters constructor. 78 | // It sets authentication false by doing this.setAuthenticated(false); 79 | UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(request.getUsername(), request.getPassword()); 80 | // we let the manager do its job. 81 | authenticationManager.authenticate(token); 82 | // if there is no exception thrown from authentication manager, 83 | // we can generate a JWT token and give it to user. 84 | String jwt = jwtUtil.generate(request.getUsername()); 85 | return ResponseEntity.ok(jwt); 86 | } 87 | 88 | @GetMapping("/hello") 89 | public ResponseEntity get(){ 90 | return ResponseEntity.ok("Hello"); 91 | } 92 | } 93 | 94 | @Data 95 | @NoArgsConstructor 96 | class LoginRequestDTO { 97 | private String username; 98 | private String password; 99 | } 100 | 101 | @Configuration 102 | @EnableWebSecurity 103 | @EnableGlobalMethodSecurity(prePostEnabled = true) 104 | class WebSecurityConfiguration { 105 | 106 | @Autowired 107 | private JwtTokenFilter jwtTokenFilter; 108 | 109 | // Injecting JWT custom authentication provider 110 | @Autowired 111 | JwtAuthenticationProvider customAuthenticationProvider; 112 | 113 | // Injecting Google custom authentication provider 114 | @Autowired 115 | GoogleCloudAuthenticationProvider googleCloudAuthenticationProvider; 116 | 117 | @Bean 118 | AuthenticationManager authenticationManager( 119 | AuthenticationConfiguration authenticationConfiguration) throws Exception { 120 | return authenticationConfiguration.getAuthenticationManager(); 121 | } 122 | 123 | // adding our custom authentication providers 124 | // authentication manager will call these custom provider's 125 | // authenticate methods from now on. 126 | @Autowired 127 | void registerProvider(AuthenticationManagerBuilder auth) { 128 | auth.authenticationProvider(customAuthenticationProvider) 129 | .authenticationProvider(googleCloudAuthenticationProvider); 130 | } 131 | 132 | @Bean 133 | public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { 134 | http 135 | // disabling csrf since we won't use form login 136 | .csrf().disable() 137 | 138 | // giving every permission to every request for /login endpoint 139 | .authorizeRequests().antMatchers("/login").permitAll() 140 | // for everything else, the user has to be authenticated 141 | .anyRequest().authenticated() 142 | // setting stateless session, because we choose to implement Rest API 143 | .and().sessionManagement() 144 | .sessionCreationPolicy(SessionCreationPolicy.STATELESS); 145 | 146 | // adding the custom filter before UsernamePasswordAuthenticationFilter in the filter chain 147 | http.addFilterBefore(jwtTokenFilter, UsernamePasswordAuthenticationFilter.class); 148 | return http.build(); 149 | } 150 | 151 | } 152 | 153 | 154 | /* 155 | * Custom filter will run once per request. We add this to Filter Chain 156 | */ 157 | @Component 158 | class JwtTokenFilter extends OncePerRequestFilter { 159 | // Simple JWT implementation 160 | @Autowired 161 | private JwtUtil jwtUtil; 162 | 163 | // Spring Security will call this method during filter chain execution 164 | @Override 165 | protected void doFilterInternal(HttpServletRequest httpServletRequest, 166 | HttpServletResponse httpServletResponse, 167 | FilterChain filterChain) throws ServletException, IOException { 168 | 169 | // trying to find Authorization header 170 | final String authorizationHeader = httpServletRequest.getHeader("Authorization"); 171 | if (authorizationHeader == null || authorizationHeader.isEmpty() || !authorizationHeader.startsWith("Bearer")){ 172 | // if Authorization header does not exist, then skip this filter 173 | // and continue to execute next filter class 174 | filterChain.doFilter(httpServletRequest, httpServletResponse); 175 | return; 176 | } 177 | 178 | final String token = authorizationHeader.split(" ")[1].trim(); 179 | if (!jwtUtil.validate(token)) { 180 | // if token is not valid, then skip this filter 181 | // and continue to execute next filter class. 182 | // This means authentication is not successful since token is invalid. 183 | filterChain.doFilter(httpServletRequest, httpServletResponse); 184 | return; 185 | } 186 | 187 | // Authorization header exists, token is valid. So, we can authenticate. 188 | String username = jwtUtil.getUsername(token); 189 | // initializing UsernamePasswordAuthenticationToken with its 3 parameter constructor 190 | // because it sets super.setAuthenticated(true); in that constructor. 191 | UsernamePasswordAuthenticationToken upassToken = new UsernamePasswordAuthenticationToken(username, null, new ArrayList<>()); 192 | upassToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(httpServletRequest)); 193 | // finally, give the authentication token to Spring Security Context 194 | SecurityContextHolder.getContext().setAuthentication(upassToken); 195 | 196 | // end of the method, so go for next filter class 197 | filterChain.doFilter(httpServletRequest, httpServletResponse); 198 | } 199 | } 200 | 201 | @Component 202 | class JwtAuthenticationProvider implements AuthenticationProvider { 203 | // Injecting available encryption bean 204 | @Autowired 205 | private PasswordEncoder passwordEncoder; 206 | 207 | // Injecting our custom UserDetailsService implementation 208 | @Autowired 209 | private UserDetailsService userDetailsService; 210 | 211 | @Override 212 | public Authentication authenticate(Authentication authentication) throws AuthenticationException { 213 | // In BasicController.login() method, we call authenticationManager.authenticate(token) 214 | // Then, Authentication Manager calls AuthenticationProvider's authenticate method. 215 | // Since JwtAuthenticationProvider is our custom authentication provider, 216 | // this method will be executed. 217 | String username = authentication.getName(); 218 | String password = String.valueOf(authentication.getCredentials()); 219 | 220 | // Fetching user as wrapped with UserDetails object 221 | UserDetails userDetails = userDetailsService.loadUserByUsername(username); 222 | 223 | // If user is not null, then we check if password matches 224 | if (userDetails != null){ 225 | if (passwordEncoder.matches(password, userDetails.getPassword())){ 226 | // if it matches, then we can initialize UsernamePasswordAuthenticationToken. 227 | // Attention! We used its 3 parameters constructor. 228 | UsernamePasswordAuthenticationToken authenticationToken = 229 | new UsernamePasswordAuthenticationToken(username, password, userDetails.getAuthorities()); 230 | return authenticationToken; 231 | } 232 | } 233 | throw new BadCredentialsException("Error!!"); 234 | } 235 | 236 | // Authentication Manager checks if the token is supported by this filter 237 | // to avoid unnecessary checks. 238 | @Override 239 | public boolean supports(Class authenticationType) { 240 | return UsernamePasswordAuthenticationToken.class.equals(authenticationType); 241 | } 242 | } 243 | 244 | /* 245 | * Custom UserDetailsService implementation 246 | */ 247 | @Service 248 | class UserDetailsService implements org.springframework.security.core.userdetails.UserDetailsService { 249 | @Autowired 250 | BCryptPasswordEncoder passwordEncoder; 251 | 252 | @Override 253 | public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { 254 | // This is where you should fetch the user from database. 255 | // We keep it simple to focus on authentication flow. 256 | Map users = new HashMap<>(); 257 | users.put("martin", passwordEncoder.encode("123")); 258 | if (users.containsKey(username)) 259 | return new User(username, users.get(username), new ArrayList<>()); 260 | // if this is thrown, then we won't generate JWT token. 261 | throw new UsernameNotFoundException(username); 262 | } 263 | } 264 | 265 | @Component 266 | class GoogleCloudAuthenticationProvider implements AuthenticationProvider { 267 | @Override 268 | public Authentication authenticate(Authentication authentication) throws AuthenticationException { 269 | String username = authentication.getName(); 270 | String password = String.valueOf(authentication.getCredentials()); 271 | 272 | // We fetch user from Google API "in theory" 273 | User user = getUserFromGoogleCloud(username, password); 274 | if (user != null) { 275 | UsernamePasswordAuthenticationToken authenticationToken = 276 | new UsernamePasswordAuthenticationToken(username, password, user.getAuthorities()); 277 | return authenticationToken; 278 | } 279 | throw new BadCredentialsException("Error!!"); 280 | } 281 | 282 | // Let's assume Google API will return the user in this method. 283 | private User getUserFromGoogleCloud(String username, String password) { 284 | Map users = new HashMap<>(); 285 | users.put("ugur", "123"); 286 | if (users.get(username) != null){ 287 | return new User(username, password, Collections.emptyList()); 288 | } 289 | return null; 290 | } 291 | 292 | @Override 293 | public boolean supports(Class authenticationType) { 294 | return UsernamePasswordAuthenticationToken.class.equals(authenticationType); 295 | } 296 | 297 | } --------------------------------------------------------------------------------