diff --git a/pom.xml b/pom.xml
index 5bc6c53..6a8c65e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1,30 +1,34 @@
-
4.0.0
org.springframework.boot
spring-boot-starter-parent
3.3.3
-
+
com
libraryman-api
0.0.1-SNAPSHOT
libraryman-api
- Revolutionize book management with LibraryMan! Easily track stock, borrowers, and due dates, streamlining operations for schools, companies, and libraries worldwide, ensuring efficient and organized book lending.
-
+ Revolutionize book management with LibraryMan! Easily track
+ stock, borrowers, and due dates, streamlining operations for schools,
+ companies, and libraries worldwide, ensuring efficient and organized
+ book lending.
+
-
+
-
+
-
-
-
-
+
+
+
+
17
@@ -47,6 +51,26 @@
runtime
true
+
+
+ io.jsonwebtoken
+ jjwt-api
+ 0.11.5
+
+
+
+ io.jsonwebtoken
+ jjwt-impl
+ 0.11.5
+ runtime
+
+
+
+ io.jsonwebtoken
+ jjwt-jackson
+ 0.11.5
+ runtime
+
com.mysql
@@ -67,9 +91,24 @@
org.springframework.boot
- spring-boot-starter-cache
+ spring-boot-starter-security
+
+
+
+ org.springframework.security
+ spring-security-test
+ test
+
+
+
+ org.springframework.security
+ spring-security-oauth2-client
+
+ org.springframework.boot
+ spring-boot-starter-cache
+
diff --git a/src/main/java/com/libraryman_api/LibrarymanApiApplication.java b/src/main/java/com/libraryman_api/LibrarymanApiApplication.java
index 042e728..fcd4665 100644
--- a/src/main/java/com/libraryman_api/LibrarymanApiApplication.java
+++ b/src/main/java/com/libraryman_api/LibrarymanApiApplication.java
@@ -16,4 +16,4 @@ public static void main(String[] args) {
SpringApplication.run(LibrarymanApiApplication.class, args);
}
-}
+}
\ No newline at end of file
diff --git a/src/main/java/com/libraryman_api/book/BookController.java b/src/main/java/com/libraryman_api/book/BookController.java
index 4401e67..8a59125 100644
--- a/src/main/java/com/libraryman_api/book/BookController.java
+++ b/src/main/java/com/libraryman_api/book/BookController.java
@@ -8,6 +8,7 @@
import org.springframework.data.domain.Sort;
import org.springframework.data.web.PageableDefault;
import org.springframework.http.ResponseEntity;
+import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
/**
@@ -71,6 +72,7 @@ public ResponseEntity getBookById(@PathVariable int id) {
* @return the added {@link Book} object.
*/
@PostMapping
+ @PreAuthorize("hasRole('LIBRARIAN') or hasRole('ADMIN')")
public BookDto addBook(@RequestBody BookDto bookDto) {
return bookService.addBook(bookDto);
}
@@ -83,6 +85,7 @@ public BookDto addBook(@RequestBody BookDto bookDto) {
* @return the updated {@link Book} object.
*/
@PutMapping("/{id}")
+ @PreAuthorize("hasRole('LIBRARIAN') or hasRole('ADMIN')")
public BookDto updateBook(@PathVariable int id, @RequestBody BookDto bookDtoDetails) {
return bookService.updateBook(id, bookDtoDetails);
}
@@ -93,6 +96,7 @@ public BookDto updateBook(@PathVariable int id, @RequestBody BookDto bookDtoDeta
* @param id the ID of the book to delete.
*/
@DeleteMapping("/{id}")
+ @PreAuthorize("hasRole('LIBRARIAN') or hasRole('ADMIN')")
public void deleteBook(@PathVariable int id) {
bookService.deleteBook(id);
}
diff --git a/src/main/java/com/libraryman_api/borrowing/BorrowingController.java b/src/main/java/com/libraryman_api/borrowing/BorrowingController.java
index 0a6dc65..2c36af8 100644
--- a/src/main/java/com/libraryman_api/borrowing/BorrowingController.java
+++ b/src/main/java/com/libraryman_api/borrowing/BorrowingController.java
@@ -2,6 +2,8 @@
import com.libraryman_api.exception.ResourceNotFoundException;
+import org.springframework.security.access.prepost.PreAuthorize;
+
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
@@ -39,6 +41,7 @@ public BorrowingController(BorrowingService borrowingService) {
* The results are sorted by borrow date by default and limited to 5 members per page.
*/
@GetMapping
+ @PreAuthorize("hasRole('LIBRARIAN') or hasRole('ADMIN')")
public Page getAllBorrowings(@PageableDefault(page=0, size=5, sort="borrowDate") Pageable pageable,
@RequestParam(required = false) String sortBy,
@RequestParam(required = false) String sortDir) {
@@ -64,6 +67,7 @@ public Page getAllBorrowings(@PageableDefault(page=0, size=5, sor
* @return the saved {@link Borrowings} object representing the borrowing record.
*/
@PostMapping
+ @PreAuthorize("hasRole('LIBRARIAN') or hasRole('ADMIN') or (hasRole('USER') and #borrowingsDto.member.memberId == authentication.principal.memberId)")
public BorrowingsDto borrowBook(@RequestBody BorrowingsDto borrowingsDto) {
return borrowingService.borrowBook(borrowingsDto);
}
@@ -101,6 +105,7 @@ public String payFine(@PathVariable int id) {
* The results are sorted by borrow date by default and limited to 5 members per page.
*/
@GetMapping("member/{memberId}")
+ @PreAuthorize("hasRole('LIBRARIAN') or hasRole('ADMIN') or (hasRole('USER') and #memberId == authentication.principal.memberId)")
public Page getAllBorrowingsOfAMember(@PathVariable int memberId,
@PageableDefault(page=0, size=5, sort="borrowDate") Pageable pageable,
@RequestParam(required = false) String sortBy,
@@ -128,6 +133,7 @@ public Page getAllBorrowingsOfAMember(@PathVariable int memberId,
* @throws ResourceNotFoundException if the borrowing record with the specified ID is not found.
*/
@GetMapping("{borrowingId}")
+ @PreAuthorize("hasRole('LIBRARIAN') or hasRole('ADMIN')")
public BorrowingsDto getBorrowingById(@PathVariable int borrowingId) {
return borrowingService.getBorrowingById(borrowingId)
.orElseThrow(() -> new ResourceNotFoundException("Borrowing not found"));
diff --git a/src/main/java/com/libraryman_api/member/MemberController.java b/src/main/java/com/libraryman_api/member/MemberController.java
index da67d9e..2f921a8 100644
--- a/src/main/java/com/libraryman_api/member/MemberController.java
+++ b/src/main/java/com/libraryman_api/member/MemberController.java
@@ -8,6 +8,7 @@
import org.springframework.data.domain.Sort;
import org.springframework.data.web.PageableDefault;
import org.springframework.http.ResponseEntity;
+import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
/**
@@ -39,6 +40,7 @@ public MemberController(MemberService memberService) {
* The results are sorted by name by default and limited to 5 members per page.
*/
@GetMapping
+ @PreAuthorize("hasRole('LIBRARIAN') or hasRole('ADMIN')")
public Page getAllMembers(@PageableDefault(page=0, size=5, sort="name") Pageable pageable,
@RequestParam(required = false) String sortBy,
@RequestParam(required = false) String sortDir) {
@@ -65,23 +67,13 @@ public Page getAllMembers(@PageableDefault(page=0, size=5, sort="nam
* @return a {@link ResponseEntity} containing the found {@link Members} object
*/
@GetMapping("/{id}")
+ @PreAuthorize("hasRole('LIBRARIAN') or hasRole('ADMIN')")
public ResponseEntity getMemberById(@PathVariable int id) {
return memberService.getMemberById(id)
.map(ResponseEntity::ok)
.orElseThrow(() -> new ResourceNotFoundException("Member not found"));
}
- /**
- * Adds a new library member.
- *
- * @param membersDto the {@link Members} object representing the new member
- * @return the added {@link Members} object
- */
- @PostMapping
- public MembersDto addMember(@RequestBody MembersDto membersDto) {
- return memberService.addMember(membersDto);
- }
-
/**
* Updates an existing library member.
* If the member is not found, a {@link ResourceNotFoundException} is thrown.
@@ -102,6 +94,7 @@ public MembersDto updateMember(@PathVariable int id, @RequestBody MembersDto mem
* @param id the ID of the member to delete
*/
@DeleteMapping("/{id}")
+ @PreAuthorize("hasRole('LIBRARIAN') or hasRole('ADMIN')")
public void deleteMember(@PathVariable int id) {
memberService.deleteMember(id);
}
diff --git a/src/main/java/com/libraryman_api/member/MemberRepository.java b/src/main/java/com/libraryman_api/member/MemberRepository.java
index fffa8d8..2c02216 100644
--- a/src/main/java/com/libraryman_api/member/MemberRepository.java
+++ b/src/main/java/com/libraryman_api/member/MemberRepository.java
@@ -10,6 +10,7 @@ public interface MemberRepository extends JpaRepository {
Optional findByMemberId(int memberId);
+ Optional findByUsername(String username);
/**
* SELECT SUM(amount) AS totalFines
diff --git a/src/main/java/com/libraryman_api/member/MemberService.java b/src/main/java/com/libraryman_api/member/MemberService.java
index 50262b8..2554db5 100644
--- a/src/main/java/com/libraryman_api/member/MemberService.java
+++ b/src/main/java/com/libraryman_api/member/MemberService.java
@@ -113,6 +113,7 @@ public MembersDto updateMember(int memberId, MembersDto membersDtoDetails) {
Members member = memberRepository.findById(memberId)
.orElseThrow(() -> new ResourceNotFoundException("Member not found"));
member.setName(membersDtoDetails.getName());
+ member.setUsername(membersDtoDetails.getUsername());
member.setEmail(membersDtoDetails.getEmail());
member.setPassword(membersDtoDetails.getPassword());
member.setRole(membersDtoDetails.getRole());
@@ -133,7 +134,6 @@ public MembersDto updateMember(int memberId, MembersDto membersDtoDetails) {
* @param memberId the ID of the member to delete
* @throws ResourceNotFoundException if the member is not found
*/
-
@CacheEvict(value = "members", key = "#memberId")
public void deleteMember(int memberId) {
Members member = memberRepository.findById(memberId)
@@ -145,45 +145,45 @@ public void deleteMember(int memberId) {
notificationService.accountDeletionNotification(member);
memberRepository.delete(member);
}
+
/**
* Converts a MembersDto object to a Members entity.
*
* This method takes a MembersDto object and transforms it into a Members entity
* to be used in database operations. It maps all relevant member details from
- * the DTO, including member ID, role, name, email, password, and membership date.
+ * the DTO, including member ID, role, name, username, email, password, and membership date.
*
* @param membersDto the DTO object containing member information
* @return a Members entity with data populated from the DTO
*/
-
-
public Members DtoEntity(MembersDto membersDto){
Members members= new Members();
members.setMemberId(membersDto.getMemberId());
members.setRole(membersDto.getRole());
members.setName(membersDto.getName());
+ members.setUsername(membersDto.getUsername());
members.setEmail(membersDto.getEmail());
members.setPassword(membersDto.getPassword());
members.setMembershipDate(membersDto.getMembershipDate());
return members;
}
+
/**
* Converts a Members entity to a MembersDto object.
*
* This method takes a Members entity object and converts it into a MembersDto
* object to be used for data transfer between layers. It maps all necessary
- * member details, including member ID, name, role, email, password, and membership
+ * member details, including member ID, name, username, role, email, password, and membership
* date, from the entity to the DTO.
*
* @param members the entity object containing member information
* @return a MembersDto object with data populated from the entity
*/
-
-
public MembersDto EntityToDto(Members members){
MembersDto membersDto= new MembersDto();
membersDto.setMemberId(members.getMemberId());
membersDto.setName(members.getName());
+ membersDto.setUsername(members.getUsername());
membersDto.setRole(members.getRole());
membersDto.setEmail(members.getEmail());
membersDto.setPassword(members.getPassword());
diff --git a/src/main/java/com/libraryman_api/member/Members.java b/src/main/java/com/libraryman_api/member/Members.java
index 352a3c7..aabdad3 100644
--- a/src/main/java/com/libraryman_api/member/Members.java
+++ b/src/main/java/com/libraryman_api/member/Members.java
@@ -2,11 +2,17 @@
import jakarta.persistence.*;
+import java.util.Collection;
+import java.util.Collections;
import java.util.Date;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.security.core.userdetails.UserDetails;
+
@Entity
-public class Members {
+public class Members implements UserDetails{
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE,
@@ -20,6 +26,9 @@ public class Members {
@Column(nullable = false)
private String name;
+ @Column(name = "username")
+ private String username;
+
@Column(unique = true, nullable = false)
private String email;
@@ -33,6 +42,7 @@ public class Members {
@Column(name = "membership_date")
private Date membershipDate;
+
public Members() {
@@ -91,4 +101,19 @@ public Date getMembershipDate() {
public void setMembershipDate(Date membershipDate) {
this.membershipDate = membershipDate;
}
+
+ public String getUsername() {
+ return username;
+ }
+
+ public void setUsername(String username) {
+ this.username = username;
+ }
+
+ @Override
+ public Collection extends GrantedAuthority> getAuthorities() {
+ // TODO Auto-generated method stub
+ return Collections.singletonList(new SimpleGrantedAuthority("ROLE_"+role.name()));
+ }
+
}
diff --git a/src/main/java/com/libraryman_api/member/MembersDto.java b/src/main/java/com/libraryman_api/member/MembersDto.java
index d4f67fe..93a7c73 100644
--- a/src/main/java/com/libraryman_api/member/MembersDto.java
+++ b/src/main/java/com/libraryman_api/member/MembersDto.java
@@ -4,10 +4,11 @@
public class MembersDto {
-
private int memberId;
private String name;
+
+ private String username;
private String email;
@@ -19,9 +20,10 @@ public class MembersDto {
private Date membershipDate;
- public MembersDto(int memberId, String name, String email, String password, Role role, Date membershipDate) {
+ public MembersDto(int memberId, String name, String username, String email, String password, Role role, Date membershipDate) {
this.memberId = memberId;
this.name = name;
+ this.username = username;
this.email = email;
this.password = password;
this.role = role;
@@ -42,10 +44,18 @@ public void setMemberId(int memberId) {
public String getName() {
return name;
}
+
+ public String getUsername() {
+ return username;
+ }
public void setName(String name) {
this.name = name;
}
+
+ public void setUsername(String username) {
+ this.username = username;
+ }
public String getEmail() {
return email;
@@ -84,6 +94,7 @@ public String toString() {
return "MembersDto{" +
"memberId=" + memberId +
", name='" + name + '\'' +
+ ", username='" + username + '\'' +
", email='" + email + '\'' +
", password='" + password + '\'' +
", role=" + role +
diff --git a/src/main/java/com/libraryman_api/security/config/PasswordEncoder.java b/src/main/java/com/libraryman_api/security/config/PasswordEncoder.java
new file mode 100644
index 0000000..4d2da03
--- /dev/null
+++ b/src/main/java/com/libraryman_api/security/config/PasswordEncoder.java
@@ -0,0 +1,14 @@
+package com.libraryman_api.security.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+
+@Configuration
+public class PasswordEncoder {
+
+ @Bean
+ public BCryptPasswordEncoder bCryptPasswordEncoder() {
+ return new BCryptPasswordEncoder();
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/libraryman_api/security/config/WebConfiguration.java b/src/main/java/com/libraryman_api/security/config/WebConfiguration.java
new file mode 100644
index 0000000..47d2d60
--- /dev/null
+++ b/src/main/java/com/libraryman_api/security/config/WebConfiguration.java
@@ -0,0 +1,87 @@
+package com.libraryman_api.security.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
+import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
+import org.springframework.security.web.SecurityFilterChain;
+import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
+import org.springframework.web.cors.CorsConfiguration;
+import org.springframework.web.cors.CorsConfigurationSource;
+import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
+import org.springframework.web.filter.CorsFilter;
+
+import com.libraryman_api.security.jwt.JwtAuthenticationFilter;
+
+import static org.springframework.security.config.Customizer.withDefaults;
+import static org.springframework.security.config.http.SessionCreationPolicy.STATELESS;
+
+@Configuration
+@EnableWebSecurity(debug = true) // Do not use (debug=true) in a production system! as this contain sensitive information.
+@EnableMethodSecurity(prePostEnabled = true)
+public class WebConfiguration {
+
+ private JwtAuthenticationFilter jwtFilter;
+
+ public WebConfiguration(JwtAuthenticationFilter jwtFilter) {
+ this.jwtFilter=jwtFilter;
+ }
+ @Bean
+ public SecurityFilterChain web(HttpSecurity http) throws Exception {
+ http
+ .csrf(AbstractHttpConfigurer::disable)
+ .authorizeHttpRequests((request) -> request
+ // make sure it is in order to access the proper Url
+
+ .requestMatchers("/api/signup").permitAll()
+ .requestMatchers("/api/login").permitAll()
+ .requestMatchers("/api/logout").permitAll()
+ .anyRequest().authenticated()
+ )
+ .logout(logout->logout
+ .deleteCookies("LibraryManCookie"))
+ .sessionManagement(session -> session.sessionCreationPolicy(STATELESS))
+ .formLogin(withDefaults());
+
+ http.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class)
+ .httpBasic(httpBasic -> {});
+
+ http.oauth2Login(withDefaults());
+ return http.build();
+ }
+
+ @Bean
+ public AuthenticationManager authenticationManager(AuthenticationConfiguration builder) throws Exception {
+ return builder.getAuthenticationManager();
+ }
+ @Bean
+ public CorsConfigurationSource corsConfigurationSource() {
+ UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
+ CorsConfiguration corsConfiguration = new CorsConfiguration();
+ corsConfiguration.setAllowCredentials(true);
+ corsConfiguration.addAllowedOriginPattern("*");
+ corsConfiguration.addAllowedHeader("Authorization");
+ corsConfiguration.addAllowedHeader("Content-Type");
+ corsConfiguration.addAllowedHeader("Accept");
+ corsConfiguration.addAllowedMethod("POST");
+ corsConfiguration.addAllowedMethod("PUT");
+ corsConfiguration.addAllowedMethod("GET");
+ corsConfiguration.addAllowedMethod("DELETE");
+ corsConfiguration.addAllowedMethod("OPTIONS");
+ corsConfiguration.setMaxAge(3600L);
+
+ source.registerCorsConfiguration("/**", corsConfiguration);
+ return source;
+ }
+
+ @Bean
+ public CorsFilter corsFilter() {
+ return new CorsFilter(corsConfigurationSource());
+ }
+
+}
diff --git a/src/main/java/com/libraryman_api/security/controllers/LoginController.java b/src/main/java/com/libraryman_api/security/controllers/LoginController.java
new file mode 100644
index 0000000..aed0c1e
--- /dev/null
+++ b/src/main/java/com/libraryman_api/security/controllers/LoginController.java
@@ -0,0 +1,43 @@
+package com.libraryman_api.security.controllers;
+
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RestController;
+
+import com.libraryman_api.security.model.LoginRequest;
+import com.libraryman_api.security.model.LoginResponse;
+import com.libraryman_api.security.services.LoginService;
+
+import jakarta.servlet.http.Cookie;
+import jakarta.servlet.http.HttpServletResponse;
+
+@RestController
+public class LoginController {
+
+ private final LoginService loginService;
+
+ public LoginController(LoginService loginService) {
+ this.loginService = loginService;
+ }
+
+ @PostMapping("/api/login")
+ public ResponseEntity login(@RequestBody LoginRequest loginRequest, HttpServletResponse response) {
+ LoginResponse loginResponse = loginService.login(loginRequest);
+
+ if (loginResponse != null) {
+ setAuthCookie(response);
+ return new ResponseEntity<>(loginResponse, HttpStatus.OK);
+ } else {
+ return new ResponseEntity<>(HttpStatus.UNAUTHORIZED);
+ }
+ }
+
+ private void setAuthCookie(HttpServletResponse response) {
+ Cookie cookie = new Cookie("LibraryManCookie", "libraryman_cookie");
+ cookie.setMaxAge(3600); // (3600 seconds)
+ cookie.setPath("/");
+ response.addCookie(cookie);
+ }
+}
diff --git a/src/main/java/com/libraryman_api/security/controllers/LogoutController.java b/src/main/java/com/libraryman_api/security/controllers/LogoutController.java
new file mode 100644
index 0000000..20d4a92
--- /dev/null
+++ b/src/main/java/com/libraryman_api/security/controllers/LogoutController.java
@@ -0,0 +1,34 @@
+package com.libraryman_api.security.controllers;
+
+import jakarta.servlet.http.Cookie;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+public class LogoutController {
+
+ @PostMapping("/api/logout")
+ public ResponseEntity logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
+ if (authentication != null) {
+ new SecurityContextLogoutHandler().logout(request, response, authentication);
+ }
+ removeAuthCookie(response);
+
+ return ResponseEntity.ok("Successfully logged out.");
+ }
+
+ private void removeAuthCookie(HttpServletResponse response) {
+ Cookie cookie = new Cookie("LibraryManCookie", null);
+ cookie.setMaxAge(0);
+ cookie.setPath("/");
+ cookie.setHttpOnly(true);
+ cookie.setSecure(true);
+ response.addCookie(cookie);
+ }
+}
diff --git a/src/main/java/com/libraryman_api/security/controllers/SignupController.java b/src/main/java/com/libraryman_api/security/controllers/SignupController.java
new file mode 100644
index 0000000..a1f6891
--- /dev/null
+++ b/src/main/java/com/libraryman_api/security/controllers/SignupController.java
@@ -0,0 +1,36 @@
+package com.libraryman_api.security.controllers;
+
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RestController;
+
+import com.libraryman_api.member.Members;
+import com.libraryman_api.security.services.SignupService;
+
+
+@RestController
+public class SignupController {
+
+ private SignupService signupService;
+ public SignupController(SignupService signupService) {
+ this.signupService=signupService;
+ }
+
+ @PostMapping("/api/signup")
+ public void signup(@RequestBody Members members) {
+ this.signupService.signup(members);
+
+ }
+ @PostMapping("/api/signup/admin")
+ @PreAuthorize("hasRole('ADMIN')")
+ public void signupAdmin(@RequestBody Members members) {
+ this.signupService.signupAdmin(members);
+ }
+ @PostMapping("/api/signup/librarian")
+ @PreAuthorize("hasRole('LIBRARIAN') or hasRole('ADMIN')")
+ public void signupLibrarian(@RequestBody Members members) {
+ this.signupService.signupLibrarian(members);
+ }
+}
diff --git a/src/main/java/com/libraryman_api/security/jwt/JwtAuthenticationFilter.java b/src/main/java/com/libraryman_api/security/jwt/JwtAuthenticationFilter.java
new file mode 100644
index 0000000..875779b
--- /dev/null
+++ b/src/main/java/com/libraryman_api/security/jwt/JwtAuthenticationFilter.java
@@ -0,0 +1,62 @@
+package com.libraryman_api.security.jwt;
+
+
+import java.io.IOException;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
+import org.springframework.stereotype.Component;
+import org.springframework.web.filter.OncePerRequestFilter;
+
+import jakarta.servlet.FilterChain;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+
+@Component
+public class JwtAuthenticationFilter extends OncePerRequestFilter {
+
+ private JwtAuthenticationHelper jwtHelper;
+
+ private UserDetailsService userDetailsService;
+
+ public JwtAuthenticationFilter(JwtAuthenticationHelper jwtHelper,UserDetailsService userDetailsService) {
+ this.jwtHelper=jwtHelper;
+ this.userDetailsService=userDetailsService;
+ }
+
+ @Override
+ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
+ throws ServletException, IOException {
+
+ String requestHeader=request.getHeader("Authorization");
+ String username=null;
+ String token=null;
+ if(requestHeader!=null && requestHeader.startsWith("Bearer ")) {
+ token=requestHeader.substring(7);
+ username=jwtHelper.getUsernameFromToken(token);
+ if(username!=null && SecurityContextHolder.getContext().getAuthentication() == null) {
+ UserDetails userDetails=userDetailsService.loadUserByUsername(username);
+ if(!(jwtHelper.isTokenExpired(token))) {
+ UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken=new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
+ usernamePasswordAuthenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
+ SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
+
+ }
+ else {
+ System.out.println("Token is expired or user details not found.");
+ }
+ }
+
+ }
+ filterChain.doFilter(request, response);
+ }
+
+
+
+
+}
diff --git a/src/main/java/com/libraryman_api/security/jwt/JwtAuthenticationHelper.java b/src/main/java/com/libraryman_api/security/jwt/JwtAuthenticationHelper.java
new file mode 100644
index 0000000..b44e446
--- /dev/null
+++ b/src/main/java/com/libraryman_api/security/jwt/JwtAuthenticationHelper.java
@@ -0,0 +1,49 @@
+package com.libraryman_api.security.jwt;
+
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.crypto.spec.SecretKeySpec;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.stereotype.Component;
+
+import io.jsonwebtoken.Claims;
+import io.jsonwebtoken.Jwts;
+import io.jsonwebtoken.SignatureAlgorithm;
+
+@Component
+public class JwtAuthenticationHelper {
+
+ private static final long JWT_TOKEN_VALIDITY=60*60;
+ @Value("${jwt.secretKey}")
+ private String secret;
+ public String getUsernameFromToken(String token) {
+ String username=getClaimsFromToken(token).getSubject();
+ return username;
+ }
+
+ public Claims getClaimsFromToken(String token) {
+ Claims claims=Jwts.parserBuilder()
+ .setSigningKey(secret.getBytes())
+ .build().parseClaimsJws(token).getBody();
+ return claims;
+ }
+
+ public Boolean isTokenExpired(String token) {
+ Claims claims=getClaimsFromToken(token);
+ Date expDate=claims.getExpiration();
+ return expDate.before(new Date());
+ }
+
+ public String generateToken(UserDetails userDetails) {
+ Map claims=new HashMap<>();
+ return Jwts.builder().setClaims(claims).setSubject(userDetails.getUsername())
+ .setIssuedAt(new Date(System.currentTimeMillis()))
+ .setExpiration(new Date(System.currentTimeMillis()+JWT_TOKEN_VALIDITY*1000))
+ .signWith(new SecretKeySpec(secret.getBytes(),SignatureAlgorithm.HS512.getJcaName()),SignatureAlgorithm.HS512)
+ .compact();
+ }
+}
diff --git a/src/main/java/com/libraryman_api/security/model/LoginRequest.java b/src/main/java/com/libraryman_api/security/model/LoginRequest.java
new file mode 100644
index 0000000..e77d1cd
--- /dev/null
+++ b/src/main/java/com/libraryman_api/security/model/LoginRequest.java
@@ -0,0 +1,28 @@
+package com.libraryman_api.security.model;
+
+public class LoginRequest {
+
+ private String username;
+
+ private String password;
+
+
+
+ public String getUsername() {
+ return username;
+ }
+
+ public void setUsername(String username) {
+ this.username = username;
+ }
+
+ public String getPassword() {
+ return password;
+ }
+
+ public void setPassword(String password) {
+ this.password = password;
+ }
+
+
+}
diff --git a/src/main/java/com/libraryman_api/security/model/LoginResponse.java b/src/main/java/com/libraryman_api/security/model/LoginResponse.java
new file mode 100644
index 0000000..2b53d41
--- /dev/null
+++ b/src/main/java/com/libraryman_api/security/model/LoginResponse.java
@@ -0,0 +1,21 @@
+package com.libraryman_api.security.model;
+
+
+public class LoginResponse {
+
+ private String token;
+
+ public LoginResponse(String token) {
+ this.token=token;
+ }
+ public String getToken() {
+ return token;
+ }
+
+ public void setToken(String token) {
+ this.token = token;
+ }
+
+
+
+}
diff --git a/src/main/java/com/libraryman_api/security/services/CustomUserDetailsService.java b/src/main/java/com/libraryman_api/security/services/CustomUserDetailsService.java
new file mode 100644
index 0000000..2c66c82
--- /dev/null
+++ b/src/main/java/com/libraryman_api/security/services/CustomUserDetailsService.java
@@ -0,0 +1,22 @@
+package com.libraryman_api.security.services;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.core.userdetails.UsernameNotFoundException;
+import org.springframework.stereotype.Service;
+
+import com.libraryman_api.member.MemberRepository;
+@Service
+public class CustomUserDetailsService implements UserDetailsService{
+
+ @Autowired
+ MemberRepository memberRepository;
+
+ @Override
+ public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
+
+ return memberRepository.findByUsername(username).orElseThrow(()-> new UsernameNotFoundException("Username not Found"));
+ }
+
+}
diff --git a/src/main/java/com/libraryman_api/security/services/LoginService.java b/src/main/java/com/libraryman_api/security/services/LoginService.java
new file mode 100644
index 0000000..c255c4e
--- /dev/null
+++ b/src/main/java/com/libraryman_api/security/services/LoginService.java
@@ -0,0 +1,51 @@
+package com.libraryman_api.security.services;
+
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.authentication.BadCredentialsException;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.stereotype.Service;
+
+import com.libraryman_api.member.MemberRepository;
+import com.libraryman_api.security.jwt.JwtAuthenticationHelper;
+import com.libraryman_api.security.model.LoginRequest;
+import com.libraryman_api.security.model.LoginResponse;
+
+
+@Service
+public class LoginService {
+
+ private AuthenticationManager authenticationManager;
+
+ private UserDetailsService userDetailsService;
+
+ private JwtAuthenticationHelper jwtHelper;
+
+ private MemberRepository memberRepository;
+
+ public LoginService(AuthenticationManager authenticationManager,UserDetailsService userDetailsService,JwtAuthenticationHelper jwtHelper,MemberRepository memberRepository) {
+ this.authenticationManager=authenticationManager;
+ this.userDetailsService=userDetailsService;
+ this.jwtHelper=jwtHelper;
+ this.memberRepository=memberRepository;
+ }
+
+ public LoginResponse login(LoginRequest loginRequest) {
+ Authenticate(loginRequest.getUsername(), loginRequest.getPassword());
+ UserDetails userDetails=userDetailsService.loadUserByUsername(loginRequest.getUsername());
+ String token=jwtHelper.generateToken(userDetails);
+ LoginResponse loginResponse=new LoginResponse(token);
+ return loginResponse;
+ }
+
+ public void Authenticate(String username,String password) {
+ UsernamePasswordAuthenticationToken authenticateToken=new UsernamePasswordAuthenticationToken(username, password);
+ try {
+ authenticationManager.authenticate(authenticateToken);
+ }
+ catch(BadCredentialsException e){
+ throw new BadCredentialsException("Invalid Username or Password");
+ }
+ }
+}
diff --git a/src/main/java/com/libraryman_api/security/services/SignupService.java b/src/main/java/com/libraryman_api/security/services/SignupService.java
new file mode 100644
index 0000000..33ff122
--- /dev/null
+++ b/src/main/java/com/libraryman_api/security/services/SignupService.java
@@ -0,0 +1,88 @@
+package com.libraryman_api.security.services;
+
+import java.util.Date;
+import java.util.Optional;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+
+
+import com.libraryman_api.exception.ResourceNotFoundException;
+import com.libraryman_api.member.MemberRepository;
+import com.libraryman_api.member.Members;
+import com.libraryman_api.member.Role;
+import com.libraryman_api.security.config.PasswordEncoder;
+
+@Service
+public class SignupService {
+
+ private MemberRepository memberRepository;
+
+ private PasswordEncoder passwordEncoder;
+
+
+ public SignupService(MemberRepository memberRepository,PasswordEncoder passwordEncoder) {
+ this.memberRepository=memberRepository;
+ this.passwordEncoder=passwordEncoder;
+ }
+ public void signup(Members members) {
+ Optional memberOptId=memberRepository.findById(members.getMemberId());
+ Optional memberOptUsername=memberRepository.findByUsername(members.getUsername());
+ if(memberOptId.isPresent()) {
+ throw new ResourceNotFoundException("User already Exists");
+ }
+ if(memberOptUsername.isPresent()) {
+ throw new ResourceNotFoundException("User already Exists");
+ }
+ String encoded_password=passwordEncoder.bCryptPasswordEncoder().encode(members.getPassword());
+ Members new_members=new Members();
+ new_members.setEmail(members.getEmail());
+ new_members.setName(members.getName());
+ new_members.setPassword(encoded_password);
+ new_members.setRole(Role.USER);
+ new_members.setMembershipDate(new Date());
+ new_members.setUsername(members.getUsername());
+ memberRepository.save(new_members);
+ }
+
+ public void signupAdmin(Members members) {
+ Optional memberOptId=memberRepository.findById(members.getMemberId());
+ Optional memberOptUsername=memberRepository.findByUsername(members.getUsername());
+ if(memberOptId.isPresent()) {
+ throw new ResourceNotFoundException("User already Exists");
+ }
+ if(memberOptUsername.isPresent()) {
+ throw new ResourceNotFoundException("User already Exists");
+ }
+
+ String encoded_password=passwordEncoder.bCryptPasswordEncoder().encode(members.getPassword());
+ Members new_members=new Members();
+ new_members.setEmail(members.getEmail());
+ new_members.setName(members.getName());
+ new_members.setPassword(encoded_password);
+ new_members.setRole(Role.ADMIN);
+ new_members.setMembershipDate(new Date());
+ new_members.setUsername(members.getUsername());
+ memberRepository.save(new_members);
+
+ }
+ public void signupLibrarian(Members members) {
+ Optional memberOptId=memberRepository.findById(members.getMemberId());
+ Optional memberOptUsername=memberRepository.findByUsername(members.getUsername());
+ if(memberOptId.isPresent()) {
+ throw new ResourceNotFoundException("User already Exists");
+ }
+ if(memberOptUsername.isPresent()) {
+ throw new ResourceNotFoundException("User already Exists");
+ }
+ String encoded_password=passwordEncoder.bCryptPasswordEncoder().encode(members.getPassword());
+ Members new_members=new Members();
+ new_members.setEmail(members.getEmail());
+ new_members.setName(members.getName());
+ new_members.setPassword(encoded_password);
+ new_members.setRole(Role.LIBRARIAN);
+ new_members.setMembershipDate(new Date());
+ new_members.setUsername(members.getUsername());
+ memberRepository.save(new_members);
+ }
+}
diff --git a/src/main/resources/application-development.properties b/src/main/resources/application-development.properties
index aedaf67..30ef46b 100644
--- a/src/main/resources/application-development.properties
+++ b/src/main/resources/application-development.properties
@@ -30,8 +30,6 @@ server.error.include-exception=true
# Logging for Spring Security
logging.level.org.springframework.security=TRACE
-
-
# --- Mail Service Setup ---
# I use docker mail service https://hub.docker.com/r/maildev/maildev
@@ -43,6 +41,3 @@ spring.mail.password=Add_Your_Mail_Service_Password
spring.mail.properties.mail.smtp.auth=Add_Your_Mail_Service_SMTP
spring.mail.properties.mail.starttls.enable=Add_Your_Mail_Service_Start_TLS
spring.mail.properties.domain_name=Add_Your_Mail_Service_Domain_Name
-
-
-
diff --git a/src/main/resources/application-production.properties b/src/main/resources/application-production.properties
index 6b8edc4..3e13ee0 100644
--- a/src/main/resources/application-production.properties
+++ b/src/main/resources/application-production.properties
@@ -6,7 +6,6 @@ spring.datasource.driver-class-name=${DATABASE_DRIVER_CLASS_NAME}
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=false
-
# --- Mail Service Setup ---
spring.mail.host=${MAIL_SERVICE_HOST}
spring.mail.port=${MAIL_SERVICE_PORT}
@@ -14,4 +13,10 @@ spring.mail.username=${MAIL_SERVICE_USERNAME}
spring.mail.password=${MAIL_SERVICE_PASSWORD}
spring.mail.properties.mail.smtp.auth=${MAIL_SERVICE_SMTP}
spring.mail.properties.mail.starttls.enable=${MAIL_SERVICE_STARTTLS}
-spring.mail.properties.domain_name=${MAIL_SERVICE_DOMAIN_NAME}
\ No newline at end of file
+spring.mail.properties.domain_name=${MAIL_SERVICE_DOMAIN_NAME}
+
+# --- Oauth 2.0 Configurations ---
+spring.security.oauth2.client.registration.google.client-name=google
+spring.security.oauth2.client.registration.google.client-id=${YOUR_CLIENT_ID}
+spring.security.oauth2.client.registration.google.client-secret=${YOUR_SECRET_KEY}
+spring.security.oauth2.client.registration.google.scope=email,profile
\ No newline at end of file
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
index 53718a7..9cd73f6 100644
--- a/src/main/resources/application.properties
+++ b/src/main/resources/application.properties
@@ -1,2 +1,3 @@
spring.application.name=libraryman-api
-spring.profiles.active=${ENV:development}
\ No newline at end of file
+spring.profiles.active=${ENV:development}
+jwt.secretKey=${YOUR_JWT_SECRET_KEY}
\ No newline at end of file