├── .gitignore ├── .mvn └── wrapper │ ├── MavenWrapperDownloader.java │ ├── maven-wrapper.jar │ └── maven-wrapper.properties ├── README.md ├── gitimages ├── jwt.jpg └── projectcreate.jpg ├── mvnw ├── mvnw.cmd ├── pom.xml └── src ├── main ├── java │ └── com │ │ └── restfulspring │ │ └── apiexample │ │ ├── ApiexampleApplication.java │ │ ├── config │ │ ├── MyFilterConfig.java │ │ └── SecurityConfig.java │ │ ├── controller │ │ ├── AccountController.java │ │ ├── CityController.java │ │ ├── CountryController.java │ │ ├── HelloController.java │ │ └── PersonController.java │ │ ├── entity │ │ ├── ApplicationUser.java │ │ ├── City.java │ │ ├── Country.java │ │ ├── Course.java │ │ ├── Person.java │ │ └── Student.java │ │ ├── exception │ │ └── NotFoundException.java │ │ ├── filters │ │ ├── JwtFilter.java │ │ └── MyFilter.java │ │ ├── repository │ │ ├── CityRepository.java │ │ ├── CountryRepository.java │ │ ├── PersonRepository.java │ │ └── UserRepository.java │ │ └── service │ │ ├── CityService.java │ │ ├── CountryService.java │ │ ├── JwtUtilService.java │ │ ├── PersonService.java │ │ └── UserService.java └── resources │ └── application.properties └── test └── java └── com └── restfulspring └── apiexample └── ApiexampleApplicationTests.java /.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/** 5 | !**/src/test/** 6 | 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 | 30 | ### VS Code ### 31 | .vscode/ 32 | -------------------------------------------------------------------------------- /.mvn/wrapper/MavenWrapperDownloader.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2007-present the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import java.net.*; 18 | import java.io.*; 19 | import java.nio.channels.*; 20 | import java.util.Properties; 21 | 22 | public class MavenWrapperDownloader { 23 | 24 | private static final String WRAPPER_VERSION = "0.5.6"; 25 | /** 26 | * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. 27 | */ 28 | private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/" 29 | + WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar"; 30 | 31 | /** 32 | * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to 33 | * use instead of the default one. 34 | */ 35 | private static final String MAVEN_WRAPPER_PROPERTIES_PATH = 36 | ".mvn/wrapper/maven-wrapper.properties"; 37 | 38 | /** 39 | * Path where the maven-wrapper.jar will be saved to. 40 | */ 41 | private static final String MAVEN_WRAPPER_JAR_PATH = 42 | ".mvn/wrapper/maven-wrapper.jar"; 43 | 44 | /** 45 | * Name of the property which should be used to override the default download url for the wrapper. 46 | */ 47 | private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; 48 | 49 | public static void main(String args[]) { 50 | System.out.println("- Downloader started"); 51 | File baseDirectory = new File(args[0]); 52 | System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath()); 53 | 54 | // If the maven-wrapper.properties exists, read it and check if it contains a custom 55 | // wrapperUrl parameter. 56 | File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); 57 | String url = DEFAULT_DOWNLOAD_URL; 58 | if (mavenWrapperPropertyFile.exists()) { 59 | FileInputStream mavenWrapperPropertyFileInputStream = null; 60 | try { 61 | mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); 62 | Properties mavenWrapperProperties = new Properties(); 63 | mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream); 64 | url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url); 65 | } catch (IOException e) { 66 | System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); 67 | } finally { 68 | try { 69 | if (mavenWrapperPropertyFileInputStream != null) { 70 | mavenWrapperPropertyFileInputStream.close(); 71 | } 72 | } catch (IOException e) { 73 | // Ignore ... 74 | } 75 | } 76 | } 77 | System.out.println("- Downloading from: " + url); 78 | 79 | File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); 80 | if (!outputFile.getParentFile().exists()) { 81 | if (!outputFile.getParentFile().mkdirs()) { 82 | System.out.println( 83 | "- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'"); 84 | } 85 | } 86 | System.out.println("- Downloading to: " + outputFile.getAbsolutePath()); 87 | try { 88 | downloadFileFromURL(url, outputFile); 89 | System.out.println("Done"); 90 | System.exit(0); 91 | } catch (Throwable e) { 92 | System.out.println("- Error downloading"); 93 | e.printStackTrace(); 94 | System.exit(1); 95 | } 96 | } 97 | 98 | private static void downloadFileFromURL(String urlString, File destination) throws Exception { 99 | if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) { 100 | String username = System.getenv("MVNW_USERNAME"); 101 | char[] password = System.getenv("MVNW_PASSWORD").toCharArray(); 102 | Authenticator.setDefault(new Authenticator() { 103 | @Override 104 | protected PasswordAuthentication getPasswordAuthentication() { 105 | return new PasswordAuthentication(username, password); 106 | } 107 | }); 108 | } 109 | URL website = new URL(urlString); 110 | ReadableByteChannel rbc; 111 | rbc = Channels.newChannel(website.openStream()); 112 | FileOutputStream fos = new FileOutputStream(destination); 113 | fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); 114 | fos.close(); 115 | rbc.close(); 116 | } 117 | 118 | } 119 | -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fawad1997/SpringWebAPI/89598fc9c372bba60ec3ade089f1ca9215dd0c38/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip 2 | wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Java Spring Web API 2 | In this repository, you can learn **Spring Rest API** from beginner to advanced level. 3 | ### Quick Links 4 | - [Creating Project](#creating-project) 5 | - [CRUD with H2 Database](#crud-with-h2-database) 6 | - [Creating Models/Tables/Entities](#creating-models-tables-entities) 7 | - [Creating Repository](#creating-repository) 8 | - [Creating Service](#creating-service) 9 | - [Creating Controller](#creating-controller) 10 | - [Enabling Cross-Origin](#enabling-cross-origin) 11 | - [Using MySQL database instead of H2](#using-mysql-database-instead-of-h2) 12 | - [One to Many Relation in Hibernate](#one-to-many-relation-in-hibernate) 13 | - [Creating Entities](#creating-entities) 14 | - [Many to Many Relation in Hibernate](#many-to-many-relation-in-hibernate) 15 | - [Error Handling](#error-handling) 16 | - [JPA Hibernate Validations](#jpa-hibernate-validations) 17 | - [Creating Filters](#creating-filters) 18 | - [Spring Security](#spring-security) 19 | - [Spring Basic Security](#spring-basic-security) 20 | - [Create table for application users](#create-table-for-application-users) 21 | - [Create repository](#create-repository) 22 | - [Create Service that will implement UserDetailsService](#create-service-that-will-implement-userdetailsservice) 23 | - [Create Security Configuration File](#create-security-configuration-file) 24 | - [Jwt Authentication](#jwt-authentication) 25 | - [Adding Dependencies](#adding-dependencies) 26 | - [Create JwtUtilService](#create-jwtutilservice) 27 | - [Generate Token](#generate-token) 28 | - [Allow Anonymous request](#allow-anonymous-request) 29 | - [Create filter to check token](#create-filter-to-check-token) 30 | - [Register User](#register-user) 31 | 32 | ## Creating Project 33 | In IntelliJ IDEA, go to spring initilizer, create new project by selecting **Spring web** in dependencies. [(referance commit)](https://github.com/fawad1997/SpringWebAPI/commit/ee38d2323931446cb310ba963d825503ae73a6a4) 34 | ![](/gitimages/projectcreate.jpg) 35 | Give it proper name at create it, it may take few minutes for downloading the dependencies. 36 | Create a package for controllers in src>main>java>[your pakage name]/controller [(referance commit)](https://github.com/fawad1997/SpringWebAPI/commit/63116f88c2c81305a20e53b795142dd6a3bd47c8) 37 | 38 | Add a controller to display hello world message. **@RestController** makes it restfull controller and **@RequestMapping(value = "/hello")** defines URL mapping 39 | ```java 40 | @RestController 41 | public class HelloController { 42 | @RequestMapping(value = "/hello") 43 | public String sayHello(){ 44 | return "Hello World!"; 45 | } 46 | } 47 | ``` 48 | 49 | ## CRUD with H2 Database 50 | to use database we need few dependencies to be installed 51 | Go to [mavenrepository](https://mvnrepository.com/) 52 | and search for the following dependencies and add it to your [pom.xml](https://github.com/fawad1997/SpringWebAPI/blob/master/pom.xml) _dependencies_ section 53 | Please donot include test scope as we will not be doing testing at that stage [(referance commit)](https://github.com/fawad1997/SpringWebAPI/commit/ba8bd9484b4901e214a9d1242c4c758c03489524) 54 | 58 | Now you have added a database named H2 database(in-memory db), you need to specify database name too 59 | 60 | 1. Open [application.properties](https://github.com/fawad1997/SpringWebAPI/blob/master/src/main/resources/application.properties) in resources folder 61 | 2. specify database name as follows 62 | 63 | ``` 64 | spring.datasource.url=jdbc:h2:~/test;DB_CLOSE_ON_EXIT=FALSE 65 | spring.jpa.hibernate.ddl-auto=update 66 | ``` 67 | ### Creating Models-Tables-Entities 68 | Create a package **entity** where you will create Entity classes 69 | e.g we are creating **Person** entity [(referance commit)](https://github.com/fawad1997/SpringWebAPI/commit/2c70939ef61ba30a905e4ecb2d0c4ecd723497aa) 70 | 71 | Use **@Entity** annotation to make it entity, **@Data** to make setter getters, **@NoArgsConstructor** to create no argument constructor, **@AllArgsConstructor** to make argument constructor. **@Id** to make it primary key, **@GeneratedValue(strategy = GenerationType.AUTO)** for autoincrement. 72 | ```java 73 | @Entity 74 | @Data 75 | @NoArgsConstructor 76 | @AllArgsConstructor 77 | public class Person { 78 | @Id 79 | @GeneratedValue(strategy = GenerationType.AUTO) 80 | private int ID; 81 | private String Name; 82 | private int Age; 83 | private double Height; 84 | private String CNIC; 85 | } 86 | ``` 87 | Now your table will be created once the application starts. 88 | 89 | ### Creating Repository 90 | Now create a repository for every entity, create package named **repository** and create interface,, e.g. PersonRepository that will extend **JpaRepository** and use **@Repository** annotation on it. [(referance commit)](https://github.com/fawad1997/SpringWebAPI/commit/d830dfddcb418ecfe81741f0842342eebb69903f) 91 | ```java 92 | @Repository 93 | public interface PersonRepository extends JpaRepository { 94 | } 95 | ``` 96 | 97 | ### Creating Service 98 | Services will contain business logic, e.g. CRUD operation in this case. 99 | Create package **service** and create service for every repository. e.g. **PersonService** 100 | 101 | Use **@Service** annotation on PersonService. In PersonService, create private object of PersonRepository and use **@Autowired** annotation on it, so spring framework will initilize that object. [(referance commit)](https://github.com/fawad1997/SpringWebAPI/commit/43ad32aac931df96392dc13dbdeedd16816df34b) 102 | 103 | ### Creating Controller 104 | 105 | Now in the controller package, create **PersonController** that will manage Http requests. 106 | Use **@RestController**, **@RequestMapping(value = "/person")** as we do in controllers. Create an object of PersonService in PersonController and use **@Autowired** annotation on it, so spring framework will manage object creation. 107 | 108 | Now create GET, POST, PUT and DELETE methods with **@GetMapping**,**@PostMapping**, **@PutMapping(value = "/{id}")** and **@DeleteMapping(value = "/{id}")**. In the function parameters, use **@PathVariable int id** to get data from URL like localhost/person/1, and if we use **@RequestParam** it would be like localhost/person?id=1 and **@RequestBody Person person** to get data from body. [(referance commit)](https://github.com/fawad1997/SpringWebAPI/commit/868f7bf954b91d4ef8d5701b6ba27dc39ac8f711) 109 | #### Enabling Cross-Origin 110 | Adding 111 | ```java 112 | @CrossOrigin(origins = "*", allowedHeaders = "*") 113 | ``` 114 | on controller so it can be accessed from anywhere. [(referance commit)](https://github.com/fawad1997/SpringWebAPI/commit/aa6237082aff953739700658aeaba375f9fc8979) 115 | 116 | Adding **@JsonProperty** on entity to will help you to change JSON object name [(referance commit)](https://github.com/fawad1997/SpringWebAPI/commit/b777ed42d0d20ae0415c530b9a4682396012c11e) 117 | 118 | ### Using MySQL database instead of H2 119 | Remove H2 Database dependency from [pom.xml](https://github.com/fawad1997/SpringWebAPI/blob/master/pom.xml) and add [MySQL Connector](https://mvnrepository.com/artifact/mysql/mysql-connector-java/8.0.19) 120 | 121 | comment/remove the H2 database configuration from [application.properties](https://github.com/fawad1997/SpringWebAPI/blob/master/src/main/resources/application.properties) file and add MySQL properties as follows: [(referance commit)](https://github.com/fawad1997/SpringWebAPI/commit/1230fa1723715194d4b5b5feae68705793eb5549) 122 | 123 | ``` 124 | spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver 125 | spring.datasource.url=jdbc:mysql://localhost:3306/test?useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC 126 | spring.datasource.username=root 127 | spring.datasource.password= 128 | spring.jpa.show-sql=true 129 | spring.jpa.hibernate.ddl-auto=update 130 | spring.jpa.properties.hibernate.dialect= org.hibernate.dialect.MySQL5Dialect 131 | ``` 132 | 133 | ### One to Many Relation in Hibernate 134 | #### Creating Entities 135 | lets create two Entities Country and City for oneToMany Relationship. 136 | Create entity **City** with properties **cityId** and **cityName** annotate them with proper annotations like **@Id**, **@GeneratedValue(strategy = GenerationType.AUTO)** and **@Column(name = "cityId")** and also annotate the table with annotations like **@Entity**, **@Data**, **@NoArgsConstructor** and **@AllArgsConstructor**. 137 | ```java 138 | @Entity 139 | @Data 140 | @NoArgsConstructor 141 | @AllArgsConstructor 142 | public class City { 143 | @Id 144 | @GeneratedValue(strategy = GenerationType.AUTO) 145 | @Column(name = "cityId") 146 | private int cityId; 147 | private String cityName; 148 | } 149 | ``` 150 | Now create an entity **Country**, annotate class with annotations **@Entity**, **@Data**, **@NoArgsConstructor** and **@AllArgsConstructor**, and create the following fields [(referance commit)](https://github.com/fawad1997/SpringWebAPI/commit/17699807e87c42b395db7917fb073c770339be12) 151 | 152 | ```java 153 | @Entity 154 | @Data 155 | @NoArgsConstructor 156 | @AllArgsConstructor 157 | @ToString 158 | public class Country { 159 | @Id 160 | @GeneratedValue(strategy = GenerationType.AUTO) 161 | @Column(name = "countryId") 162 | private int countryId; 163 | private String countryName; 164 | 165 | @OneToMany(cascade = CascadeType.ALL) 166 | @JoinColumn(name = "cc_fk",referencedColumnName = "countryId") 167 | private List cities; 168 | } 169 | ``` 170 | Annotate it with **@OneToMany(cascade = CascadeType.ALL)** and **@JoinColumn(name = "cc_fk",referencedColumnName = "countryId")**. Yhis will create cc_fk column in City table and make it foreign key. 171 | 172 | ##### Creating Repositories 173 | same as above [(referance commit)](https://github.com/fawad1997/SpringWebAPI/commit/6c1d0b675fd4d4dd8b7463c41de71f5c61b5540a) 174 | ##### Creating Services 175 | same as above [(referance commit)](https://github.com/fawad1997/SpringWebAPI/commit/362bbd5eb20b8473a36e333143ecd28c3a15e25d) 176 | ##### Creating Controllers 177 | same as above [(referance commit)](https://github.com/fawad1997/SpringWebAPI/commit/72620f3b512d3e5f8ad3a12fcb29846e949c5738) 178 | 179 | ### Many to Many Relation in Hibernate 180 | Lets take the example of two entities **Student** and **Course**, One student can be enrolled in multiple courses, similarly, Each course contains multiple students. If you remember database normalization rules, we need to break the entity to seperate entity for Many to Many relation. 181 | 182 | One ony below table, use **@JoinTable** annotation to specify 3rd table name, column to join from current table, and column from the other table. 183 | ```java 184 | @Entity 185 | @Data 186 | @NoArgsConstructor 187 | @AllArgsConstructor 188 | public class Student { 189 | @Id 190 | @GeneratedValue(strategy = GenerationType.IDENTITY) 191 | private int studentId; 192 | private String name; 193 | private String regNo; 194 | @ManyToMany(cascade = CascadeType.ALL) 195 | @JoinTable(name = "student_courses", 196 | joinColumns = {@JoinColumn(name = "studentId")}, 197 | inverseJoinColumns = {@JoinColumn(name = "courseId")}) 198 | private List courses; 199 | } 200 | ``` 201 | and 202 | ```java 203 | @Entity 204 | @Data 205 | @NoArgsConstructor 206 | @AllArgsConstructor 207 | public class Course { 208 | @Id 209 | @GeneratedValue(strategy = GenerationType.IDENTITY) 210 | private int courseId; 211 | private String courseTitle; 212 | private String courseCode; 213 | @ManyToMany 214 | private List students; 215 | } 216 | ``` 217 | ### Error Handling 218 | Suppose users requests a resource by FindbyId, currently it returns null, instead of null, we will now handle the error and return not found error. For that create a package named **exception** and create a class to handle exception. [(referance commit)](https://github.com/fawad1997/SpringWebAPI/commit/71dabd4475d16720909d32e4dadf3c0224bdaaaa) 219 | ```java 220 | @ResponseStatus(HttpStatus.NOT_FOUND) 221 | public class NotFoundException extends RuntimeException { 222 | public NotFoundException(String message){ 223 | super(message); 224 | } 225 | } 226 | ``` 227 | modify getPerson method to throw notfound error 228 | ```java 229 | public Person getPerson(int id){ 230 | Optional person = personRepository.findById(id); 231 | if(!person.isPresent()){ 232 | throw new NotFoundException("Person not found!"); 233 | } 234 | return person.get(); 235 | } 236 | ``` 237 | ### JPA Hibernate Validations 238 | To customize table design, we can use different annotatons on top of each field. 239 | 240 | - **@Id** makes it Primary Key. 241 | - **@GeneratedValue(strategy = GenerationType.IDENTITY)** make it autoincrement by 1. 242 | - **@JsonProperty(value = "ID")** make sures it should be **ID** in JSON format instead of **id** 243 | - **@NotNull(message = "Name cann't be null")** makes the field non nullable. 244 | - **@Size(min = 2,max = 100, message = "Name must be minimum 2 characters and maximum 100 characters long")** validates the size of the field. 245 | - **@Email** is used to validate email. 246 | - **@Min(8)** and **@Max(110)** says that number should be between 8-110. 247 | - **@Pattern(regexp = "^\\(?(\\d{5})\\)?[-]?(\\d{7})[-]?(\\d{1})$",message = "CNIC sholud be in format xxxxx-xxxxxxx-x")** is used for regular expression. 248 | - **@Past** makes sure that date should be from past, mnot future. 249 | - **@JsonFormat(pattern = "yyyy-MM-dd")** make sures JSON data should be in this format. 250 | 251 | ```java 252 | @Entity 253 | @Data 254 | @NoArgsConstructor 255 | @AllArgsConstructor 256 | public class Person { 257 | @Id 258 | @GeneratedValue(strategy = GenerationType.IDENTITY) 259 | @JsonProperty(value = "ID") 260 | private int id; 261 | @JsonProperty(value = "Name") 262 | @NotNull(message = "Name cann't be null") 263 | @Size(min = 2,max = 100, message = "Name must be minimum 2 characters and maximum 100 characters long") 264 | private String name; 265 | @Email 266 | private String email; 267 | @JsonProperty(value = "Age") 268 | @Min(8) 269 | @Max(110) 270 | private int age; 271 | @JsonProperty(value = "Height") 272 | private double height; 273 | @JsonProperty(value = "CNIC") 274 | @Pattern(regexp = "^\\(?(\\d{5})\\)?[-]?(\\d{7})[-]?(\\d{1})$",message = "CNIC sholud be in format xxxxx-xxxxxxx-x") 275 | private String cnic; 276 | @Past 277 | @JsonFormat(pattern = "yyyy-MM-dd") 278 | private LocalDate doB; 279 | } 280 | ``` 281 | After adding these annotations, our table will be created with those restrictions. But if we insert invalid data through JSON, our application will throw exception or maybe crash. We need to validate these properties in controller to avoid exceptions. 282 | #### Handling Validations in Controller 283 | Use **@Valid** to check for validations, and also inject BindingResult in it which will help us to catch errors, If invalid, then return errors, else, return created object. Simplarly check for validations in PutMapping function too. 284 | ```java 285 | @PostMapping 286 | public ResponseEntity addPerson(@Valid @RequestBody Person person, BindingResult result){ 287 | if(result.hasErrors()){ 288 | Map errors = new HashMap<>(); 289 | for(FieldError error:result.getFieldErrors()){ 290 | errors.put(error.getField(),error.getDefaultMessage()); 291 | } 292 | return new ResponseEntity>(errors, HttpStatus.BAD_REQUEST); 293 | } 294 | Person p = personService.addPerson(person); 295 | return new ResponseEntity(p,HttpStatus.CREATED); 296 | } 297 | ``` 298 | ### Creating Filters 299 | Create a package **filters** and create filter in it. 300 | Make it **component** of spring framework by adding **@Component** annotation. By default, filter is called on every url pattren. [(referance commit)](https://github.com/fawad1997/SpringWebAPI/commit/6c14edfd6299cf040d63c8a689c3395d5415de35) 301 | ```java 302 | import org.springframework.stereotype.Component; 303 | import javax.servlet.*; 304 | import java.io.IOException; 305 | @Component 306 | public class MyFilter implements Filter { 307 | public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException { 308 | System.out.println("Filter Called"); 309 | chain.doFilter(req, resp); 310 | } 311 | } 312 | 313 | ``` 314 | To map filter on specific URL pattren, we need to do configuration. create pakage **config** and create class that will map URL pattren as follows: [(referance commit)](https://github.com/fawad1997/SpringWebAPI/commit/0034545758cbe486666a85cc32916a2821681a25) 315 | ```java 316 | @Configuration 317 | public class MyFilterConfig { 318 | @Bean 319 | public FilterRegistrationBean registrationBean(){ 320 | FilterRegistrationBean registrationBean = new FilterRegistrationBean<>(); 321 | registrationBean.setFilter(new MyFilter()); 322 | registrationBean.addUrlPatterns("/person/*"); 323 | return registrationBean; 324 | } 325 | } 326 | ``` 327 | 328 | # Spring Security 329 | ## Spring Basic Security 330 | To implement spring security, First we need to add **Spring Web Security** from maven repository 331 | - [Spring Boot Web Security](https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-security) 332 | 333 | Now we should have a table in database to register and authenticate users. 334 | #### Create table for application users 335 | Lets create a table/entity for our application users. e.g. **ApplicationUser** 336 | ```java 337 | @Entity 338 | @Table(name = "user") 339 | @Data 340 | @NoArgsConstructor 341 | @AllArgsConstructor 342 | public class ApplicationUser { 343 | @Id 344 | @GeneratedValue(strategy = GenerationType.IDENTITY) 345 | private int id; 346 | @NotBlank(message = "username cann't be blank") 347 | @Column(unique = true,nullable = false) 348 | private String username; 349 | @Column(nullable = false) 350 | private String password; 351 | } 352 | ``` 353 | #### Create repository 354 | Add an extra method **findByUsername** which will help us to find user by their username. 355 | ```java 356 | @Repository 357 | public interface UserRepository extends JpaRepository { 358 | ApplicationUser findByUsername(String username); 359 | } 360 | ``` 361 | #### Create Service that will implement UserDetailsService 362 | Now create a service that should implement **UserDetailsService** as follows 363 | ```java 364 | @Service 365 | public class UserService implements UserDetailsService { 366 | @Autowired 367 | private UserRepository userRepository; 368 | @Override 369 | public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { 370 | ApplicationUser user = userRepository.findByUsername(username); 371 | return new User(user.getUsername(),user.getPassword(),new ArrayList<>()); 372 | } 373 | } 374 | ``` 375 | where **User** is from ```import org.springframework.security.core.userdetails.User;``` following import. 376 | #### Create Security Configuration File 377 | Now create a configure class that will extend **WebSecurityConfigurerAdapter** and tell spring security to use our own userdetails service. 378 | ```java 379 | @Configuration 380 | @EnableWebSecurity 381 | public class SecurityConfig extends WebSecurityConfigurerAdapter { 382 | @Autowired 383 | private UserService userService; 384 | 385 | @Override 386 | protected void configure(AuthenticationManagerBuilder auth) throws Exception { 387 | auth.userDetailsService(userService).passwordEncoder(passwordEncoder()); 388 | } 389 | @Bean 390 | public BCryptPasswordEncoder passwordEncoder() { 391 | return new BCryptPasswordEncoder(); 392 | } 393 | } 394 | ``` 395 | Now application will use our own customized security 396 | #### Add data to database on application start 397 | (Optional) Suppose we want to new user data into database when the application starts we can create new method in main class and use **@PostConstruct** annotation to bind it with application start. 398 | ```java 399 | @Autowired 400 | @Autowired 401 | private UserRepository userRepository; 402 | @Autowired 403 | private BCryptPasswordEncoder passwordEncoder; 404 | @PostConstruct 405 | public void seedUser() { 406 | if (userRepository.findByUsername("test") == null) { 407 | String encodedPassword = passwordEncoder.encode("12345"); 408 | ApplicationUser user = new ApplicationUser(1, "test", encodedPassword); 409 | userRepository.save(user); 410 | } 411 | } 412 | ``` 413 | ## Jwt Authentication 414 | [JSON Web Tokens](https://jwt.io/) are an open, industry standard RFC 7519 method for representing claims securely between two parties. 415 | Its on one of the best way of authentication of modern time. 416 | ![](/gitimages/jwt.jpg) 417 | #### Adding Dependencies 418 | To include Jwt in your project, you need to include the following dependencies: 419 | - [JSON Web Token](https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt/0.9.1) 420 | - [JAXB API](https://mvnrepository.com/artifact/javax.xml.bind/jaxb-api/2.3.1) 421 | 422 | you need to include [JAXB API](https://mvnrepository.com/artifact/javax.xml.bind/jaxb-api/2.3.1) if you are using Java 9 or above. 423 | 424 | #### Create JwtUtilService 425 | Now create a JwtUtilService that will contains business logic needed by Jwt. Copy it as given below 426 | ```java 427 | @Service 428 | public class JwtUtilService { 429 | private final String secret = "fawad"; 430 | 431 | public String extractUsername(String token) { 432 | return extractClaim(token, Claims::getSubject); 433 | } 434 | 435 | public Date extractExpiration(String token) { 436 | return extractClaim(token, Claims::getExpiration); 437 | } 438 | 439 | public T extractClaim(String token, Function claimsResolver) { 440 | final Claims claims = extractAllClaims(token); 441 | return claimsResolver.apply(claims); 442 | } 443 | private Claims extractAllClaims(String token) { 444 | return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody(); 445 | } 446 | 447 | private Boolean isTokenExpired(String token) { 448 | return extractExpiration(token).before(new Date()); 449 | } 450 | 451 | public String generateToken(String username) { 452 | Map claims = new HashMap<>(); 453 | return createToken(claims, username); 454 | } 455 | 456 | private String createToken(Map claims, String subject) { 457 | 458 | return Jwts.builder().setClaims(claims).setSubject(subject).setIssuedAt(new Date(System.currentTimeMillis())) 459 | .setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 10)) 460 | .signWith(SignatureAlgorithm.HS256, secret).compact(); 461 | } 462 | 463 | public Boolean validateToken(String token, UserDetails userDetails) { 464 | final String username = extractUsername(token); 465 | return (username.equals(userDetails.getUsername()) && !isTokenExpired(token)); 466 | } 467 | } 468 | ``` 469 | 470 | #### Generate Token 471 | Now lets create a controller that will be responsible for creating **User** account and generate **Jwt Token**. 472 | 473 | Before creating token you need to add one more method in your **SecurityConfig** class for AuthenticationManager, as we will be using it in our controller. 474 | ```java 475 | @Bean(name = BeanIds.AUTHENTICATION_MANAGER) 476 | @Override 477 | public AuthenticationManager authenticationManagerBean() throws Exception { 478 | return super.authenticationManagerBean(); 479 | } 480 | ``` 481 | add it in **SecurityConfig** class. 482 | 483 | Now lets create a controller to generate token 484 | ```java 485 | @RestController 486 | @RequestMapping("/account") 487 | public class AccountController { 488 | @Autowired 489 | private AuthenticationManager authenticationManager; 490 | @Autowired 491 | private JwtUtilService jwtUtil; 492 | @Autowired 493 | private UserService userService; 494 | 495 | @PostMapping("/login") 496 | public String login(@RequestBody ApplicationUser userCredentials) throws Exception { 497 | try { 498 | authenticationManager.authenticate( 499 | new UsernamePasswordAuthenticationToken(userCredentials.getUsername(),userCredentials.getPassword()) 500 | ); 501 | }catch (Exception e){ throw new Exception("Invalid Credentials");} 502 | return jwtUtil.generateToken(userCredentials.getUsername()); 503 | } 504 | } 505 | ``` 506 | #### Allow Anonymous request 507 | As authorization is applied to every page, but we want that there should be no authorization on **AccountController** so we can register or login user, to do that, you need to modify **SecurityConfig** file. 508 | ```java 509 | @Override 510 | protected void configure(HttpSecurity http) throws Exception { 511 | http.csrf().disable().authorizeRequests() 512 | .antMatchers("/account/**").permitAll() 513 | .anyRequest().authenticated(); 514 | } 515 | ``` 516 | 517 | #### Create filter to check token 518 | Now lets create a filter that will extend **OncePerRequestFilter** to verify token. 519 | ```java 520 | @Component 521 | public class JwtFilter extends OncePerRequestFilter { 522 | 523 | @Autowired 524 | private JwtUtilService jwtUtil; 525 | @Autowired 526 | private UserService userService; 527 | @Override 528 | protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException { 529 | String authorizationHeader = httpServletRequest.getHeader("Authorization"); 530 | //eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJmYXdhZDE5OTciLCJleHAiOjE1ODcxNTA2NjgsImlhdCI6MTU4NzE0ODg2OH0.OEvx_a1nTW2vzH7ofuDXJHL8By_32_D3OIfycBoXykY 531 | String token = null; 532 | String username = null; 533 | if(authorizationHeader!=null && authorizationHeader.startsWith("Bearer")){ 534 | token = authorizationHeader.substring(7); 535 | username = jwtUtil.extractUsername(token); 536 | } 537 | if(username!=null && SecurityContextHolder.getContext().getAuthentication() == null){ 538 | UserDetails userDetails = userService.loadUserByUsername(username); 539 | if(jwtUtil.validateToken(token,userDetails)){ 540 | UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken( 541 | userDetails,null,userDetails.getAuthorities()); 542 | authenticationToken.setDetails( 543 | new WebAuthenticationDetailsSource().buildDetails(httpServletRequest) 544 | ); 545 | SecurityContextHolder.getContext().setAuthentication(authenticationToken); 546 | } 547 | } 548 | filterChain.doFilter(httpServletRequest,httpServletResponse); 549 | } 550 | } 551 | ``` 552 | and in the Security config, register filter and stateless session. Inject **JwtFilter** using **@Autowired** and update the configure method. 553 | ```java 554 | @Override 555 | protected void configure(HttpSecurity http) throws Exception { 556 | http.csrf().disable().authorizeRequests() 557 | .antMatchers("/account/**").permitAll() 558 | .anyRequest().authenticated() 559 | .and().exceptionHandling() 560 | .and().sessionManagement() 561 | .sessionCreationPolicy(SessionCreationPolicy.STATELESS); 562 | http.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class); 563 | } 564 | ``` 565 | #### Register User 566 | Create a method in **UserService** that will contain business logic of user creation. 567 | ```java 568 | @Autowired 569 | private BCryptPasswordEncoder passwordEncoder; 570 | public Boolean registerUser(ApplicationUser user){ 571 | String passwordHash = passwordEncoder.encode(user.getPassword()); 572 | user.setPassword(passwordHash); 573 | userRepository.save(user); 574 | return true; 575 | } 576 | ``` 577 | 578 | Now create a method in controller to register user. 579 | ```java 580 | @PostMapping("/register") 581 | public String register(@RequestBody ApplicationUser user){ 582 | if(userService.registerUser(user)){ 583 | return "User Created"; 584 | } 585 | return "User Creation Failed!"; 586 | } 587 | ``` 588 | -------------------------------------------------------------------------------- /gitimages/jwt.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fawad1997/SpringWebAPI/89598fc9c372bba60ec3ade089f1ca9215dd0c38/gitimages/jwt.jpg -------------------------------------------------------------------------------- /gitimages/projectcreate.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fawad1997/SpringWebAPI/89598fc9c372bba60ec3ade089f1ca9215dd0c38/gitimages/projectcreate.jpg -------------------------------------------------------------------------------- /mvnw: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # ---------------------------------------------------------------------------- 3 | # Licensed to the Apache Software Foundation (ASF) under one 4 | # or more contributor license agreements. See the NOTICE file 5 | # distributed with this work for additional information 6 | # regarding copyright ownership. The ASF licenses this file 7 | # to you under the Apache License, Version 2.0 (the 8 | # "License"); you may not use this file except in compliance 9 | # with the License. You may obtain a copy of the License at 10 | # 11 | # https://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, 14 | # software distributed under the License is distributed on an 15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | # KIND, either express or implied. See the License for the 17 | # specific language governing permissions and limitations 18 | # under the License. 19 | # ---------------------------------------------------------------------------- 20 | 21 | # ---------------------------------------------------------------------------- 22 | # Maven Start Up Batch script 23 | # 24 | # Required ENV vars: 25 | # ------------------ 26 | # JAVA_HOME - location of a JDK home dir 27 | # 28 | # Optional ENV vars 29 | # ----------------- 30 | # M2_HOME - location of maven2's installed home dir 31 | # MAVEN_OPTS - parameters passed to the Java VM when running Maven 32 | # e.g. to debug Maven itself, use 33 | # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 34 | # MAVEN_SKIP_RC - flag to disable loading of mavenrc files 35 | # ---------------------------------------------------------------------------- 36 | 37 | if [ -z "$MAVEN_SKIP_RC" ]; then 38 | 39 | if [ -f /etc/mavenrc ]; then 40 | . /etc/mavenrc 41 | fi 42 | 43 | if [ -f "$HOME/.mavenrc" ]; then 44 | . "$HOME/.mavenrc" 45 | fi 46 | 47 | fi 48 | 49 | # OS specific support. $var _must_ be set to either true or false. 50 | cygwin=false 51 | darwin=false 52 | mingw=false 53 | case "$(uname)" in 54 | CYGWIN*) cygwin=true ;; 55 | MINGW*) mingw=true ;; 56 | Darwin*) 57 | darwin=true 58 | # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home 59 | # See https://developer.apple.com/library/mac/qa/qa1170/_index.html 60 | if [ -z "$JAVA_HOME" ]; then 61 | if [ -x "/usr/libexec/java_home" ]; then 62 | export JAVA_HOME="$(/usr/libexec/java_home)" 63 | else 64 | export JAVA_HOME="/Library/Java/Home" 65 | fi 66 | fi 67 | ;; 68 | esac 69 | 70 | if [ -z "$JAVA_HOME" ]; then 71 | if [ -r /etc/gentoo-release ]; then 72 | JAVA_HOME=$(java-config --jre-home) 73 | fi 74 | fi 75 | 76 | if [ -z "$M2_HOME" ]; then 77 | ## resolve links - $0 may be a link to maven's home 78 | PRG="$0" 79 | 80 | # need this for relative symlinks 81 | while [ -h "$PRG" ]; do 82 | ls=$(ls -ld "$PRG") 83 | link=$(expr "$ls" : '.*-> \(.*\)$') 84 | if expr "$link" : '/.*' >/dev/null; then 85 | PRG="$link" 86 | else 87 | PRG="$(dirname "$PRG")/$link" 88 | fi 89 | done 90 | 91 | saveddir=$(pwd) 92 | 93 | M2_HOME=$(dirname "$PRG")/.. 94 | 95 | # make it fully qualified 96 | M2_HOME=$(cd "$M2_HOME" && pwd) 97 | 98 | cd "$saveddir" 99 | # echo Using m2 at $M2_HOME 100 | fi 101 | 102 | # For Cygwin, ensure paths are in UNIX format before anything is touched 103 | if $cygwin; then 104 | [ -n "$M2_HOME" ] && 105 | M2_HOME=$(cygpath --unix "$M2_HOME") 106 | [ -n "$JAVA_HOME" ] && 107 | JAVA_HOME=$(cygpath --unix "$JAVA_HOME") 108 | [ -n "$CLASSPATH" ] && 109 | CLASSPATH=$(cygpath --path --unix "$CLASSPATH") 110 | fi 111 | 112 | # For Mingw, ensure paths are in UNIX format before anything is touched 113 | if $mingw; then 114 | [ -n "$M2_HOME" ] && 115 | M2_HOME="$( ( 116 | cd "$M2_HOME" 117 | pwd 118 | ))" 119 | [ -n "$JAVA_HOME" ] && 120 | JAVA_HOME="$( ( 121 | cd "$JAVA_HOME" 122 | pwd 123 | ))" 124 | fi 125 | 126 | if [ -z "$JAVA_HOME" ]; then 127 | javaExecutable="$(which javac)" 128 | if [ -n "$javaExecutable" ] && ! [ "$(expr \"$javaExecutable\" : '\([^ ]*\)')" = "no" ]; then 129 | # readlink(1) is not available as standard on Solaris 10. 130 | readLink=$(which readlink) 131 | if [ ! $(expr "$readLink" : '\([^ ]*\)') = "no" ]; then 132 | if $darwin; then 133 | javaHome="$(dirname \"$javaExecutable\")" 134 | javaExecutable="$(cd \"$javaHome\" && pwd -P)/javac" 135 | else 136 | javaExecutable="$(readlink -f \"$javaExecutable\")" 137 | fi 138 | javaHome="$(dirname \"$javaExecutable\")" 139 | javaHome=$(expr "$javaHome" : '\(.*\)/bin') 140 | JAVA_HOME="$javaHome" 141 | export JAVA_HOME 142 | fi 143 | fi 144 | fi 145 | 146 | if [ -z "$JAVACMD" ]; then 147 | if [ -n "$JAVA_HOME" ]; then 148 | if [ -x "$JAVA_HOME/jre/sh/java" ]; then 149 | # IBM's JDK on AIX uses strange locations for the executables 150 | JAVACMD="$JAVA_HOME/jre/sh/java" 151 | else 152 | JAVACMD="$JAVA_HOME/bin/java" 153 | fi 154 | else 155 | JAVACMD="$(which java)" 156 | fi 157 | fi 158 | 159 | if [ ! -x "$JAVACMD" ]; then 160 | echo "Error: JAVA_HOME is not defined correctly." >&2 161 | echo " We cannot execute $JAVACMD" >&2 162 | exit 1 163 | fi 164 | 165 | if [ -z "$JAVA_HOME" ]; then 166 | echo "Warning: JAVA_HOME environment variable is not set." 167 | fi 168 | 169 | CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher 170 | 171 | # traverses directory structure from process work directory to filesystem root 172 | # first directory with .mvn subdirectory is considered project base directory 173 | find_maven_basedir() { 174 | 175 | if [ -z "$1" ]; then 176 | echo "Path not specified to find_maven_basedir" 177 | return 1 178 | fi 179 | 180 | basedir="$1" 181 | wdir="$1" 182 | while [ "$wdir" != '/' ]; do 183 | if [ -d "$wdir"/.mvn ]; then 184 | basedir=$wdir 185 | break 186 | fi 187 | # workaround for JBEAP-8937 (on Solaris 10/Sparc) 188 | if [ -d "${wdir}" ]; then 189 | wdir=$( 190 | cd "$wdir/.." 191 | pwd 192 | ) 193 | fi 194 | # end of workaround 195 | done 196 | echo "${basedir}" 197 | } 198 | 199 | # concatenates all lines of a file 200 | concat_lines() { 201 | if [ -f "$1" ]; then 202 | echo "$(tr -s '\n' ' ' <"$1")" 203 | fi 204 | } 205 | 206 | BASE_DIR=$(find_maven_basedir "$(pwd)") 207 | if [ -z "$BASE_DIR" ]; then 208 | exit 1 209 | fi 210 | 211 | ########################################################################################## 212 | # Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 213 | # This allows using the maven wrapper in projects that prohibit checking in binary data. 214 | ########################################################################################## 215 | if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then 216 | if [ "$MVNW_VERBOSE" = true ]; then 217 | echo "Found .mvn/wrapper/maven-wrapper.jar" 218 | fi 219 | else 220 | if [ "$MVNW_VERBOSE" = true ]; then 221 | echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." 222 | fi 223 | if [ -n "$MVNW_REPOURL" ]; then 224 | jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" 225 | else 226 | jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" 227 | fi 228 | while IFS="=" read key value; do 229 | case "$key" in wrapperUrl) 230 | jarUrl="$value" 231 | break 232 | ;; 233 | esac 234 | done <"$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" 235 | if [ "$MVNW_VERBOSE" = true ]; then 236 | echo "Downloading from: $jarUrl" 237 | fi 238 | wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" 239 | if $cygwin; then 240 | wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath") 241 | fi 242 | 243 | if command -v wget >/dev/null; then 244 | if [ "$MVNW_VERBOSE" = true ]; then 245 | echo "Found wget ... using wget" 246 | fi 247 | if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then 248 | wget "$jarUrl" -O "$wrapperJarPath" 249 | else 250 | wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" 251 | fi 252 | elif command -v curl >/dev/null; then 253 | if [ "$MVNW_VERBOSE" = true ]; then 254 | echo "Found curl ... using curl" 255 | fi 256 | if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then 257 | curl -o "$wrapperJarPath" "$jarUrl" -f 258 | else 259 | curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f 260 | fi 261 | 262 | else 263 | if [ "$MVNW_VERBOSE" = true ]; then 264 | echo "Falling back to using Java to download" 265 | fi 266 | javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" 267 | # For Cygwin, switch paths to Windows format before running javac 268 | if $cygwin; then 269 | javaClass=$(cygpath --path --windows "$javaClass") 270 | fi 271 | if [ -e "$javaClass" ]; then 272 | if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then 273 | if [ "$MVNW_VERBOSE" = true ]; then 274 | echo " - Compiling MavenWrapperDownloader.java ..." 275 | fi 276 | # Compiling the Java class 277 | ("$JAVA_HOME/bin/javac" "$javaClass") 278 | fi 279 | if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then 280 | # Running the downloader 281 | if [ "$MVNW_VERBOSE" = true ]; then 282 | echo " - Running MavenWrapperDownloader.java ..." 283 | fi 284 | ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") 285 | fi 286 | fi 287 | fi 288 | fi 289 | ########################################################################################## 290 | # End of extension 291 | ########################################################################################## 292 | 293 | export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} 294 | if [ "$MVNW_VERBOSE" = true ]; then 295 | echo $MAVEN_PROJECTBASEDIR 296 | fi 297 | MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" 298 | 299 | # For Cygwin, switch paths to Windows format before running java 300 | if $cygwin; then 301 | [ -n "$M2_HOME" ] && 302 | M2_HOME=$(cygpath --path --windows "$M2_HOME") 303 | [ -n "$JAVA_HOME" ] && 304 | JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME") 305 | [ -n "$CLASSPATH" ] && 306 | CLASSPATH=$(cygpath --path --windows "$CLASSPATH") 307 | [ -n "$MAVEN_PROJECTBASEDIR" ] && 308 | MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR") 309 | fi 310 | 311 | # Provide a "standardized" way to retrieve the CLI args that will 312 | # work with both Windows and non-Windows executions. 313 | MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" 314 | export MAVEN_CMD_LINE_ARGS 315 | 316 | WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 317 | 318 | exec "$JAVACMD" \ 319 | $MAVEN_OPTS \ 320 | -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ 321 | "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ 322 | ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" 323 | -------------------------------------------------------------------------------- /mvnw.cmd: -------------------------------------------------------------------------------- 1 | @REM ---------------------------------------------------------------------------- 2 | @REM Licensed to the Apache Software Foundation (ASF) under one 3 | @REM or more contributor license agreements. See the NOTICE file 4 | @REM distributed with this work for additional information 5 | @REM regarding copyright ownership. The ASF licenses this file 6 | @REM to you under the Apache License, Version 2.0 (the 7 | @REM "License"); you may not use this file except in compliance 8 | @REM with the License. You may obtain a copy of the License at 9 | @REM 10 | @REM https://www.apache.org/licenses/LICENSE-2.0 11 | @REM 12 | @REM Unless required by applicable law or agreed to in writing, 13 | @REM software distributed under the License is distributed on an 14 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | @REM KIND, either express or implied. See the License for the 16 | @REM specific language governing permissions and limitations 17 | @REM under the License. 18 | @REM ---------------------------------------------------------------------------- 19 | 20 | @REM ---------------------------------------------------------------------------- 21 | @REM Maven Start Up Batch script 22 | @REM 23 | @REM Required ENV vars: 24 | @REM JAVA_HOME - location of a JDK home dir 25 | @REM 26 | @REM Optional ENV vars 27 | @REM M2_HOME - location of maven2's installed home dir 28 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands 29 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending 30 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven 31 | @REM e.g. to debug Maven itself, use 32 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 33 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files 34 | @REM ---------------------------------------------------------------------------- 35 | 36 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' 37 | @echo off 38 | @REM set title of command window 39 | title %0 40 | @REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' 41 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% 42 | 43 | @REM set %HOME% to equivalent of $HOME 44 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") 45 | 46 | @REM Execute a user defined script before this one 47 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre 48 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending 49 | if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" 50 | if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" 51 | :skipRcPre 52 | 53 | @setlocal 54 | 55 | set ERROR_CODE=0 56 | 57 | @REM To isolate internal variables from possible post scripts, we use another setlocal 58 | @setlocal 59 | 60 | @REM ==== START VALIDATION ==== 61 | if not "%JAVA_HOME%" == "" goto OkJHome 62 | 63 | echo. 64 | echo Error: JAVA_HOME not found in your environment. >&2 65 | echo Please set the JAVA_HOME variable in your environment to match the >&2 66 | echo location of your Java installation. >&2 67 | echo. 68 | goto error 69 | 70 | :OkJHome 71 | if exist "%JAVA_HOME%\bin\java.exe" goto init 72 | 73 | echo. 74 | echo Error: JAVA_HOME is set to an invalid directory. >&2 75 | echo JAVA_HOME = "%JAVA_HOME%" >&2 76 | echo Please set the JAVA_HOME variable in your environment to match the >&2 77 | echo location of your Java installation. >&2 78 | echo. 79 | goto error 80 | 81 | @REM ==== END VALIDATION ==== 82 | 83 | :init 84 | 85 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". 86 | @REM Fallback to current working directory if not found. 87 | 88 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% 89 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir 90 | 91 | set EXEC_DIR=%CD% 92 | set WDIR=%EXEC_DIR% 93 | :findBaseDir 94 | IF EXIST "%WDIR%"\.mvn goto baseDirFound 95 | cd .. 96 | IF "%WDIR%"=="%CD%" goto baseDirNotFound 97 | set WDIR=%CD% 98 | goto findBaseDir 99 | 100 | :baseDirFound 101 | set MAVEN_PROJECTBASEDIR=%WDIR% 102 | cd "%EXEC_DIR%" 103 | goto endDetectBaseDir 104 | 105 | :baseDirNotFound 106 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR% 107 | cd "%EXEC_DIR%" 108 | 109 | :endDetectBaseDir 110 | 111 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig 112 | 113 | @setlocal EnableExtensions EnableDelayedExpansion 114 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a 115 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% 116 | 117 | :endReadAdditionalConfig 118 | 119 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" 120 | set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" 121 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 122 | 123 | set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" 124 | 125 | FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( 126 | IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B 127 | ) 128 | 129 | @REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 130 | @REM This allows using the maven wrapper in projects that prohibit checking in binary data. 131 | if exist %WRAPPER_JAR% ( 132 | if "%MVNW_VERBOSE%" == "true" ( 133 | echo Found %WRAPPER_JAR% 134 | ) 135 | ) else ( 136 | if not "%MVNW_REPOURL%" == "" ( 137 | SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" 138 | ) 139 | if "%MVNW_VERBOSE%" == "true" ( 140 | echo Couldn't find %WRAPPER_JAR%, downloading it ... 141 | echo Downloading from: %DOWNLOAD_URL% 142 | ) 143 | 144 | powershell -Command "&{"^ 145 | "$webclient = new-object System.Net.WebClient;"^ 146 | "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ 147 | "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ 148 | "}"^ 149 | "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ 150 | "}" 151 | if "%MVNW_VERBOSE%" == "true" ( 152 | echo Finished downloading %WRAPPER_JAR% 153 | ) 154 | ) 155 | @REM End of extension 156 | 157 | @REM Provide a "standardized" way to retrieve the CLI args that will 158 | @REM work with both Windows and non-Windows executions. 159 | set MAVEN_CMD_LINE_ARGS=%* 160 | 161 | %MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* 162 | if ERRORLEVEL 1 goto error 163 | goto end 164 | 165 | :error 166 | set ERROR_CODE=1 167 | 168 | :end 169 | @endlocal & set ERROR_CODE=%ERROR_CODE% 170 | 171 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost 172 | @REM check for post script, once with legacy .bat ending and once with .cmd ending 173 | if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" 174 | if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" 175 | :skipRcPost 176 | 177 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' 178 | if "%MAVEN_BATCH_PAUSE%" == "on" pause 179 | 180 | if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% 181 | 182 | exit /B %ERROR_CODE% 183 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 2.2.6.RELEASE 9 | 10 | 11 | com.restfulspring 12 | apiexample 13 | 0.0.1-SNAPSHOT 14 | apiexample 15 | Demo project for Spring Boot 16 | 17 | 18 | 1.8 19 | 20 | 21 | 22 | 23 | org.springframework.boot 24 | spring-boot-starter-web 25 | 26 | 27 | 28 | 29 | org.springframework.boot 30 | spring-boot-starter-security 31 | 2.2.6.RELEASE 32 | 33 | 34 | 35 | io.jsonwebtoken 36 | jjwt 37 | 0.9.1 38 | 39 | 40 | 41 | javax.xml.bind 42 | jaxb-api 43 | 2.3.1 44 | 45 | 46 | 47 | 48 | 49 | org.projectlombok 50 | lombok 51 | 1.18.12 52 | provided 53 | 54 | 55 | 56 | org.springframework.boot 57 | spring-boot-starter-data-jpa 58 | 2.2.6.RELEASE 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | mysql 70 | mysql-connector-java 71 | 8.0.19 72 | 73 | 74 | 75 | 76 | org.springframework.boot 77 | spring-boot-starter-test 78 | test 79 | 80 | 81 | org.junit.vintage 82 | junit-vintage-engine 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | org.springframework.boot 92 | spring-boot-maven-plugin 93 | 94 | 95 | 96 | 97 | 98 | -------------------------------------------------------------------------------- /src/main/java/com/restfulspring/apiexample/ApiexampleApplication.java: -------------------------------------------------------------------------------- 1 | package com.restfulspring.apiexample; 2 | 3 | import com.restfulspring.apiexample.entity.ApplicationUser; 4 | import com.restfulspring.apiexample.repository.UserRepository; 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.security.crypto.bcrypt.BCryptPasswordEncoder; 9 | 10 | import javax.annotation.PostConstruct; 11 | 12 | @SpringBootApplication 13 | public class ApiexampleApplication { 14 | 15 | @Autowired 16 | private UserRepository userRepository; 17 | @Autowired 18 | private BCryptPasswordEncoder passwordEncoder; 19 | 20 | @PostConstruct 21 | public void seedUser() { 22 | if (userRepository.findByUsername("test") == null) { 23 | String encodedPassword = passwordEncoder.encode("12345"); 24 | ApplicationUser user = new ApplicationUser(1, "test", encodedPassword); 25 | userRepository.save(user); 26 | } 27 | } 28 | 29 | public static void main(String[] args) { 30 | SpringApplication.run(ApiexampleApplication.class, args); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/restfulspring/apiexample/config/MyFilterConfig.java: -------------------------------------------------------------------------------- 1 | package com.restfulspring.apiexample.config; 2 | 3 | import com.restfulspring.apiexample.filters.MyFilter; 4 | import org.springframework.boot.web.servlet.FilterRegistrationBean; 5 | import org.springframework.context.annotation.Bean; 6 | import org.springframework.context.annotation.Configuration; 7 | 8 | @Configuration 9 | public class MyFilterConfig { 10 | @Bean 11 | public FilterRegistrationBean registrationBean() { 12 | FilterRegistrationBean registrationBean = new FilterRegistrationBean<>(); 13 | registrationBean.setFilter(new MyFilter()); 14 | registrationBean.addUrlPatterns("/person/*"); 15 | return registrationBean; 16 | } 17 | } -------------------------------------------------------------------------------- /src/main/java/com/restfulspring/apiexample/config/SecurityConfig.java: -------------------------------------------------------------------------------- 1 | package com.restfulspring.apiexample.config; 2 | 3 | import com.restfulspring.apiexample.filters.JwtFilter; 4 | import com.restfulspring.apiexample.service.UserService; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.context.annotation.Bean; 7 | import org.springframework.context.annotation.Configuration; 8 | import org.springframework.security.authentication.AuthenticationManager; 9 | import org.springframework.security.config.BeanIds; 10 | import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; 11 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 12 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; 13 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; 14 | import org.springframework.security.config.http.SessionCreationPolicy; 15 | import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; 16 | import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; 17 | 18 | @Configuration 19 | @EnableWebSecurity 20 | public class SecurityConfig extends WebSecurityConfigurerAdapter { 21 | @Autowired 22 | private UserService userService; 23 | @Autowired 24 | private JwtFilter jwtFilter; 25 | 26 | @Override 27 | protected void configure(AuthenticationManagerBuilder auth) throws Exception { 28 | auth.userDetailsService(userService).passwordEncoder(passwordEncoder()); 29 | } 30 | 31 | @Override 32 | protected void configure(HttpSecurity http) throws Exception { 33 | http.csrf().disable().authorizeRequests() 34 | .antMatchers("/account/**", "/person/**").permitAll() 35 | .anyRequest().authenticated() 36 | .and().exceptionHandling() 37 | .and().sessionManagement() 38 | .sessionCreationPolicy(SessionCreationPolicy.STATELESS); 39 | http.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class); 40 | } 41 | 42 | @Bean 43 | public BCryptPasswordEncoder passwordEncoder() { 44 | return new BCryptPasswordEncoder(); 45 | } 46 | 47 | @Bean(name = BeanIds.AUTHENTICATION_MANAGER) 48 | @Override 49 | public AuthenticationManager authenticationManagerBean() throws Exception { 50 | return super.authenticationManagerBean(); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/com/restfulspring/apiexample/controller/AccountController.java: -------------------------------------------------------------------------------- 1 | package com.restfulspring.apiexample.controller; 2 | 3 | import com.restfulspring.apiexample.entity.ApplicationUser; 4 | import com.restfulspring.apiexample.service.JwtUtilService; 5 | import com.restfulspring.apiexample.service.UserService; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.security.authentication.AuthenticationManager; 8 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 9 | import org.springframework.web.bind.annotation.PostMapping; 10 | import org.springframework.web.bind.annotation.RequestBody; 11 | import org.springframework.web.bind.annotation.RequestMapping; 12 | import org.springframework.web.bind.annotation.RestController; 13 | 14 | @RestController 15 | @RequestMapping("/account") 16 | public class AccountController { 17 | @Autowired 18 | private AuthenticationManager authenticationManager; 19 | @Autowired 20 | private JwtUtilService jwtUtil; 21 | @Autowired 22 | private UserService userService; 23 | 24 | @PostMapping("/login") 25 | public String login(@RequestBody ApplicationUser userCredentials) throws Exception { 26 | try { 27 | authenticationManager.authenticate( 28 | new UsernamePasswordAuthenticationToken(userCredentials.getUsername(), userCredentials.getPassword()) 29 | ); 30 | } catch (Exception e) { 31 | throw new Exception("Invalid Credentials"); 32 | } 33 | return jwtUtil.generateToken(userCredentials.getUsername()); 34 | } 35 | 36 | @PostMapping("/register") 37 | public String register(@RequestBody ApplicationUser user) { 38 | if (userService.registerUser(user)) { 39 | return "User Created"; 40 | } 41 | return "User Creation Failed!"; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/com/restfulspring/apiexample/controller/CityController.java: -------------------------------------------------------------------------------- 1 | package com.restfulspring.apiexample.controller; 2 | 3 | import com.restfulspring.apiexample.entity.City; 4 | import com.restfulspring.apiexample.entity.Country; 5 | import com.restfulspring.apiexample.repository.CityRepository; 6 | import com.restfulspring.apiexample.service.CityService; 7 | import com.restfulspring.apiexample.service.CountryService; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.web.bind.annotation.*; 10 | 11 | import java.util.List; 12 | 13 | @RestController 14 | @RequestMapping("/city") 15 | public class CityController { 16 | @Autowired 17 | private CityService cityService; 18 | 19 | @GetMapping 20 | public List getCities(){ 21 | return cityService.getCities(); 22 | } 23 | @GetMapping("/{id}") 24 | public City getCity(@PathVariable int id){ 25 | return cityService.getCity(id); 26 | } 27 | @PostMapping 28 | public City addCity(@RequestBody City city){ 29 | return cityService.addCity(city); 30 | } 31 | @PutMapping("/{id}") 32 | public City addCity(@PathVariable int id,@RequestBody City city){ 33 | return cityService.updateCity(id,city); 34 | } 35 | @DeleteMapping("/{id}") 36 | public void deleteCity(@PathVariable int id){ 37 | cityService.deleteCity(id); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/com/restfulspring/apiexample/controller/CountryController.java: -------------------------------------------------------------------------------- 1 | package com.restfulspring.apiexample.controller; 2 | 3 | import com.restfulspring.apiexample.entity.Country; 4 | import com.restfulspring.apiexample.repository.CountryRepository; 5 | import com.restfulspring.apiexample.service.CountryService; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.web.bind.annotation.*; 8 | 9 | import java.util.List; 10 | 11 | @RestController 12 | @RequestMapping(value = "country") 13 | public class CountryController { 14 | @Autowired 15 | private CountryService countryService; 16 | 17 | @GetMapping 18 | public List getCountries(){ 19 | return countryService.getCountries(); 20 | } 21 | @GetMapping("/{id}") 22 | public Country getCountry(@PathVariable int id){ 23 | return countryService.getCountry(id); 24 | } 25 | @PostMapping 26 | public Country addCountry(@RequestBody Country country){ 27 | return countryService.addCountry(country); 28 | } 29 | @PutMapping("/{id}") 30 | public Country addCountry(@PathVariable int id,@RequestBody Country country){ 31 | return countryService.updateCountry(id,country); 32 | } 33 | @DeleteMapping("/{id}") 34 | public void deleteCountry(@PathVariable int id){ 35 | countryService.deleteCountry(id); 36 | } 37 | } -------------------------------------------------------------------------------- /src/main/java/com/restfulspring/apiexample/controller/HelloController.java: -------------------------------------------------------------------------------- 1 | package com.restfulspring.apiexample.controller; 2 | 3 | import org.springframework.web.bind.annotation.RequestMapping; 4 | import org.springframework.web.bind.annotation.RestController; 5 | 6 | @RestController 7 | public class HelloController { 8 | @RequestMapping(value = "/") 9 | public String sayHello(){ 10 | return "Hello World!"; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/restfulspring/apiexample/controller/PersonController.java: -------------------------------------------------------------------------------- 1 | package com.restfulspring.apiexample.controller; 2 | 3 | import com.restfulspring.apiexample.entity.Person; 4 | import com.restfulspring.apiexample.service.PersonService; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.http.HttpStatus; 7 | import org.springframework.http.ResponseEntity; 8 | import org.springframework.validation.BindingResult; 9 | import org.springframework.validation.FieldError; 10 | import org.springframework.web.bind.annotation.*; 11 | 12 | import javax.validation.Valid; 13 | import java.util.HashMap; 14 | import java.util.List; 15 | import java.util.Map; 16 | 17 | @RestController 18 | @RequestMapping(value = "/person") 19 | @CrossOrigin(origins = "*", allowedHeaders = "*") 20 | public class PersonController { 21 | @Autowired 22 | private PersonService personService; 23 | @GetMapping 24 | public List getPersons(){ 25 | return personService.getPersons(); 26 | } 27 | 28 | @GetMapping(value = "/{id}") 29 | public Person getPerson(@PathVariable int id){ 30 | return personService.getPerson(id); 31 | } 32 | 33 | @PostMapping 34 | public ResponseEntity addPerson(@Valid @RequestBody Person person, BindingResult result) { 35 | if (result.hasErrors()) { 36 | Map errors = new HashMap<>(); 37 | for (FieldError error : result.getFieldErrors()) { 38 | errors.put(error.getField(), error.getDefaultMessage()); 39 | } 40 | return new ResponseEntity>(errors, HttpStatus.BAD_REQUEST); 41 | } 42 | Person p = personService.addPerson(person); 43 | return new ResponseEntity(p, HttpStatus.CREATED); 44 | } 45 | 46 | @PutMapping(value = "/{id}") 47 | public Person updatePerson(@PathVariable int id, @RequestBody Person person) { 48 | return personService.updatePerson(id, person); 49 | } 50 | 51 | @DeleteMapping(value = "/{id}") 52 | public void deletePerson(@PathVariable int id){ 53 | personService.deletePerson(id); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/com/restfulspring/apiexample/entity/ApplicationUser.java: -------------------------------------------------------------------------------- 1 | package com.restfulspring.apiexample.entity; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | import javax.persistence.*; 8 | 9 | @Entity 10 | @Table(name = "user") 11 | @Data 12 | @NoArgsConstructor 13 | @AllArgsConstructor 14 | public class ApplicationUser { 15 | @Id 16 | @GeneratedValue(strategy = GenerationType.IDENTITY) 17 | private int id; 18 | @Column(unique = true, nullable = false) 19 | private String username; 20 | @Column(nullable = false) 21 | private String password; 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/restfulspring/apiexample/entity/City.java: -------------------------------------------------------------------------------- 1 | package com.restfulspring.apiexample.entity; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | import javax.persistence.*; 8 | 9 | @Entity 10 | @Data 11 | @NoArgsConstructor 12 | @AllArgsConstructor 13 | public class City { 14 | @Id 15 | @GeneratedValue(strategy = GenerationType.AUTO) 16 | @Column(name = "cityId") 17 | private int cityId; 18 | private String cityName; 19 | 20 | // @ManyToOne(cascade = CascadeType.ALL) 21 | // @JoinColumn(name = "countryId") 22 | // private Country country; 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/restfulspring/apiexample/entity/Country.java: -------------------------------------------------------------------------------- 1 | package com.restfulspring.apiexample.entity; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | import lombok.ToString; 7 | 8 | import javax.persistence.*; 9 | import java.util.List; 10 | 11 | @Entity 12 | @Data 13 | @NoArgsConstructor 14 | @AllArgsConstructor 15 | @ToString 16 | public class Country { 17 | @Id 18 | @GeneratedValue(strategy = GenerationType.AUTO) 19 | @Column(name = "countryId") 20 | private int countryId; 21 | private String countryName; 22 | 23 | @OneToMany(cascade = CascadeType.ALL) 24 | @JoinColumn(name = "cc_fk",referencedColumnName = "countryId") 25 | private List cities; 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/restfulspring/apiexample/entity/Course.java: -------------------------------------------------------------------------------- 1 | package com.restfulspring.apiexample.entity; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | import javax.persistence.*; 8 | import java.util.List; 9 | 10 | @Entity 11 | @Data 12 | @NoArgsConstructor 13 | @AllArgsConstructor 14 | public class Course { 15 | @Id 16 | @GeneratedValue(strategy = GenerationType.IDENTITY) 17 | private int courseId; 18 | private String courseTitle; 19 | private String courseCode; 20 | @ManyToMany 21 | private List students; 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/restfulspring/apiexample/entity/Person.java: -------------------------------------------------------------------------------- 1 | package com.restfulspring.apiexample.entity; 2 | 3 | import com.fasterxml.jackson.annotation.JsonFormat; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | import javax.persistence.Entity; 9 | import javax.persistence.GeneratedValue; 10 | import javax.persistence.GenerationType; 11 | import javax.persistence.Id; 12 | import javax.validation.constraints.*; 13 | import java.time.LocalDate; 14 | 15 | @Entity 16 | @Data 17 | @NoArgsConstructor 18 | @AllArgsConstructor 19 | public class Person { 20 | @Id 21 | @GeneratedValue(strategy = GenerationType.IDENTITY) 22 | //@JsonProperty(value = "ID") 23 | private int id; 24 | @NotNull(message = "Name cann't be null") 25 | @Size(min = 2, max = 100, message = "Name must be minimum 2 characters and maximum 100 characters long") 26 | private String name; 27 | @Email 28 | private String email; 29 | @Min(8) 30 | @Max(110) 31 | private int age; 32 | private double height; 33 | @Pattern(regexp = "^\\(?(\\d{5})\\)?[-]?(\\d{7})[-]?(\\d{1})$", message = "CNIC sholud be in format xxxxx-xxxxxxx-x") 34 | private String cnic; 35 | @Past 36 | @JsonFormat(pattern = "yyyy-MM-dd") 37 | private LocalDate dob; 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/com/restfulspring/apiexample/entity/Student.java: -------------------------------------------------------------------------------- 1 | package com.restfulspring.apiexample.entity; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | import javax.persistence.*; 8 | import java.util.List; 9 | 10 | @Entity 11 | @Data 12 | @NoArgsConstructor 13 | @AllArgsConstructor 14 | public class Student { 15 | @Id 16 | @GeneratedValue(strategy = GenerationType.IDENTITY) 17 | private int studentId; 18 | private String name; 19 | private String regNo; 20 | @ManyToMany(cascade = CascadeType.ALL) 21 | @JoinTable(name = "student_courses", 22 | joinColumns = {@JoinColumn(name = "studentId")}, 23 | inverseJoinColumns = {@JoinColumn(name = "courseId")}) 24 | private List courses; 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/restfulspring/apiexample/exception/NotFoundException.java: -------------------------------------------------------------------------------- 1 | package com.restfulspring.apiexample.exception; 2 | 3 | import org.springframework.http.HttpStatus; 4 | import org.springframework.web.bind.annotation.ResponseStatus; 5 | 6 | @ResponseStatus(HttpStatus.NOT_FOUND) 7 | public class NotFoundException extends RuntimeException { 8 | public NotFoundException(String message){ 9 | super(message); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/restfulspring/apiexample/filters/JwtFilter.java: -------------------------------------------------------------------------------- 1 | package com.restfulspring.apiexample.filters; 2 | 3 | import com.restfulspring.apiexample.service.JwtUtilService; 4 | import com.restfulspring.apiexample.service.UserService; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 7 | import org.springframework.security.core.context.SecurityContextHolder; 8 | import org.springframework.security.core.userdetails.UserDetails; 9 | import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; 10 | import org.springframework.stereotype.Component; 11 | import org.springframework.web.filter.OncePerRequestFilter; 12 | 13 | import javax.servlet.FilterChain; 14 | import javax.servlet.ServletException; 15 | import javax.servlet.http.HttpServletRequest; 16 | import javax.servlet.http.HttpServletResponse; 17 | import java.io.IOException; 18 | 19 | @Component 20 | public class JwtFilter extends OncePerRequestFilter { 21 | 22 | @Autowired 23 | private JwtUtilService jwtUtil; 24 | @Autowired 25 | private UserService userService; 26 | 27 | @Override 28 | protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException { 29 | String authorizationHeader = httpServletRequest.getHeader("Authorization"); 30 | //eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJmYXdhZDE5OTciLCJleHAiOjE1ODcxNTA2NjgsImlhdCI6MTU4NzE0ODg2OH0.OEvx_a1nTW2vzH7ofuDXJHL8By_32_D3OIfycBoXykY 31 | String token = null; 32 | String username = null; 33 | if (authorizationHeader != null && authorizationHeader.startsWith("Bearer")) { 34 | token = authorizationHeader.substring(7); 35 | username = jwtUtil.extractUsername(token); 36 | } 37 | if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) { 38 | UserDetails userDetails = userService.loadUserByUsername(username); 39 | if (jwtUtil.validateToken(token, userDetails)) { 40 | UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken( 41 | userDetails, null, userDetails.getAuthorities()); 42 | authenticationToken.setDetails( 43 | new WebAuthenticationDetailsSource().buildDetails(httpServletRequest) 44 | ); 45 | SecurityContextHolder.getContext().setAuthentication(authenticationToken); 46 | } 47 | } 48 | filterChain.doFilter(httpServletRequest, httpServletResponse); 49 | } 50 | } -------------------------------------------------------------------------------- /src/main/java/com/restfulspring/apiexample/filters/MyFilter.java: -------------------------------------------------------------------------------- 1 | package com.restfulspring.apiexample.filters; 2 | 3 | import org.springframework.stereotype.Component; 4 | 5 | import javax.servlet.*; 6 | import java.io.IOException; 7 | 8 | @Component 9 | public class MyFilter implements Filter { 10 | public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException { 11 | System.out.println("Filter Called"); 12 | chain.doFilter(req, resp); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/restfulspring/apiexample/repository/CityRepository.java: -------------------------------------------------------------------------------- 1 | package com.restfulspring.apiexample.repository; 2 | 3 | import com.restfulspring.apiexample.entity.City; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | import org.springframework.stereotype.Repository; 6 | 7 | @Repository 8 | public interface CityRepository extends JpaRepository { 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/com/restfulspring/apiexample/repository/CountryRepository.java: -------------------------------------------------------------------------------- 1 | package com.restfulspring.apiexample.repository; 2 | 3 | import com.restfulspring.apiexample.entity.Country; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | import org.springframework.stereotype.Repository; 6 | 7 | @Repository 8 | public interface CountryRepository extends JpaRepository { 9 | 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/restfulspring/apiexample/repository/PersonRepository.java: -------------------------------------------------------------------------------- 1 | package com.restfulspring.apiexample.repository; 2 | 3 | import com.restfulspring.apiexample.entity.Person; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | import org.springframework.stereotype.Repository; 6 | 7 | @Repository 8 | public interface PersonRepository extends JpaRepository { 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/com/restfulspring/apiexample/repository/UserRepository.java: -------------------------------------------------------------------------------- 1 | package com.restfulspring.apiexample.repository; 2 | 3 | import com.restfulspring.apiexample.entity.ApplicationUser; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | import org.springframework.stereotype.Repository; 6 | 7 | @Repository 8 | public interface UserRepository extends JpaRepository { 9 | ApplicationUser findByUsername(String username); 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/restfulspring/apiexample/service/CityService.java: -------------------------------------------------------------------------------- 1 | package com.restfulspring.apiexample.service; 2 | 3 | import com.restfulspring.apiexample.entity.City; 4 | import com.restfulspring.apiexample.entity.Country; 5 | import com.restfulspring.apiexample.exception.NotFoundException; 6 | import com.restfulspring.apiexample.repository.CityRepository; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.stereotype.Service; 9 | 10 | import java.util.List; 11 | import java.util.Optional; 12 | 13 | @Service 14 | public class CityService { 15 | @Autowired 16 | private CityRepository cityRepository; 17 | public List getCities(){ 18 | return cityRepository.findAll(); 19 | } 20 | public City getCity(int id){ 21 | Optional city = cityRepository.findById(id); 22 | if(!city.isPresent()) 23 | throw new NotFoundException("City not found!"); 24 | return city.get(); 25 | } 26 | public City addCity(City city){ 27 | return cityRepository.save(city); 28 | } 29 | public City updateCity(int id,City city){ 30 | city.setCityId(id); 31 | return cityRepository.save(city); 32 | } 33 | public void deleteCity(int id){ 34 | cityRepository.deleteById(id); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/restfulspring/apiexample/service/CountryService.java: -------------------------------------------------------------------------------- 1 | package com.restfulspring.apiexample.service; 2 | 3 | import com.restfulspring.apiexample.entity.Country; 4 | import com.restfulspring.apiexample.exception.NotFoundException; 5 | import com.restfulspring.apiexample.repository.CountryRepository; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.stereotype.Service; 8 | 9 | import java.util.List; 10 | import java.util.Optional; 11 | 12 | @Service 13 | public class CountryService { 14 | @Autowired 15 | private CountryRepository countryRepository; 16 | 17 | public List getCountries(){ 18 | return countryRepository.findAll(); 19 | } 20 | public Country getCountry(int id){ 21 | Optional country = countryRepository.findById(id); 22 | if(!country.isPresent()) 23 | throw new NotFoundException("Country not found!"); 24 | return country.get(); 25 | } 26 | public Country addCountry(Country country){ 27 | return countryRepository.save(country); 28 | } 29 | public Country updateCountry(int id,Country country){ 30 | country.setCountryId(id); 31 | return countryRepository.save(country); 32 | } 33 | public void deleteCountry(int id){ 34 | countryRepository.deleteById(id); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/restfulspring/apiexample/service/JwtUtilService.java: -------------------------------------------------------------------------------- 1 | package com.restfulspring.apiexample.service; 2 | 3 | import io.jsonwebtoken.Claims; 4 | import io.jsonwebtoken.Jwts; 5 | import io.jsonwebtoken.SignatureAlgorithm; 6 | import org.springframework.security.core.userdetails.UserDetails; 7 | import org.springframework.stereotype.Service; 8 | 9 | import java.util.Date; 10 | import java.util.HashMap; 11 | import java.util.Map; 12 | import java.util.function.Function; 13 | 14 | @Service 15 | public class JwtUtilService { 16 | private final String secret = "fawad"; 17 | 18 | public String extractUsername(String token) { 19 | return extractClaim(token, Claims::getSubject); 20 | } 21 | 22 | public Date extractExpiration(String token) { 23 | return extractClaim(token, Claims::getExpiration); 24 | } 25 | 26 | public T extractClaim(String token, Function claimsResolver) { 27 | final Claims claims = extractAllClaims(token); 28 | return claimsResolver.apply(claims); 29 | } 30 | 31 | private Claims extractAllClaims(String token) { 32 | return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody(); 33 | } 34 | 35 | private Boolean isTokenExpired(String token) { 36 | return extractExpiration(token).before(new Date()); 37 | } 38 | 39 | public String generateToken(String username) { 40 | Map claims = new HashMap<>(); 41 | return createToken(claims, username); 42 | } 43 | 44 | private String createToken(Map claims, String subject) { 45 | 46 | return Jwts.builder().setClaims(claims).setSubject(subject).setIssuedAt(new Date(System.currentTimeMillis())) 47 | .setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 10)) 48 | .signWith(SignatureAlgorithm.HS256, secret).compact(); 49 | } 50 | 51 | public Boolean validateToken(String token, UserDetails userDetails) { 52 | final String username = extractUsername(token); 53 | return (username.equals(userDetails.getUsername()) && !isTokenExpired(token)); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/com/restfulspring/apiexample/service/PersonService.java: -------------------------------------------------------------------------------- 1 | package com.restfulspring.apiexample.service; 2 | 3 | import com.restfulspring.apiexample.entity.Person; 4 | import com.restfulspring.apiexample.exception.NotFoundException; 5 | import com.restfulspring.apiexample.repository.PersonRepository; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.stereotype.Service; 8 | 9 | import java.util.List; 10 | import java.util.Optional; 11 | 12 | @Service 13 | public class PersonService { 14 | @Autowired 15 | private PersonRepository personRepository; 16 | 17 | public List getPersons(){ 18 | return personRepository.findAll(); 19 | } 20 | 21 | public Person getPerson(int id){ 22 | Optional person = personRepository.findById(id); 23 | if(!person.isPresent()){ 24 | throw new NotFoundException("Person not found!"); 25 | } 26 | return person.get(); 27 | } 28 | 29 | public Person addPerson(Person person){ 30 | personRepository.save(person); 31 | return person; 32 | } 33 | 34 | public Person updatePerson(int id,Person person){ 35 | person.setId(id); 36 | personRepository.save(person); 37 | return person; 38 | } 39 | 40 | public void deletePerson(int id){ 41 | personRepository.deleteById(id); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/com/restfulspring/apiexample/service/UserService.java: -------------------------------------------------------------------------------- 1 | package com.restfulspring.apiexample.service; 2 | 3 | import com.restfulspring.apiexample.entity.ApplicationUser; 4 | import com.restfulspring.apiexample.repository.UserRepository; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.security.core.userdetails.User; 7 | import org.springframework.security.core.userdetails.UserDetails; 8 | import org.springframework.security.core.userdetails.UserDetailsService; 9 | import org.springframework.security.core.userdetails.UsernameNotFoundException; 10 | import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; 11 | import org.springframework.stereotype.Service; 12 | 13 | import java.util.ArrayList; 14 | 15 | @Service 16 | public class UserService implements UserDetailsService { 17 | @Autowired 18 | private UserRepository userRepository; 19 | @Autowired 20 | private BCryptPasswordEncoder passwordEncoder; 21 | 22 | @Override 23 | public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { 24 | ApplicationUser user = userRepository.findByUsername(username); 25 | return new User(user.getUsername(), user.getPassword(), new ArrayList<>()); 26 | } 27 | 28 | public Boolean registerUser(ApplicationUser user) { 29 | String passwordHash = passwordEncoder.encode(user.getPassword()); 30 | user.setPassword(passwordHash); 31 | userRepository.save(user); 32 | return true; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | #spring.datasource.url=jdbc:h2:~/test;DB_CLOSE_ON_EXIT=FALSE 2 | #spring.jpa.hibernate.ddl-auto=update 3 | 4 | spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver 5 | spring.datasource.url=jdbc:mysql://localhost:3306/test?useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC 6 | spring.datasource.username=root 7 | spring.datasource.password= 8 | spring.jpa.show-sql=true 9 | spring.jpa.hibernate.ddl-auto=create-drop 10 | spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5Dialect 11 | -------------------------------------------------------------------------------- /src/test/java/com/restfulspring/apiexample/ApiexampleApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.restfulspring.apiexample; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class ApiexampleApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | --------------------------------------------------------------------------------