Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

added security component, added user to the model #6

Merged
merged 10 commits into from
Oct 2, 2023
47 changes: 44 additions & 3 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
<java.version>17</java.version>
<maven.checkstyle.plugin.configLocation>checkstyle.xml</maven.checkstyle.plugin.configLocation>
<org.mapstruct.version>1.5.5.Final</org.mapstruct.version>
<springdoc-openapi.version>2.1.0</springdoc-openapi.version>
<jjwt.version>0.11.5</jjwt.version>
</properties>
<dependencies>
<dependency>
Expand Down Expand Up @@ -76,7 +78,39 @@
<artifactId>hibernate-validator</artifactId>
<version>8.0.1.Final</version>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>${springdoc-openapi.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>${jjwt.version}</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>${jjwt.version}</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>${jjwt.version}</version>
</dependency>
<dependency>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
<version>3.3.0</version>
</dependency>

</dependencies>

Expand All @@ -89,7 +123,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
<version>3.1.1</version>
<version>3.3.0</version>
<executions>
<execution>
<phase>compile</phase>
Expand All @@ -100,7 +134,6 @@
</executions>
<configuration>
<configLocation>${maven.checkstyle.plugin.configLocation}</configLocation>
<encoding>UTF-8</encoding>
<consoleOutput>true</consoleOutput>
<failsOnError>true</failsOnError>
<linkXRef>false</linkXRef>
Expand Down Expand Up @@ -133,6 +166,14 @@
</annotationProcessorPaths>
</configuration>
</plugin>
<plugin>
<groupId>org.liquibase</groupId>
<artifactId>liquibase-maven-plugin</artifactId>
<version>${liquibase.version}</version>
<configuration>
<propertyFile>src/main/resources/liquibase.properties</propertyFile>
</configuration>
</plugin>
</plugins>
</build>

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package mate.academy.bookstore.annotation;

import jakarta.validation.Constraint;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Constraint(validatedBy = FieldsValueMatchValidator.class)
@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface FieldsValueMatch {
String message() default "Fields values don't match!";
String field();
String fieldMatch();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package mate.academy.bookstore.annotation;

import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
import org.springframework.beans.BeanWrapperImpl;

public class FieldsValueMatchValidator implements ConstraintValidator<FieldsValueMatch, Object> {
private String field;
private String fieldMatch;

@Override
public void initialize(FieldsValueMatch constraintAnnotation) {
this.field = constraintAnnotation.field();
this.fieldMatch = constraintAnnotation.fieldMatch();
}

@Override
public boolean isValid(Object value,
ConstraintValidatorContext constraintValidatorContext) {
Object fieldValue = new BeanWrapperImpl(value)
.getPropertyValue(field);
Object fieldMatchValue = new BeanWrapperImpl(value)
.getPropertyValue(fieldMatch);

if (fieldValue != null) {
return fieldValue.equals(fieldMatchValue);
} else {
return fieldMatchValue == null;
}
}
}
60 changes: 60 additions & 0 deletions src/main/java/mate/academy/bookstore/config/SecurityConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package mate.academy.bookstore.config;

import lombok.RequiredArgsConstructor;
import mate.academy.bookstore.security.JwtAuthenticationFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.Customizer;
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.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

@EnableWebSecurity
@EnableMethodSecurity
@Configuration
@RequiredArgsConstructor
public class SecurityConfig {
private final UserDetailsService userDetailsService;
private final JwtAuthenticationFilter jwtAuthenticationFilter;

@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http.cors(AbstractHttpConfigurer::disable)
.csrf(AbstractHttpConfigurer::disable)
.authorizeHttpRequests(
auth -> auth
.requestMatchers("/auth/**", "/error")
.permitAll()
.anyRequest()
.authenticated()
)
.httpBasic(Customizer.withDefaults())

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since you added JWT security httpBasic is no more needed actually

.sessionManagement(session
-> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.addFilterBefore(jwtAuthenticationFilter,
UsernamePasswordAuthenticationFilter.class)
.userDetailsService(userDetailsService)
.build();
}

@Bean
public AuthenticationManager authenticationManager(
AuthenticationConfiguration authenticationConfiguration
) throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package mate.academy.bookstore.controller;

import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import mate.academy.bookstore.dto.user.UserLoginRequestDto;
import mate.academy.bookstore.dto.user.UserLoginResponseDto;
import mate.academy.bookstore.dto.user.UserRegistrationRequest;
import mate.academy.bookstore.dto.user.UserResponseDto;
import mate.academy.bookstore.exception.RegistrationException;
import mate.academy.bookstore.security.AuthenticationService;
import mate.academy.bookstore.service.UserService;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequiredArgsConstructor
@RequestMapping("/auth")
public class AuthController {
private final UserService userService;
private final AuthenticationService authenticationService;

@PostMapping("/login")
public UserLoginResponseDto login(@RequestBody UserLoginRequestDto requestDto) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please, add the @Valid annotation)

return authenticationService.authenticate(requestDto);
}

@PostMapping("/register")
public UserResponseDto register(@RequestBody @Valid UserRegistrationRequest request)
throws RegistrationException {
return userService.register(request);

}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
package mate.academy.bookstore.controller;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import java.util.List;
import lombok.RequiredArgsConstructor;
import mate.academy.bookstore.dto.BookDto;
import mate.academy.bookstore.dto.CreateBookRequestDto;
import mate.academy.bookstore.service.BookService;
import org.springframework.data.domain.Pageable;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
Expand All @@ -15,34 +19,43 @@
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@Tag(name = "Book management", description = "Endpoints for managing books")
@RequiredArgsConstructor
@RestController
@RequestMapping(value = "/api/books")
public class BookController {
private final BookService bookService;

@GetMapping
public List<BookDto> getAll() {
return bookService.findAll();
@Operation(summary = "Get all books", description = "Get a list of all available books")
public List<BookDto> getAll(Pageable pageable) {
return bookService.findAll(pageable);
}

@GetMapping("/{id}")
@Operation(summary = "Get a book by id", description = "Get a book by id")
public BookDto getBookById(Long id) {
return bookService.findById(id);
}

@PreAuthorize("hasRole('ADMIN')")
@PostMapping
@Operation(summary = "Create a new book", description = "Create a new book")
public BookDto createBook(@RequestBody @Valid CreateBookRequestDto bookDto) {
return bookService.save(bookDto);
}

@PreAuthorize("hasRole('ADMIN')")
@PutMapping("/{id}")
@Operation(summary = "Update a book", description = "Update a book by id")
public BookDto updateBook(@PathVariable Long id,
@RequestBody @Valid CreateBookRequestDto requestDto) {
return bookService.update(id, requestDto);
}

@PreAuthorize("hasRole('ADMIN')")
@DeleteMapping("/{id}")
@Operation(summary = "Delete a book", description = "Delete a book by id")
public void deleteBook(@PathVariable Long id) {
bookService.delete(id);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package mate.academy.bookstore.dto.user;

import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.Size;
import lombok.Data;

@Data
public class UserLoginRequestDto {
@NotEmpty

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@notempty and @notblank are mostly useless here as you anyway validate the length of string in Size annotation. So you can replace @notempty and @notblank by @NotNull

@NotBlank
@Size(min = 8, max = 20)
@Email
private String email;
@NotEmpty
@NotBlank
@Size(min = 8, max = 20)
private String password;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package mate.academy.bookstore.dto.user;

public record UserLoginResponseDto(String token) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package mate.academy.bookstore.dto.user;

import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import lombok.Data;
import mate.academy.bookstore.annotation.FieldsValueMatch;

@Data
@FieldsValueMatch(
field = "password",
fieldMatch = "repeatPassword",
message = "Passwords do not match!"
)
public class UserRegistrationRequest {
@NotBlank
@Size(min = 4, max = 100)
private String email;
@NotBlank
@Size(min = 6, max = 100)
private String password;
@NotBlank
@Size(min = 6, max = 100)
private String repeatPassword;
@NotBlank
private String firstName;
@NotBlank
private String lastName;
private String shippingAddress;
}
12 changes: 12 additions & 0 deletions src/main/java/mate/academy/bookstore/dto/user/UserResponseDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package mate.academy.bookstore.dto.user;

import lombok.Data;

@Data
public class UserResponseDto {
private Long id;
private String email;
private String firstName;
private String lastName;
private String shippingAddress;
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package mate.academy.bookstore.exceptions;
package mate.academy.bookstore.exception;

import java.time.LocalDateTime;
import java.util.LinkedHashMap;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package mate.academy.bookstore.exceptions;
package mate.academy.bookstore.exception;

public class EntityNotFoundException extends RuntimeException {
public EntityNotFoundException(String message) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package mate.academy.bookstore.exception;

public class RegistrationException extends Exception {
public RegistrationException(String message) {
super(message);
}
}
14 changes: 14 additions & 0 deletions src/main/java/mate/academy/bookstore/mapper/UserMapper.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package mate.academy.bookstore.mapper;

import mate.academy.bookstore.config.MapperConfig;
import mate.academy.bookstore.dto.user.UserRegistrationRequest;
import mate.academy.bookstore.dto.user.UserResponseDto;
import mate.academy.bookstore.model.User;
import org.mapstruct.Mapper;

@Mapper(config = MapperConfig.class)
public interface UserMapper {
UserResponseDto toUserResponse(User user);

User toModel(UserRegistrationRequest request);
}
Loading
Loading