Expense Tracker API
Expense Tracker API
What is REST API? CRUD oprations using Data JPA Con gure the Spring Security
Setup the Dev Environment Add Pagination and Sorting Create Mapping between entities
Different ways of creating project Handle the Exceptions Adding JWT to the application
Basics of Spring Boot Validate the REST APIs Advance features in Postman
Develop REST APIs for Expense Filter the records Deploy application to Heroku
Connecting to the MySQL DB Develop REST APIs for User What’s next ?
fi
Edition - 2 (Updating Soon)
• For HTTP POST and PUT methods supports HTTP Request Body
What is our Goal?
• Create these kind of URLs and make them available on the internet
• Anyone in the world can call to our REST end point from their application
• There are different data formats are available to exchange the information
• JSON is the most commonly and used data format to exchange information
"name": "Bushan"
"age": 29,
"address":
"location": "India"
{
}
}
Application Architecture
• It a avour of Eclipse IDE but it has a great support for spring boot applications
fl
m
Challenge #1
Files/Folder Description
JRE System Library Contains the jdk and system related jars
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
fi
Why we need starter dependencies?
* denotes a particular type of application
• Spring boot starters follows a naming conventio
spring-boot-starter-
Summary
• Spring boot starter dependencies are group of maven dependencie
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {}
• These classes contains beans by de ning the methods with @Bean annotation
@SpringBootApplication
public class SpringbootdemoApplication {
}
fi
fi
fi
fi
e
fi
fi
y
Understanding @ComponentScan
• @ComponentScan will scan for all the components in our applicatio
• Spring will scan for all the components under the base packag
package in.bushansirgur.springbootdemo
@SpringBootApplication
public class SpringbootdemoApplication {
}
;
• Spring will automatically con gure the app based on the classpath dependencie
fi
a
Summary
URL: localhost:8080/expenses
File: ExpenseController.java
}
2. De ne a method getAllExpenses()
URL: localhost:8080/expenses
File: ExpenseController.java
public class ExpenseController {
}
fi
3. Map the URL to a Java method
URL: localhost:8080/expenses
File: ExpenseController.java
public class ExpenseController {
@GetMapping("/expenses")
public String getAllExpenses() {
return "List of expenses";
}
@RequestMapping(method = RequestMethod.GET)
4. Make the class as Rest controller
URL: localhost:8080/expenses
@Controller @Component
File: ExpenseController.java
@RestController
public class ExpenseController {
@GetMapping("/expenses")
public String getAllExpenses() {
return "List of expenses";
}
@ResponseBody
Add the dependencies to the application
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
</dependencies>
Create a database and table for expenses
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "expense_name")
private String name;
@Column(name = "expense_amount")
private BigDecimal amount;
}
3. Create an Expense service
File: ExpenseService.java
public interface ExpenseService {
List<Expense> getAllExpenses();
}
4. Create an Expense service implementation
File: ExpenseServiceImpl.java
@Service
public class ExpenseServiceImpl implements ExpenseService {
@Autowired
private ExpenseRepository expenseRepo;
@Override
public List<Expense> getAllExpenses() {
return expenseRepo.findAll();
}
}
5. Update the Expense controller
File: ExpenseController.java
@RestController
public class ExpenseController {
@Autowired
private ExpenseService expenseService;
@GetMapping("/expenses")
public List<Expense> getAllExpenses() {
return expenseService.getAllExpenses();
}
}
Creating database tables using JPA
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
fi
Add base URL to the REST end points
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
server.servlet.context-path=/api/v1
fi
Understand passing a parameter in the URL
URL: localhost:8080/expenses/45
@GetMapping("/expenses/{id}")
public Expense getExpenseById(@PathVariable("id") Long id) {
...
}
URL: localhost:8080/users/12/expenses/45
@GetMapping("/users/{userId}/expenses/{id}")
public List<Expense> getExpensesByUserId(@PathVariable("userId") Long user,
@PathVariable Long id) {
//TODO:
}
Passing a parameter using query strings
URL: localhost:8080/expenses?id=45
@GetMapping("/expenses")
public Expense getExpenseById(@RequestParam(“id") Long id) {
...
}
URL: localhost:8080/users/expenses?userId=23&id=34
@GetMapping("/users/expenses")
public List<Expense> getExpensesByUserId(@RequestParam("userId") Long user,
@RequestParam Long id) {
//TODO:
}
Create a REST end point to get the expense by id
...
}
fi
2. Call a repository method to retrieve the expense
File: ExpenseServiceImpl.java
@Service
public class ExpenseServiceImpl implements ExpenseService {
@Autowired
private ExpenseRepository expenseRepo;
...
@Override
public Expense getExpenseById(Long id) {
Optional<Expense> expense = expenseRepo.findById(id);
if (expense.isPresent()) {
return expense.get();
}
throw new RuntimeException("Expense is not found for the id "+id);
}
}
3. Create a REST end point to retrieve the expense
URL: localhost:8080/expenses/45
File: ExpenseController.java
@RestController
public class ExpenseController {
@Autowired
private ExpenseService expenseService;
...
@GetMapping("/expenses/{id}")
public Expense getExpenseById(@PathVariable("id") Long id) {
return expenseService.getExpenseById(id);
}
}
Create a REST end point to delete expense by id
...
@Autowired
private ExpenseRepository expenseRepo;
...
@Override
public void deleteExpenseById(Long id) {
expenseRepo.deleteById(id);
}
}
3. Create a REST end point to delete the expense by id
URL: localhost:8080/expenses?id=45
File: ExpenseController.java
@RestController
public class ExpenseController {
@Autowired
private ExpenseService expenseService;
...
@DeleteMapping("/expenses")
public void deleteExpenseById(@RequestParam("id") Long id) {
expenseService.deleteExpenseById(id);
}
}
Map the HTTP Request to a Java Object
URL: localhost:8080/expenses
public class Expense {
}
Map the HTTP Request to a Java Object
@RequestBody annotation Map the
@PostMapping("/expenses")
public void saveExpenseDetails(@RequestBody Expense expense) {
}
Create a REST end point to save the expense data
...
}
fi
2. Call a repository method to save the expense data
File: ExpenseServiceImpl.java
@Service
public class ExpenseServiceImpl implements ExpenseService {
@Autowired
private ExpenseRepository expenseRepo;
...
@Override
public Expense saveExpenseDetails(Expense expense) {
return expenseRepo.save(expense);
}
}
3. Create a REST end point to save the expense data
URL: localhost:8080/expenses
File: ExpenseController.java
@RestController
public class ExpenseController {
@Autowired
private ExpenseService expenseService;
...
@PostMapping("/expenses")
public Expense saveExpenseDetails(@RequestBody Expense expense) {
return expenseService.saveExpenseDetails(expense);
}
}
Create a REST end point to update the expense data
...
}
fi
2. Call a repository method to update the expense data
File: ExpenseServiceImpl.java
@Service
public class ExpenseServiceImpl implements ExpenseService {
@Autowired
private ExpenseRepository expenseRepo;
...
@Override
public Expense updateExpenseDetails(Long id, Expense expense) {
Expense existingExpense = getExpenseById(id);
existingExpense.setName(expense.getName() != null ? expense.getName() : existingExpense.getName());
existingExpense.setDescription(expense.getDescription() != null ? expense.getDescription() : existingExpense.getDescription());
existingExpense.setCategory(expense.getCategory() != null ? expense.getCategory() : existingExpense.getCategory());
existingExpense.setAmount(expense.getAmount() != null ? expense.getAmount() : existingExpense.getAmount());
existingExpense.setDate(expense.getDate() != null ? expense.getDate() : existingExpense.getDate());
return expenseRepo.save(existingExpense);
}
}
3. Create a REST end point to update the expense data
URL: localhost:8080/expenses/34
File: ExpenseController.java
@RestController
public class ExpenseController {
@Autowired
private ExpenseService expenseService;
...
@PutMapping("/expenses/{id}")
public Expense updateExpenseDetails(@RequestBody Expense expense, @PathVariable Long id) {
return expenseService.updateExpenseDetails(id, expense);
}
}
Save the Timestamps to the Expense table
...
@Column(name = "updated_at")
@UpdateTimestamp
private Timestamp updatedAt;
}
Understand the HTTP Response Status codes
• It indicates that whether a speci c HTTP request has been successfully complete
• By default every HTTP request contains HTTP status code 200 (OK
403 (FORBIDDEN) Client does not have right access for the content.
500 (INTERNAL SERVER ERROR) Server does not know how to handle the request.
Understanding Pagination and Sorting
• Data JPA provides support for Pagination and Sorting out of the bo
• Data JPA provides a repository speci cally for Pagination and Sorting
fi
x
File: JPARepository.java
public interface JpaRepository<T, ID> extends PagingAndSortingRepository<T, ID>, QueryByExampleExecutor<T> {
}
Add the pagination support
File: PagingAndSortingRepository.java
Page<T> findAll(Pageable pageable);
localhost:8080/expenses?page=0&size=5
URL: localhost:8080/expenses
File: ExpenseController.java
@RestController
public class ExpenseController {
@Autowired
private ExpenseService expenseService;
...
@GetMapping("/expenses")
public Page<Expense> getAllExpenses(Pageable page) {
return expenseService.getAllExpenses(page);
}
}
2. Modify the method in Expense Service interface
File: ExpenseService.java
public interface ExpenseService {
...
@Autowired
private ExpenseRepository expenseRepo;
...
@Override
public Page<Expense> getAllExpenses(Pageable page) {
return expenseRepo.findAll(page);
}
}
Create a Custom Exception for Expense
File: ErrorObject.java
@Data
public class ErrorObject {
File: ExpenseNotFoundException.java
public class ExpenseNotFoundException extends RuntimeException {
/**
*
*/
private static final long serialVersionUID = 1L;
File: GlobalExceptionHandler.java
@ControllerAdvice
public class GlobalExceptionHandler{
@ExceptionHandler(ExpenseNotFoundException.class)
public ResponseEntity<ErrorObject> handleExpenseNotFoundException(ExpenseNotFoundException ex, WebRequest request) {
eObject.setStatusCode(HttpStatus.NOT_FOUND.value());
eObject.setMessage(ex.getMessage());
eObject.setTimestamp(System.currentTimeMillis());
@Autowired
private ExpenseRepository expenseRepo;
...
@Override
public Expense getExpenseById(Long id) throws ExpenseNotFoundException {
Optional<Expense> expense = expenseRepo.findById(id);
if (expense.isPresent()) {
return expense.get();
}
throw new ExpenseNotFoundException("Expense is not found for the id "+id);
}
}
Handling Invalid method argument Exception
File: GlobalExceptionHandler.java
public class GlobalExceptionHandler{
...
@ExceptionHandler(MethodArgumentTypeMismatchException.class)
public ResponseEntity<Object> handleException(MethodArgumentTypeMismatchException ex, WebRequest request) {
ErrorObject eObject = new ErrorObject();
eObject.setStatusCode(HttpStatus.BAD_REQUEST.value());
eObject.setMessage(ex.getMessage());
eObject.setTimestamp(System.currentTimeMillis());
return new ResponseEntity<Object>(eObject, HttpStatus.BAD_REQUEST);
}
}
Handling the General Exception
File: GlobalExceptionHandler.java
public class GlobalExceptionHandler{
...
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorObject> handleGeneralException(Exception ex, WebRequest request) {
ErrorObject eObject = new ErrorObject();
eObject.setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR.value());
eObject.setMessage(ex.getMessage());
eObject.setTimestamp(System.currentTimeMillis());
return new ResponseEntity<ErrorObject>(eObject, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
Understanding about Hibernate Validator
• Hibernate validator allows us to validate the Java beans with its annotations
Annotation Purpose
@NotNull Checks that the element is not null
@NotEmpty Checks that the element is not null nor empty
@NotBlank Checks that the element is not null and trimmed length is > 0
@Email Checks that the element is valid email
@Size Checks that the element size is between min and max
@Future Checks that the annotated date is in the future
@Pattern Checks that the string matches the regular expression
Add validations to the Expenses
File: Expense.java
public class Expense {
...
@Column(name = "expense_name")
@NotNull
private String name;
...
}
Update ExpenseController to validate the Expense bean
File: ExpenseController.java
@RestController
public class ExpenseController {
@Autowired
private ExpenseService expenseService;
...
@PostMapping("/expenses")
public Expense saveExpenseDetails(@Valid @RequestBody Expense expense) {
return expenseService.saveExpenseDetails(expense);
}
}
Customise the error response
File: GlobalExceptionHandler.java
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler{
...
@Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex,
HttpHeaders headers, HttpStatus status, WebRequest request) {
body.put("errors", errors);
File: ExpenseRepository.java
@Repository
public interface ExpenseRepository extends JpaRepository<Expense, Long> {
File: ExpenseService.java
public interface ExpenseService {
...
File: ExpenseServiceImpl.java
@Service
public class ExpenseServiceImpl implements ExpenseService {
@Autowired
private ExpenseRepository expenseRepo;
...
@Override
public List<Expense> readByName(String name, Pageable page) {
return expenseRepo.findByNameContaining(name, page).toList();
}
}
4. Create a REST end point to lter expenses by Category
URL: localhost:8080/api/v1/expenses/name?name=bills
File: ExpenseFilterController.java
@RestController
public class ExpenseController {
@Autowired
private ExpenseService expenseService;
@GetMapping("/expenses/name")
public List<Expense> getAllExpensesByName(@RequestParam String name, Pageable page) {
return expenseService.readByName(name, page);
}
}
fi
Filter the expenses by Category
File: ExpenseRepository.java
@Repository
public interface ExpenseRepository extends JpaRepository<Expense, Long> {
File: ExpenseService.java
public interface ExpenseService {
...
File: ExpenseServiceImpl.java
@Service
public class ExpenseServiceImpl implements ExpenseService {
@Autowired
private ExpenseRepository expenseRepo;
...
@Override
public List<Expense> readByCategory(String category, Pageable page) {
return expenseRepo.findByCategory(category, page).toList();
}
}
4. Create a REST end point to lter expenses by Category
URL: localhost:8080/api/v1/expenses/category?category=vegetables
File: ExpenseFilterController.java
@RestController
public class ExpenseController {
@Autowired
private ExpenseService expenseService;
@GetMapping("/expenses/category")
public List<Expense> getAllExpensesByCategory(@RequestParam String category, Pageable page) {
return expenseService.readByCategory(category, page);
}
}
fi
Filter the expenses by Dates
File: ExpenseRepository.java
@Repository
public interface ExpenseRepository extends JpaRepository<Expense, Long> {
File: ExpenseService.java
public interface ExpenseService {
...
File: ExpenseServiceImpl.java
@Service
public class ExpenseServiceImpl implements ExpenseService {
@Autowired
private ExpenseRepository expenseRepo;
...
@Override
public List<Expense> readByDate(Date startDate, Date endDate, Pageable page) {
if (startDate == null) {
startDate = new Date(0);
}
if (endDate == null) {
endDate = new Date(System.currentTimeMillis());
}
Page<Expense> pages = eRepo.findByDateBetween(startDate, endDate, page);
return pages.toList();
}
4. Create a REST end point to lter expenses by Dates
URL: localhost:8080/api/v1/expenses/date?startDate=2021-01-01&endDate=2021-03-31
File: ExpenseFilterController.java
@RestController
public class ExpenseController {
@Autowired
private ExpenseService expenseService;
@GetMapping("/expenses/date")
public List<Expense> getAllExpensesByDate(
@RequestParam(required = false) Date startDate,
@RequestParam(required = false) Date endDate,
Pageable page) {
return expenseService.readByDate(startDate, endDate, page);
}
} fi
Create REST end point for create user
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(unique = true)
private String email;
@Column(name="updated_at")
@UpdateTimestamp
private Timestamp updated_at;
}
2. Create a User model class
File: UserModel.java
@Data
public class UserModel {
}
3. Create a User repository
File: UserRepository.java
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
}
4. Create a User service and declare a method
File: UserService.java
public interface UserService {
...
File: UserServiceImpl.java
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserRepository userRepo;
...
@Override
public User create(UserModel uModel) {
User user = new User();
BeanUtils.copyProperties(uModel, user);
return userRepo.save(user);
}
}
6. Create a REST end point to create new user
URL: localhost:8080/api/v1/users
File: UserController.java
@RestController
public class UserController {
@Autowired
private UserService userService;
@PostMapping("/register")
public ResponseEntity<User> save(@RequestBody UserModel user) {
return new ResponseEntity<User>(userService.create(user), HttpStatus.CREATED);
}
}
Add validations to the create user REST API
}
2. Create a REST end point to create new user
URL: localhost:8080/api/v1/users
File: UserController.java
@RestController
public class UserController {
@Autowired
private UserService userService;
@PostMapping("/register")
public ResponseEntity<User> save(@Valid @RequestBody UserModel user) {
return new ResponseEntity<User>(userService.create(user), HttpStatus.CREATED);
}
}
Handle the Exception Existing email
File: UserRepository.java
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
}
fi
2. Create a Custom Exception for Existing items
File: ItemAlreadyExistsException.java
public class ItemAlreadyExistsException extends RuntimeException {
/**
*
*/
private static final long serialVersionUID = 1L;
File: GlobalExceptionHandler.java
public class GlobalExceptionHandler{
...
@ExceptionHandler(ItemAlreadyExistsException.class)
public ResponseEntity<Object> handleItemExistsException(ItemAlreadyExistsException ex, WebRequest request) {
ErrorObject eObject = new ErrorObject();
eObject.setStatusCode(HttpStatus.CONFLICT.value());
eObject.setMessage(ex.getMessage());
eObject.setTimestamp(System.currentTimeMillis());
return new ResponseEntity<Object>(eObject, HttpStatus.CONFLICT);
}
}
4. Check for email exists before save the details to database
File: UserServiceImpl.java
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserRepository userRepo;
...
@Override
public User create(UserModel uModel) {
if (uRepo.existsByEmail(uModel.getEmail())) {
throw new ItemAlreadyExistsException("User is already registered with email:"+uModel.getEmail());
}
User user = new User();
BeanUtils.copyProperties(uModel, user);
return userRepo.save(user);
}
}
Create REST end point to read user info
File: UserService.java
public interface UserService {
...
File: UserServiceImpl.java
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserRepository userRepo;
...
@Override
public User read(Long id) throws ResourceNotFoundException {
return userRepo.findById(id).orElseThrow(() -> new ResourceNotFoundException("User not found for the id:"+id));
}
}
3. Create a REST end point to read user info
URL: localhost:8080/api/v1/users/3
File: UserController.java
@RestController
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/users/{id}")
public ResponseEntity<User> get(@PathVariable Long id){
return new ResponseEntity<User>(userService.read(id), HttpStatus.OK);
}
}
Create REST end point to update user info
File: UserService.java
public interface UserService {
...
File: UserServiceImpl.java
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserRepository userRepo;
...
@Override
public User update(User user, Long id) throws ResourceNotFoundException {
User oUser = read(id);
oUser.setName(user.getName() != null ? user.getName() : oUser.getName());
oUser.setEmail(user.getEmail() != null ? user.getEmail() : oUser.getEmail());
oUser.setPassword(user.getPassword() != null ? user.getPassword() : oUser.getPassword());
oUser.setAge(user.getAge() != null ? user.getAge() : oUser.getAge());
return userRepo.save(oUser);
}
}
3. Create a REST end point to update user info
URL: localhost:8080/api/v1/users/3
File: UserController.java
@RestController
public class UserController {
@Autowired
private UserService userService;
@PutMapping("/users/{id}")
public ResponseEntity<User> update(@RequestBody User user, @PathVariable Long id){
User mUser = userService.update(user, id);
return new ResponseEntity<User>(mUser, HttpStatus.OK);
}
}
Create REST end point to delete user info
File: UserService.java
public interface UserService {
...
File: UserServiceImpl.java
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserRepository userRepo;
...
@Override
public void delete(Long id){
User user = read(id);
userRepo.delete(user);
}
}
3. Create a REST end point to delete user info
URL: localhost:8080/api/v1/users/3
File: UserController.java
@RestController
public class UserController {
@Autowired
private UserService userService;
@DeleteMapping("/users/{id}")
public ResponseEntity<HttpStatus> delete(@PathVariable Long id) throws ResourceNotFoundException {
userService.delete(id);
return new ResponseEntity<HttpStatus>(HttpStatus.NO_CONTENT);
}
}
Understand the Spring Security Default
Con guration
File: pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
Spring Security Control Flow
WebSecurity
MySecurityConfig ConfigurerAdapter
WebSecurityCon gurerAdpater
Available Methods
…
fi
Customise the Spring Security
Default implementation: configure(HttpSecurity)
...
http.formLogin();
http.httpBasic();
}
...
}
fi
Override the con gure() to customise the HTTP requests
@Configuration
public class AppSecurityConfig extends WebSecurityConfigurerAdapter{
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/login", "/register").permitAll()
.anyRequest().authenticated()
.and()
.httpBasic();
}
}
fi
fi
Customize the Spring security to con gure
multiple users
fi
Customise the Spring Security
WebSecurityCon gurerAdpater
Available Methods
…
fi
Override the con gure() to customise the HTTP requests
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{
...
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("bushan").password("12345").authorities("admin")
.and()
.withUser("bharath").password("12345").authorities("user")
.and()
.passwordEncoder(NoOpPasswordEncoder.getInstance());
}
}
fi
fi
Customize the Spring security to con gure
multiple users
fi
Con gure multiple users
File: WebSecurityCon g.java
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
InMemoryUserDetailsManager userDetailsService = new InMemoryUserDetailsManager();
UserDetails user1 = User.withUsername("bushan").password("12345").authorities("admin").build();
UserDetails user2 = User.withUsername(“bharath").password("12345").authorities("user").build();
userDetailsService.createUser(user1);
userDetailsService.createUser(user2);
auth.userDetailsService(userDetailsService);
}
@Bean
public PasswordEncoder passwordEncoder() {
return NoOpPasswordEncoder.getInstance();
}
}
fi
fi
Creating a custom user details service
File: UserRepository.java
@Repository
public interface UserRepository extends JpaRepository<User, Long>{
...
fi
2. Create a custom user details service class
File: CustomUserDetailsService.java
@Service
public class CustomUserDetailsService implements UserDetailsService{
@Autowired
private UserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
User existingUser = userRepository.findByEmail(email).orElseThrow(() ->
new UsernameNotFoundException("User not found the for the email:"+email));
return new org.springframework.security.core.userdetails.User(
existingUser.getEmail(), existingUser.getPassword(), new ArrayList<>());
}
}
3. Con gure authentication manager to use custom user details service
@Autowired
private CustomUserDetailsService userDetailsService;
...
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
@Bean
public PasswordEncoder passwordEncoder() {
return NoOpPasswordEncoder.getInstance();
}
}
fi
fi
Encode the user password and save to the database
...
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
fi
2. Encode the user password before saving to the database
File: UserServiceImpl.java
@Service
public class UserServiceImpl implements UserService {
@Autowired
private PasswordEncoder bcryptEncoder;
...
@Override
public User create(UserModel uModel) {
if (uRepo.existsByEmail(uModel.getEmail())) {
throw new ItemAlreadyExistsException("User is already registered with email:"+uModel.getEmail());
}
User user = new User();
BeanUtils.copyProperties(uModel, user);
user.setPassword(bcryptEncoder.encode(user.getPassword()));
return userRepo.save(user);
}
}
3. Encode the password before updating it to the database
File: UserServiceImpl.java
@Service
public class UserServiceImpl implements UserService {
@Autowired
private PasswordEncoder bcryptEncoder;
...
@Override
public User update(User user, Long id) throws ResourceNotFoundException {
User oUser = read(id);
oUser.setName(user.getName() != null ? user.getName() : oUser.getName());
oUser.setEmail(user.getEmail() != null ? user.getEmail() : oUser.getEmail());
oUser.setPassword(user.getPassword() != null ? bcryptEncoder.encode(user.getPassword()) : oUser.getPassword());
oUser.setAge(user.getAge() != null ? user.getAge() : oUser.getAge());
return userRepo.save(oUser);
}
}
Create REST API for login
File: LoginModel.java
@Data
public class LoginModel {
File: AuthController.java
@RestController
public class AuthController {
@Autowired
private AuthenticationManager authenticationManager;
@PostMapping("/login")
public ResponseEntity<HttpStatus> login(@RequestBody LoginModel login) {
Authentication authenticate = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken
(login.getEmail(), login.getPassword()));
SecurityContextHolder.getContext().setAuthentication(authenticate);
return new ResponseEntity<HttpStatus>(HttpStatus.OK);
}
}
Create Mapping between Expense and User
One Many
User
fi
s
File: AuthController.java
@RestController
public class AuthController {
@Autowired
private AuthenticationManager authenticationManager;
@PostMapping("/login")
public ResponseEntity<HttpStatus> login(@RequestBody LoginModel login) {
Authentication authenticate = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken
(login.getEmail(), login.getPassword()));
SecurityContextHolder.getContext().setAuthentication(authenticate);
return new ResponseEntity<HttpStatus>(HttpStatus.OK);
}
}
Declare a method inside User service
File: UserService.java
public interface UserService {
...
User getLoggedInUser();
}
Get the logged in user from Spring security context
File: UserServiceImpl.java
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserRepository userRepo;
...
@Override
public User getLoggedInUser() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
}
Add user to the expense entity before saving to db
@Autowired
private UserService userService;
...
@Override
public Expense saveExpenseDetails(Expense expense) {
expense.setUser(userService.getLoggedInUser());
return expenseRepo.save(expense);
}
}
Read all expense by user id
@Service
public class ExpenseServiceImpl implements ExpenseService {
@Autowired
private UserService userService;
...
@Override
public Page<Expense> getAllExpenses(Pageable page) {
return expenseRepo.findByUserId(userService.getLoggedInUser().getId(), page);
}
}
Read single expense by user id
@Autowired
private UserService userService;
...
@Override
public Expense getExpenseById(Long id){
Optional<Expense> expense = expenseRepo.findByUserIdAndId(userService.getLoggedInUser().getId(), id);
if (expense.isPresent()) {
return expense.get();
}
throw new ResourceNotFoundException("Expense is not found for the id "+id);
}
}
Updating the JPA nder methods
fi
Update the JPA nder methods
File: ExpenseRepository.java
@Repository
public interface ExpenseRepository extends JpaRepository<Expense, Long> {
>SELECT * FROM tbl_expenses WHERE user_id = ? AND date BETWEEN ‘startDate’ AND ‘endDate’
fi
Call the updated nder methods
File: ExpenseServiceImpl.java
@Service
public class ExpenseServiceImpl implements ExpenseService {
@Override
public List<Expense> readByCategory(String category, Pageable page) {
return expenseRepo.findByUserIdAndCategory(userService.getLoggedInUser().getId(), category, page).toList();
}
@Override
public List<Expense> readByName(String keyword, Pageable page) {
return expenseRepo.findByUserIdAndNameContaining(userService.getLoggedInUser().getId(), keyword, page).toList();
}
@Override
public List<Expense> readByDate(Date startDate, Date endDate, Pageable page) {
if (startDate == null) {
startDate = new Date(0);
}
if (endDate == null) {
endDate = new Date(System.currentTimeMillis());
}
return expenseRepo.findByUserIdAndDateBetween(userService.getLoggedInUser().getId(),
startDate, endDate, page).toList();
}
fi
Update the User REST end points
File: UserService.java
public interface UserService {
...
User readUser();
void deleteUser();
}
2. Modify the user service implementation class
File: UserServiceImpl.java
@Service
public class UserServiceImpl implements UserService {
@Override
public User readUser() {
Long userId = getLoggedInUser().getId();
return userRepository.findById(userId).orElseThrow(() ->
new ResourceNotFoundException("User not found for the id:”+userId));
}
@Override
public User updateUser(UserModel user) {
User existingUser = readUser();
existingUser.setName(user.getName() != null ? user.getName() : existingUser.getName());
existingUser.setEmail(user.getEmail() != null ? user.getEmail() : existingUser.getEmail());
existingUser.setPassword(user.getPassword() != null ?
bcryptEncoder.encode(user.getPassword()) : existingUser.getPassword());
existingUser.setAge(user.getAge() != null ? user.getAge() : existingUser.getAge());
return userRepository.save(existingUser);
}
@Override
public void deleteUser() {
User existingUser = readUser();
userRepository.delete(existingUser);
}
}
3. Update the REST end points
File: UserController.java
@RestController
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/profile")
public ResponseEntity<User> readUser() {
return new ResponseEntity<User>(userService.readUser(), HttpStatus.OK);
}
@PutMapping("/profile")
public ResponseEntity<User> updateUser(@RequestBody UserModel user) {
return new ResponseEntity<User>(userService.updateUser(user), HttpStatus.OK);
}
@DeleteMapping("/deactivate")
public ResponseEntity<HttpStatus> deleteUser() {
userService.deleteUser();
return new ResponseEntity<HttpStatus>(HttpStatus.NO_CONTENT);
}
}
Understand - What is JWT?
JWT
• JWT stands for Json Web Toke
Structure of JWT
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzd
WIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4g
RG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.S KxwRJ
SMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
Header:
Alogirithm and Token Payload: Data Signature
Type
fl
JWT Control ow Server
1 Email/Password
2 Authenticate
@Component
public class JwtTokenUtil {
@Value("${jwt.secret}")
private String secret;
}
}
Create a custom JWT lter
File: JwtRequestFilter.java
public class JwtRequestFilter extends OncePerRequestFilter {
...
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
jwtToken = requestTokenHeader.substring(7);
try {
username = jwtTokenUtil.getUsernameFromToken(jwtToken);
} catch (IllegalArgumentException e) {
throw new RuntimeException("Unable to get JWT Token");
//System.out.println("Unable to get JWT Token");
} catch (ExpiredJwtException e) {
throw new RuntimeException("Jwt Token has expired");
//System.out.println("Jwt Token has expired");
}
...
}
fi
Add the lter to the con g le
File: WebSecurityCon g.java
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests().antMatchers("/login", "/register").permitAll()
.anyRequest().authenticated()
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class);
http.httpBasic();
}
fi
fi
fi
fi