diff --git a/pom.xml b/pom.xml index eb72bdc..4a22100 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 @@ -50,7 +54,23 @@ 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 @@ -80,6 +100,11 @@ spring-security-test test + + org.springframework.security + spring-security-oauth2-client + + diff --git a/src/main/java/com/libraryman_api/book/BookController.java b/src/main/java/com/libraryman_api/book/BookController.java index 778f7c4..a071d7d 100644 --- a/src/main/java/com/libraryman_api/book/BookController.java +++ b/src/main/java/com/libraryman_api/book/BookController.java @@ -3,6 +3,7 @@ import com.libraryman_api.exception.ResourceNotFoundException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.*; import java.util.List; @@ -51,6 +52,7 @@ public ResponseEntity getBookById(@PathVariable int id) { * @return the added {@link Book} object. */ @PostMapping + @PreAuthorize("hasRole('LIBRARIAN') or hasRole('ADMIN')") public Book addBook(@RequestBody Book book) { return bookService.addBook(book); } @@ -63,6 +65,7 @@ public Book addBook(@RequestBody Book book) { * @return the updated {@link Book} object. */ @PutMapping("/{id}") + @PreAuthorize("hasRole('LIBRARIAN') or hasRole('ADMIN')") public Book updateBook(@PathVariable int id, @RequestBody Book bookDetails) { return bookService.updateBook(id, bookDetails); } @@ -73,6 +76,7 @@ public Book updateBook(@PathVariable int id, @RequestBody Book bookDetails) { * @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 e76ae53..042dd26 100644 --- a/src/main/java/com/libraryman_api/borrowing/BorrowingController.java +++ b/src/main/java/com/libraryman_api/borrowing/BorrowingController.java @@ -1,6 +1,8 @@ package com.libraryman_api.borrowing; import com.libraryman_api.exception.ResourceNotFoundException; + +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.*; import java.util.List; @@ -31,6 +33,7 @@ public BorrowingController(BorrowingService borrowingService) { * @return a list of {@link Borrowings} objects representing all borrowings. */ @GetMapping + @PreAuthorize("hasRole('LIBRARIAN') or hasRole('ADMIN')") public List getAllBorrowings() { return borrowingService.getAllBorrowings(); } @@ -75,6 +78,7 @@ public String payFine(@PathVariable int id) { * @return a list of {@link Borrowings} objects representing the member's borrowings. */ @GetMapping("member/{memberId}") + @PreAuthorize("hasRole('LIBRARIAN') or hasRole('ADMIN')") public List getAllBorrowingsOfAMember(@PathVariable int memberId) { return borrowingService.getAllBorrowingsOfMember(memberId); } @@ -87,6 +91,7 @@ public List 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 Borrowings 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 246589c..8a1c750 100644 --- a/src/main/java/com/libraryman_api/member/MemberController.java +++ b/src/main/java/com/libraryman_api/member/MemberController.java @@ -2,6 +2,7 @@ import com.libraryman_api.exception.ResourceNotFoundException; import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.*; import java.util.List; @@ -31,6 +32,7 @@ public MemberController(MemberService memberService) { * @return a list of {@link Members} representing all members in the library */ @GetMapping + @PreAuthorize("hasRole('LIBRARIAN') or hasRole('ADMIN')") public List getAllMembers() { return memberService.getAllMembers(); } @@ -43,6 +45,7 @@ public List getAllMembers() { * @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) @@ -80,6 +83,7 @@ public Members updateMember(@PathVariable int id, @RequestBody Members memberDet * @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/Members.java b/src/main/java/com/libraryman_api/member/Members.java index f11b805..591eeef 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() { @@ -89,4 +99,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 getAuthorities() { + // TODO Auto-generated method stub + return Collections.singletonList(new SimpleGrantedAuthority("ROLE_"+role.name())); + } + } diff --git a/src/main/java/com/libraryman_api/security/LoginController.java b/src/main/java/com/libraryman_api/security/LoginController.java deleted file mode 100644 index 27349de..0000000 --- a/src/main/java/com/libraryman_api/security/LoginController.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.libraryman_api.security; - -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RestController; - -import java.security.Principal; - -@RestController -public class LoginController { - - @GetMapping("/api/ajay") - public String login(Principal principal) { - System.out.println("\n\n\n\n\n\n\n\n\n\n\n\n\n\n"); - System.out.println("Principal Name: " + principal.getName()); - System.out.println("Principal: " + principal); - - return "Hello World"; - } - -} diff --git a/src/main/java/com/libraryman_api/security/SignupController.java b/src/main/java/com/libraryman_api/security/SignupController.java deleted file mode 100644 index 68584e1..0000000 --- a/src/main/java/com/libraryman_api/security/SignupController.java +++ /dev/null @@ -1,4 +0,0 @@ -package com.libraryman_api.security; - -public class SignupController { -} diff --git a/src/main/java/com/libraryman_api/security/WebConfiguration.java b/src/main/java/com/libraryman_api/security/WebConfiguration.java deleted file mode 100644 index a0b6518..0000000 --- a/src/main/java/com/libraryman_api/security/WebConfiguration.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.libraryman_api.security; - -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.http.HttpMethod; -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 static com.libraryman_api.member.Role.ADMIN; -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. -public class WebConfiguration { - - @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("/signup").permitAll() - .requestMatchers("/login").permitAll() - ) -// .sessionManagement(session -> session.sessionCreationPolicy(STATELESS)) - .formLogin(withDefaults()); - return http.build(); - } -} diff --git a/src/main/java/com/libraryman_api/security/PasswordEncoder.java b/src/main/java/com/libraryman_api/security/config/PasswordEncoder.java similarity index 88% rename from src/main/java/com/libraryman_api/security/PasswordEncoder.java rename to src/main/java/com/libraryman_api/security/config/PasswordEncoder.java index 31a6fa5..4d2da03 100644 --- a/src/main/java/com/libraryman_api/security/PasswordEncoder.java +++ b/src/main/java/com/libraryman_api/security/config/PasswordEncoder.java @@ -1,4 +1,4 @@ -package com.libraryman_api.security; +package com.libraryman_api.security.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; 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-production.properties b/src/main/resources/application-production.properties index 6b8edc4..76adb12 100644 --- a/src/main/resources/application-production.properties +++ b/src/main/resources/application-production.properties @@ -14,4 +14,12 @@ 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} +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 + diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index eeefb5c..004aed8 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:dev} \ No newline at end of file +spring.profiles.active=${ENV:dev} +jwt.secretKey=${YOUR_JWT_SECRET_KEY}