basic authorization and authentication configurations

This commit is contained in:
Maksim Skobaro 2024-09-29 05:40:23 +03:00 committed by Velixeor
parent c387436681
commit a05e250888
12 changed files with 274 additions and 28 deletions

View File

@ -84,6 +84,10 @@
<artifactId>postgresql</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
<build>

View File

@ -1,19 +1,98 @@
package ru.tubryansk.tdms.config;
import jakarta.servlet.http.HttpSessionEvent;
import jakarta.servlet.http.HttpSessionListener;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.annotation.web.configurers.AuthorizeHttpRequestsConfigurer;
import org.springframework.security.config.annotation.web.configurers.SessionManagementConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
import static org.springframework.security.web.context.HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY;
@Configuration
public class SecurityConfiguration {
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
return httpSecurity
.authorizeHttpRequests(req -> req.requestMatchers("/**").permitAll())
.csrf(AbstractHttpConfigurer::disable).build();
.authorizeHttpRequests(this::configureHttpAuthorization)
.csrf(AbstractHttpConfigurer::disable)
.cors(AbstractHttpConfigurer::disable)
.authenticationManager(authenticationManager())
.sessionManagement(this::configureSessionManagement)
.build();
}
private void configureHttpAuthorization(AuthorizeHttpRequestsConfigurer<HttpSecurity>.AuthorizationManagerRequestMatcherRegistry httpAuthorization) {
/* API ROUTES */
httpAuthorization.requestMatchers("/api/diploma-topic/**").permitAll();
httpAuthorization.requestMatchers("/api/**").denyAll();
/* STATIC ROUTES */
httpAuthorization.requestMatchers("/**").permitAll();
/* OTHER */
httpAuthorization.anyRequest().denyAll();
}
@Bean
public AuthenticationManager authenticationManager() {
return new ProviderManager(authenticationProvider());
}
private AuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider(passwordEncoder());
provider.setUserDetailsService(userDetailsService());
return provider;
}
private UserDetailsService userDetailsService() {
return new InMemoryUserDetailsManager(User.builder()
.username("admin")
.password("{noop}admin")
.authorities("ROLE_STUDENT", "ROLE_TEACHER", "ROLE_CURATOR")
.build());
}
private PasswordEncoder passwordEncoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
@Bean
// todo: remove when login/logout is implemented
public HttpSessionListener autoAuthenticateUnderAdmin() {
return new HttpSessionListener() {
@Override
public void sessionCreated(HttpSessionEvent se) {
SecurityContext context = SecurityContextHolder.createEmptyContext();
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken("admin", "admin");
Authentication authenticated = authenticationManager().authenticate(authentication);
context.setAuthentication(authenticated);
SecurityContextHolder.setContext(context);
se.getSession().setAttribute(SPRING_SECURITY_CONTEXT_KEY, context);
}
};
}
// todo: remove when login/logout is implemented, since we do not need automatically created session with no authentication
private void configureSessionManagement(SessionManagementConfigurer<HttpSecurity> sessionManagement) {
sessionManagement.sessionCreationPolicy(SessionCreationPolicy.ALWAYS);
}
}

View File

@ -0,0 +1,33 @@
package ru.tubryansk.tdms.controller;
import jakarta.validation.constraints.PositiveOrZero;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import ru.tubryansk.tdms.dto.DiplomaTopicDTO;
import ru.tubryansk.tdms.service.DiplomaTopicService;
import java.util.List;
@RestController
@RequestMapping("/api/diploma-topic/")
@Validated
public class DiplomaTopicRestController {
@Autowired
private DiplomaTopicService diplomaTopicService;
@GetMapping("/get-all")
public List<DiplomaTopicDTO> getAll() {
return diplomaTopicService.getAll();
}
@GetMapping("/get-by-id/{id:[\\-+]?\\d+}")
public DiplomaTopicDTO getById(@PathVariable @PositiveOrZero Integer id) {
return diplomaTopicService.getById(id);
}
}

View File

@ -1,16 +0,0 @@
package ru.tubryansk.tdms.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping("/")
public class TestController {
@GetMapping
public String root() {
return "index.html";
}
}

View File

