diff --git a/src/main/java/com/example/onomatopoeiaback/controller/EmployeeController.java b/src/main/java/com/example/onomatopoeiaback/controller/EmployeeController.java index e3f23e7..41362e8 100644 --- a/src/main/java/com/example/onomatopoeiaback/controller/EmployeeController.java +++ b/src/main/java/com/example/onomatopoeiaback/controller/EmployeeController.java @@ -7,6 +7,7 @@ import com.example.onomatopoeiaback.domain.visit.Visit; import com.example.onomatopoeiaback.domain.visit.VisitDTO; import com.example.onomatopoeiaback.service.EmployeeService; import com.example.onomatopoeiaback.service.VisitService; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; @@ -26,22 +27,26 @@ public class EmployeeController { } @PostMapping("/create") + @SecurityRequirement(name = "basicAuth") public ResponseEntity createEmployee(@RequestBody EmployeeDTO employeeDTO) { return ResponseEntity.ok(employeeService.createEmployee(employeeDTO)); } @GetMapping("/{username}/info") + @SecurityRequirement(name = "basicAuth") public ResponseEntity info(@PathVariable String username) { return ResponseEntity.ok(employeeService.info(username)); } @GetMapping("/{username}/auth") + @SecurityRequirement(name = "basicAuth") public ResponseEntity auth(@PathVariable String username) { employeeService.auth(username); return new ResponseEntity<>(HttpStatus.OK); } @PatchMapping("/{username}/open") + @SecurityRequirement(name = "basicAuth") public ResponseEntity open(@PathVariable String username, @RequestBody VisitDTO visitDTO) { visitService.register(username, visitDTO); return new ResponseEntity<>(HttpStatus.OK); diff --git a/src/main/java/com/example/onomatopoeiaback/controller/QrCodeController.java b/src/main/java/com/example/onomatopoeiaback/controller/QrCodeController.java index 50706e4..5bffd98 100644 --- a/src/main/java/com/example/onomatopoeiaback/controller/QrCodeController.java +++ b/src/main/java/com/example/onomatopoeiaback/controller/QrCodeController.java @@ -1,8 +1,11 @@ package com.example.onomatopoeiaback.controller; import com.example.onomatopoeiaback.domain.qrcode.QrCode; +import com.example.onomatopoeiaback.security.Auth; import com.example.onomatopoeiaback.service.QrCodeService; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; import org.springframework.http.ResponseEntity; +import org.springframework.security.core.Authentication; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; @@ -19,7 +22,9 @@ public class QrCodeController { } @PostMapping("/create") - public ResponseEntity createQrCode(@RequestParam String name) { + @SecurityRequirement(name = "basicAuth") + public ResponseEntity createQrCode(Authentication authentication, @RequestParam String name) { + Auth.getEmployee(authentication); return ResponseEntity.ok(qrCodeService.createQrCode(name)); } } diff --git a/src/main/java/com/example/onomatopoeiaback/controller/VisitController.java b/src/main/java/com/example/onomatopoeiaback/controller/VisitController.java index b89a78c..4c40b3c 100644 --- a/src/main/java/com/example/onomatopoeiaback/controller/VisitController.java +++ b/src/main/java/com/example/onomatopoeiaback/controller/VisitController.java @@ -3,6 +3,7 @@ package com.example.onomatopoeiaback.controller; import com.example.onomatopoeiaback.domain.visit.Visit; import com.example.onomatopoeiaback.domain.visit.VisitDTO; import com.example.onomatopoeiaback.service.VisitService; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; import org.springframework.data.domain.Page; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; @@ -19,6 +20,7 @@ public class VisitController { } @GetMapping("/{login}/visits") + @SecurityRequirement(name = "basicAuth") public ResponseEntity> getVisits( @PathVariable String login, @RequestParam(defaultValue = "0") int page, diff --git a/src/main/java/com/example/onomatopoeiaback/exceptions/ForbiddenException.java b/src/main/java/com/example/onomatopoeiaback/exceptions/ForbiddenException.java new file mode 100644 index 0000000..8f7d2db --- /dev/null +++ b/src/main/java/com/example/onomatopoeiaback/exceptions/ForbiddenException.java @@ -0,0 +1,8 @@ +package com.example.onomatopoeiaback.exceptions; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +@ResponseStatus(code = HttpStatus.FORBIDDEN, reason = "Доступ запрещен") +public class ForbiddenException extends RuntimeException { +} diff --git a/src/main/java/com/example/onomatopoeiaback/exceptions/GeneralExceptionsHandler.java b/src/main/java/com/example/onomatopoeiaback/exceptions/GeneralExceptionsHandler.java index 2ebcacd..dd1dfe1 100644 --- a/src/main/java/com/example/onomatopoeiaback/exceptions/GeneralExceptionsHandler.java +++ b/src/main/java/com/example/onomatopoeiaback/exceptions/GeneralExceptionsHandler.java @@ -21,4 +21,11 @@ public class GeneralExceptionsHandler { public ExceptionDTO handleUnauthorizedException(UnauthorizedException e) { return new ExceptionDTO("UNAUTHORIZED", e.getMessage()); } + + @ResponseStatus(HttpStatus.FORBIDDEN) + @ExceptionHandler(ForbiddenException.class) + @ResponseBody + public ExceptionDTO handleForbiddenException(ForbiddenException e) { + return new ExceptionDTO("FORBIDDEN", e.getMessage()); + } } diff --git a/src/main/java/com/example/onomatopoeiaback/repository/EmployeeRepository.java b/src/main/java/com/example/onomatopoeiaback/repository/EmployeeRepository.java index 34b49cf..06a4e71 100644 --- a/src/main/java/com/example/onomatopoeiaback/repository/EmployeeRepository.java +++ b/src/main/java/com/example/onomatopoeiaback/repository/EmployeeRepository.java @@ -1,12 +1,15 @@ package com.example.onomatopoeiaback.repository; import com.example.onomatopoeiaback.domain.employee.Employee; +import lombok.NonNull; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; +import java.util.Optional; + @Repository public interface EmployeeRepository extends JpaRepository { Employee getEmployeeById(Long id); - Employee getEmployeesByLogin(String login); + Optional findByLogin(@NonNull String login); } diff --git a/src/main/java/com/example/onomatopoeiaback/security/Auth.java b/src/main/java/com/example/onomatopoeiaback/security/Auth.java new file mode 100644 index 0000000..5c0a570 --- /dev/null +++ b/src/main/java/com/example/onomatopoeiaback/security/Auth.java @@ -0,0 +1,14 @@ +package com.example.onomatopoeiaback.security; + +import com.example.onomatopoeiaback.domain.employee.Employee; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; + +public class Auth { + public static Employee getEmployee(Authentication authentication) { + if (authentication instanceof UsernamePasswordAuthenticationToken t) { + return ((CustomEmployeeDetails) t.getPrincipal()).getEmployee(); + } + return null; + } +} diff --git a/src/main/java/com/example/onomatopoeiaback/security/CustomAuthenticationProvider.java b/src/main/java/com/example/onomatopoeiaback/security/CustomAuthenticationProvider.java index 78044c5..c4a3f43 100644 --- a/src/main/java/com/example/onomatopoeiaback/security/CustomAuthenticationProvider.java +++ b/src/main/java/com/example/onomatopoeiaback/security/CustomAuthenticationProvider.java @@ -1,15 +1,40 @@ package com.example.onomatopoeiaback.security; +import com.example.onomatopoeiaback.exceptions.BadRequestException; +import com.example.onomatopoeiaback.exceptions.ForbiddenException; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.AuthenticationProvider; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; +import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Component; @Component public class CustomAuthenticationProvider implements AuthenticationProvider { + final + CustomEmployeeDetailsService customEmployeeDetailsService; + final PasswordEncoder passwordEncoder; + + public CustomAuthenticationProvider(CustomEmployeeDetailsService customEmployeeDetailsService, PasswordEncoder bCryptPasswordEncoder, PasswordEncoder passwordEncoder) { + this.customEmployeeDetailsService = customEmployeeDetailsService; + this.passwordEncoder = passwordEncoder; + } + @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { - return null; + String login = authentication.getName(); + String password = passwordEncoder.encode((String) authentication.getCredentials()); + if (login == null || password == null) { + throw new BadRequestException(); + } + + CustomEmployeeDetails customEmployeeDetails = customEmployeeDetailsService.loadUserByUsername(login); + if (customEmployeeDetails == null || !customEmployeeDetails.getPassword().equals(password)) { + throw new ForbiddenException(); + } + + return new UsernamePasswordAuthenticationToken(customEmployeeDetails, null, customEmployeeDetails.getAuthorities()); } @Override diff --git a/src/main/java/com/example/onomatopoeiaback/security/CustomEmployeeDetails.java b/src/main/java/com/example/onomatopoeiaback/security/CustomEmployeeDetails.java new file mode 100644 index 0000000..ebe33d9 --- /dev/null +++ b/src/main/java/com/example/onomatopoeiaback/security/CustomEmployeeDetails.java @@ -0,0 +1,33 @@ +package com.example.onomatopoeiaback.security; + +import com.example.onomatopoeiaback.domain.employee.Employee; +import lombok.Getter; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; + +import java.util.Collection; +import java.util.Collections; + +@Getter +public class CustomEmployeeDetails implements UserDetails { + Employee employee; + + public CustomEmployeeDetails(Employee employee) { + this.employee = employee; + } + + @Override + public Collection getAuthorities() { + return Collections.emptyList(); + } + + @Override + public String getPassword() { + return employee.getPassword(); + } + + @Override + public String getUsername() { + return employee.getLogin(); + } +} diff --git a/src/main/java/com/example/onomatopoeiaback/security/CustomEmployeeDetailsService.java b/src/main/java/com/example/onomatopoeiaback/security/CustomEmployeeDetailsService.java new file mode 100644 index 0000000..2261b0f --- /dev/null +++ b/src/main/java/com/example/onomatopoeiaback/security/CustomEmployeeDetailsService.java @@ -0,0 +1,25 @@ +package com.example.onomatopoeiaback.security; + +import com.example.onomatopoeiaback.domain.employee.Employee; +import com.example.onomatopoeiaback.exceptions.UnauthorizedException; +import com.example.onomatopoeiaback.repository.EmployeeRepository; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.stereotype.Service; + +import java.util.Optional; + +@Service("employeeDetailsService") +public class CustomEmployeeDetailsService implements UserDetailsService { + final + EmployeeRepository employeeRepository; + + public CustomEmployeeDetailsService(EmployeeRepository employeeRepository) { + this.employeeRepository = employeeRepository; + } + + @Override + public CustomEmployeeDetails loadUserByUsername(String login) throws UnauthorizedException { + Optional employeeOptional = employeeRepository.findByLogin(login); + return employeeOptional.map(CustomEmployeeDetails::new).orElse(null); + } +} diff --git a/src/main/java/com/example/onomatopoeiaback/security/NoPopupBasicAuthenticationEntryPoint.java b/src/main/java/com/example/onomatopoeiaback/security/NoPopupBasicAuthenticationEntryPoint.java new file mode 100644 index 0000000..f1a4087 --- /dev/null +++ b/src/main/java/com/example/onomatopoeiaback/security/NoPopupBasicAuthenticationEntryPoint.java @@ -0,0 +1,20 @@ +package com.example.onomatopoeiaback.security; + +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.AuthenticationEntryPoint; + +import java.io.IOException; + +public class NoPopupBasicAuthenticationEntryPoint implements AuthenticationEntryPoint { + + @Override + public void commence(HttpServletRequest request, HttpServletResponse response, + AuthenticationException authException) throws IOException, ServletException { + + response.sendError(HttpServletResponse.SC_UNAUTHORIZED, authException.getMessage()); + } + +} \ No newline at end of file diff --git a/src/main/java/com/example/onomatopoeiaback/security/PasswordEncoderConfiguration.java b/src/main/java/com/example/onomatopoeiaback/security/PasswordEncoderConfiguration.java new file mode 100644 index 0000000..f388099 --- /dev/null +++ b/src/main/java/com/example/onomatopoeiaback/security/PasswordEncoderConfiguration.java @@ -0,0 +1,14 @@ +package com.example.onomatopoeiaback.security; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; + +@Configuration +public class PasswordEncoderConfiguration { + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } +} diff --git a/src/main/java/com/example/onomatopoeiaback/security/SecurityConfig.java b/src/main/java/com/example/onomatopoeiaback/security/SecurityConfig.java index e7971f6..a2e599c 100644 --- a/src/main/java/com/example/onomatopoeiaback/security/SecurityConfig.java +++ b/src/main/java/com/example/onomatopoeiaback/security/SecurityConfig.java @@ -1,16 +1,53 @@ package com.example.onomatopoeiaback.security; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Configurable; -import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +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.config.http.SessionCreationPolicy; import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.web.SecurityFilterChain; -@Configurable +@Configuration @EnableWebSecurity public class SecurityConfig { + private final UserDetailsService userDetailsService; + private final CustomAuthenticationProvider customAuthenticationProvider; + + public SecurityConfig(CustomAuthenticationProvider customAuthenticationProvider, UserDetailsService userDetailsService) { + this.customAuthenticationProvider = customAuthenticationProvider; + this.userDetailsService = userDetailsService; + } + @Autowired - private UserDetailsService userDetailsService; - + public void configureGlobal(AuthenticationManagerBuilder auth, PasswordEncoder passwordEncoder) throws Exception { + auth + .authenticationProvider(customAuthenticationProvider) + .userDetailsService(userDetailsService) + .passwordEncoder(passwordEncoder); + } + @Bean + public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception { + httpSecurity + .csrf(AbstractHttpConfigurer::disable) + .sessionManagement(management -> management + .sessionCreationPolicy(SessionCreationPolicy.STATELESS)) + .httpBasic(basic -> basic + .authenticationEntryPoint(new NoPopupBasicAuthenticationEntryPoint())) + .authorizeHttpRequests(requests -> requests + .requestMatchers( + "/docs", + "/docs/**", + "/v3/api-docs/**", + "/swagger-ui/**" + ) + .permitAll() + .anyRequest().authenticated()); + return httpSecurity.build(); + } } diff --git a/src/main/java/com/example/onomatopoeiaback/service/EmployeeService.java b/src/main/java/com/example/onomatopoeiaback/service/EmployeeService.java index e05c5a5..16721af 100644 --- a/src/main/java/com/example/onomatopoeiaback/service/EmployeeService.java +++ b/src/main/java/com/example/onomatopoeiaback/service/EmployeeService.java @@ -8,6 +8,8 @@ import com.example.onomatopoeiaback.repository.VisitRepository; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.stereotype.Service; +import java.util.Optional; + @Service public class EmployeeService { @@ -34,20 +36,20 @@ public class EmployeeService { } public void auth(String login) { - Employee employee = employeeRepository.getEmployeesByLogin(login); + Optional employeeOptional = employeeRepository.findByLogin(login); - if (employee == null) { + if (employeeOptional.isEmpty()) { throw new UnauthorizedException(); } } public Employee info(String login) { - Employee employee = employeeRepository.getEmployeesByLogin(login); + Optional employeeOptional = employeeRepository.findByLogin(login); - if (employee == null) { + if (employeeOptional.isEmpty()) { throw new UnauthorizedException(); } - return employee; + return employeeOptional.get(); } } diff --git a/src/main/java/com/example/onomatopoeiaback/service/VisitService.java b/src/main/java/com/example/onomatopoeiaback/service/VisitService.java index 8ec113c..e88b3b7 100644 --- a/src/main/java/com/example/onomatopoeiaback/service/VisitService.java +++ b/src/main/java/com/example/onomatopoeiaback/service/VisitService.java @@ -35,7 +35,7 @@ public class VisitService { public void register(String login, VisitDTO visitDTO) { - Employee employee = employeeRepository.getEmployeesByLogin(login); + Optional employeeOptional = employeeRepository.findByLogin(login); LocalDateTime localDateTime = LocalDateTime.now().truncatedTo(ChronoUnit.SECONDS); Optional qrCodeOptional = qrCodeRepository.findById(visitDTO.getQrCodeId()); @@ -43,11 +43,12 @@ public class VisitService { throw new BadRequestException(); } - if (employee == null) { + if (employeeOptional.isEmpty()) { throw new UnauthorizedException(); } QrCode qrCode = qrCodeOptional.get(); + Employee employee = employeeOptional.get(); Visit visit = new Visit(); visit.setQrCode(qrCode); visit.setVisitType(visitDTO.getVisitType()); @@ -59,8 +60,13 @@ public class VisitService { } public Page getVisits(String login, Integer page, Integer size) { - Employee employee = employeeRepository.getEmployeesByLogin(login); + Optional employeeOptional = employeeRepository.findByLogin(login); + + if (employeeOptional.isEmpty()) { + throw new UnauthorizedException(); + } + PageRequest pageable = PageRequest.of(page, size); - return visitRepository.findByEmployeeId(employee.getId(), pageable); + return visitRepository.findByEmployeeId(employeeOptional.get().getId(), pageable); } }