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

Spring security #10

Merged
merged 6 commits into from
Sep 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,12 @@
<scope>runtime</scope>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<version>3.3.3</version>
</dependency>

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
Expand Down
44 changes: 44 additions & 0 deletions src/main/java/project/bookstore/config/SecurityConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package project.bookstore.config;

import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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.configurers.AbstractHttpConfigurer;
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;

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

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

@Bean
public SecurityFilterChain getSecurityFilterChain(HttpSecurity http) throws Exception {
return http
.cors(AbstractHttpConfigurer::disable)
.csrf(AbstractHttpConfigurer::disable)
.authorizeHttpRequests(
auth -> auth
.requestMatchers(
"/auth/**",
"/swagger-ui/**",
"/v3/api-docs/**"
)
.permitAll()
.anyRequest()
.authenticated()
)
.userDetailsService(userDetailsService)
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ public class AuthenticationController {
)
public UserResponseDto register(@RequestBody @Valid UserRegistrationRequestDto requestDto)
throws RegistrationException {

return userService.register(requestDto);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import java.util.List;
import lombok.RequiredArgsConstructor;
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 Down Expand Up @@ -45,6 +46,7 @@ public BookDto getBookById(@PathVariable Long id) {
}

@PostMapping
@PreAuthorize("hasRole('ROLE_ADMIN')")
@Operation(
summary = "Create new book",
description = "Create new book and add to db")
Expand All @@ -53,6 +55,7 @@ public BookDto createBook(@RequestBody @Valid CreateBookRequestDto requestDto) {
}

@PutMapping("/{id}")
@PreAuthorize("hasRole('ROLE_ADMIN')")
@Operation(summary = "Update book's info", description = "Update book's info")
public BookDto updateBook(
@RequestBody @Valid CreateBookRequestDto requestDto,
Expand All @@ -62,6 +65,7 @@ public BookDto updateBook(
}

@DeleteMapping("/{id}")
@PreAuthorize("hasRole('ROLE_ADMIN')")
@Operation(summary = "Delete book", description = "Delete book from db")
public void deleteBook(@PathVariable Long id) {
bookService.deleteBookById(id);
Expand Down
31 changes: 31 additions & 0 deletions src/main/java/project/bookstore/model/Role.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package project.bookstore.model;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import lombok.Getter;
import lombok.Setter;
import org.springframework.security.core.GrantedAuthority;

@Getter
@Setter
@Entity
@Table(name = "roles")
public class Role implements GrantedAuthority {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(unique = true, nullable = false)
@Enumerated(EnumType.STRING)
private RoleName role;

@Override
public String getAuthority() {
return role.name();
}
}
6 changes: 6 additions & 0 deletions src/main/java/project/bookstore/model/RoleName.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package project.bookstore.model;

public enum RoleName {
ROLE_USER,
ROLE_ADMIN
}
54 changes: 48 additions & 6 deletions src/main/java/project/bookstore/model/User.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,30 +5,72 @@
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.JoinTable;
import jakarta.persistence.ManyToMany;
import jakarta.persistence.Table;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import lombok.Getter;
import lombok.Setter;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

@Entity
@Table(name = "users")
@Getter
@Setter
public class User {
public class User implements UserDetails {

Choose a reason for hiding this comment

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

It is better to override all methods with UserDetails. In particular, the isEnabled method will have its own implementation in your solution ;)

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@Column(unique = true, nullable = false)
private String email;

@Column(nullable = false)
private String password;

@Column(nullable = false)
private String firstName;

@Column(nullable = false)
private String lastName;

private String shippingAddress;
@ManyToMany
@JoinTable(
name = "users_roles",
joinColumns = @JoinColumn(name = "user_id"),
inverseJoinColumns = @JoinColumn(name = "role_id")
)
private Set<Role> roles = new HashSet<>();
@Column(nullable = false, columnDefinition = "TINYINT(1)")
private boolean isDeleted = false;

@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return roles;
}

@Override
public String getUsername() {
return email;
}

@Override
public boolean isAccountNonExpired() {
return true;
}

@Override
public boolean isAccountNonLocked() {
return true;
}

@Override
public boolean isEnabled() {
return true;
}

@Override
public boolean isCredentialsNonExpired() {
return true;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package project.bookstore.repository.role;

import org.springframework.data.jpa.repository.JpaRepository;
import project.bookstore.model.Role;
import project.bookstore.model.RoleName;

public interface RoleRepository extends JpaRepository<Role, Long> {
Role findByRole(RoleName name);
}
10 changes: 0 additions & 10 deletions src/main/java/project/bookstore/repository/user/UserRepo.java

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package project.bookstore.repository.user;

import java.util.Optional;
import org.springframework.data.jpa.repository.EntityGraph;
import org.springframework.data.jpa.repository.JpaRepository;
import project.bookstore.model.User;

public interface UserRepository extends JpaRepository<User, Long> {
boolean existsByEmail(String email);

@EntityGraph(attributePaths = "roles")
Optional<User> findByEmail(String email);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package project.bookstore.security;

import lombok.RequiredArgsConstructor;
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 project.bookstore.repository.user.UserRepository;

@Service
@RequiredArgsConstructor
public class CustomUserDetailsService implements UserDetailsService {
private final UserRepository userRepo;

@Override
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
return userRepo.findByEmail(email)
.orElseThrow(
() -> new UsernameNotFoundException("Can't find user by email: " + email));
}
}
16 changes: 13 additions & 3 deletions src/main/java/project/bookstore/service/impl/UserServiceImpl.java
Original file line number Diff line number Diff line change
@@ -1,27 +1,37 @@
package project.bookstore.service.impl;

import java.util.Set;
import lombok.RequiredArgsConstructor;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
import project.bookstore.dto.user.UserRegistrationRequestDto;
import project.bookstore.dto.user.UserResponseDto;
import project.bookstore.exception.RegistrationException;
import project.bookstore.mapper.UserMapper;
import project.bookstore.model.Role;
import project.bookstore.model.RoleName;
import project.bookstore.model.User;
import project.bookstore.repository.user.UserRepo;
import project.bookstore.repository.role.RoleRepository;
import project.bookstore.repository.user.UserRepository;
import project.bookstore.service.UserService;

@RequiredArgsConstructor
@Component
public class UserServiceImpl implements UserService {
private final UserMapper userMapper;
private final UserRepo userRepository;
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
private final RoleRepository roleRepository;

@Override
public UserResponseDto register(UserRegistrationRequestDto requestDto) {
if (userRepository.existsUserByEmail(requestDto.getEmail())) {
if (userRepository.existsByEmail(requestDto.getEmail())) {
throw new RegistrationException(requestDto.getEmail() + "allready exists!");
}
User user = userMapper.toModel(requestDto);
user.setPassword(passwordEncoder.encode(user.getPassword()));
Role userRole = roleRepository.findByRole(RoleName.ROLE_USER);
user.setRoles(Set.of(userRole));
userRepository.save(user);

return userMapper.toResponseDto(user);
Expand Down
2 changes: 2 additions & 0 deletions src/main/resources/application.properties
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,5 @@ spring.datasource.password=pass
spring.jpa.hibernate.ddl-auto=validate
spring.jpa.show-sql=true
spring.jpa.open-in-view=false

server.servlet.context-path=/api
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ databaseChangeLog:
author: vlad
changes:
- createTable:
- tableName: books
- columns:
tableName: books
columns:
- column:
name: id
type: bigint
Expand Down
21 changes: 21 additions & 0 deletions src/main/resources/db/changelog/changes/03-create-roles-table.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
databaseChangeLog:
- changeSet:
id: create-roles-table
author: vlad
changes:
- createTable:
tableName: roles
columns:
- column:
name: id
type: bigint
autoIncrement: true
constraints:
primaryKey: true
nullable: false
- column:
name: role
type: varchar(255)
constraints:
unique: true
nullable: false
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
databaseChangeLog:
- changeSet:
id: create-users-roles-table
author: vlad
changes:
- createTable:
tableName: users_roles
columns:
- column:
name: user_id
type: bigint
constraints:
primaryKey: true
nullable: false
foreignKeyName: fk_users_id
references: users(id)
- column:
name: role_id
type: bigint
constraints:
primaryKey: true
nullable: false
foreignKeyName: fk_roles_id
references: roles(id)
Loading
Loading