@ -1,15 +1,16 @@
package ru.tubryansk.tdms.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.Builder;
import ru.tubryansk.tdms.entity.DiplomaTopic;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class DiplomaTopicDTO {
private Integer id;
private String name;
@Builder
public record DiplomaTopicDTO(Integer id, String name) {
public static DiplomaTopicDTO fromEntity(DiplomaTopic diplomaTopic) {
return DiplomaTopicDTO.builder()
.id(diplomaTopic.getId())
.name(diplomaTopic.getName())
.build();
}
}

View File

@ -0,0 +1,20 @@
package ru.tubryansk.tdms.dto;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
public record ErrorResponse(String message, ErrorCode errorCode) {
@RequiredArgsConstructor
@Getter
public enum ErrorCode {
BAD_REQUEST(HttpStatus.BAD_REQUEST),
VALIDATION_ERROR(HttpStatus.BAD_REQUEST),
INTERNAL_ERROR(HttpStatus.INTERNAL_SERVER_ERROR),
NOT_FOUND(HttpStatus.NOT_FOUND),
ACCESS_DENIED(HttpStatus.FORBIDDEN)
;
private final HttpStatus httpStatus;
}
}

View File

@ -0,0 +1,14 @@
package ru.tubryansk.tdms.exception;
import ru.tubryansk.tdms.dto.ErrorResponse;
public class AccessDeniedException extends BusinessException {
public AccessDeniedException() {
super("Access denied");
}
@Override
public ErrorResponse.ErrorCode getErrorCode() {
return ErrorResponse.ErrorCode.ACCESS_DENIED;
}
}

View File

@ -0,0 +1,13 @@
package ru.tubryansk.tdms.exception;
import ru.tubryansk.tdms.dto.ErrorResponse;
public class BusinessException extends RuntimeException {
public BusinessException(String message) {
super(message);
}
public ErrorResponse.ErrorCode getErrorCode() {
return ErrorResponse.ErrorCode.INTERNAL_ERROR;
}
}

View File

@ -0,0 +1,44 @@
package ru.tubryansk.tdms.exception;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.resource.NoResourceFoundException;
import ru.tubryansk.tdms.dto.ErrorResponse;
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ErrorResponse handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
// todo: make a better error message
return new ErrorResponse(e.getMessage(), ErrorResponse.ErrorCode.VALIDATION_ERROR);
}
@ExceptionHandler(BusinessException.class)
public ErrorResponse handleBusinessException(BusinessException e, HttpServletResponse response) {
response.setStatus(e.getErrorCode().getHttpStatus().value());
return new ErrorResponse(e.getMessage(), e.getErrorCode());
}
@ExceptionHandler(NoResourceFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public ErrorResponse handleNoResourceFoundException(NoResourceFoundException e) {
// todo: make error page
return new ErrorResponse(e.getMessage(), ErrorResponse.ErrorCode.NOT_FOUND);
}
@ExceptionHandler(Exception.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public ErrorResponse handleUnexpectedException(Exception e) {
// todo: make error page
log.error("Unexpected exception.", e);
return new ErrorResponse(e.getMessage(), ErrorResponse.ErrorCode.INTERNAL_ERROR);
}
}

View File

@ -0,0 +1,14 @@
package ru.tubryansk.tdms.exception;
import ru.tubryansk.tdms.dto.ErrorResponse;
public class NotFoundException extends BusinessException {
public NotFoundException(Class<?> entityClass, Integer id) {
super(entityClass.getSimpleName() + " with id " + id + " not found");
}
@Override
public ErrorResponse.ErrorCode getErrorCode() {
return ErrorResponse.ErrorCode.NOT_FOUND;
}
}

View File

@ -0,0 +1,9 @@
package ru.tubryansk.tdms.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import ru.tubryansk.tdms.entity.DiplomaTopic;
@Repository
public interface DiplomaTopicRepository extends JpaRepository<DiplomaTopic, Integer> {
}

View File

@ -0,0 +1,31 @@
package ru.tubryansk.tdms.service;
import jakarta.transaction.Transactional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import ru.tubryansk.tdms.entity.DiplomaTopic;
import ru.tubryansk.tdms.exception.NotFoundException;
import ru.tubryansk.tdms.repository.DiplomaTopicRepository;
import ru.tubryansk.tdms.dto.DiplomaTopicDTO;
import java.util.List;
import java.util.stream.Collectors;
@Service
@Transactional
public class DiplomaTopicService {
@Autowired
private DiplomaTopicRepository diplomaTopicRepository;
public List<DiplomaTopicDTO> getAll() {
return diplomaTopicRepository.findAll()
.stream()
.map(DiplomaTopicDTO::fromEntity)
.collect(Collectors.toList());
}
public DiplomaTopicDTO getById(Integer id) {
return DiplomaTopicDTO.fromEntity(diplomaTopicRepository.findById(id)
.orElseThrow(() -> new NotFoundException(DiplomaTopic.class, id)));
}
}