Skip to content

Commit

Permalink
Merge pull request #41 from saneci/feature/authorization
Browse files Browse the repository at this point in the history
Authorization and authentication
  • Loading branch information
saneci authored Mar 12, 2024
2 parents 8c18a59 + d4ceaf4 commit 8ccad63
Show file tree
Hide file tree
Showing 38 changed files with 980 additions and 39 deletions.
18 changes: 16 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.2</version>
<version>3.2.3</version>
<relativePath/>
</parent>

<groupId>ru.saneci</groupId>
<artifactId>book-library</artifactId>
<version>1.0.2</version>
<version>1.1.0</version>
<name>SB Library</name>
<description>Saneci Book Library is a digital accounting of books in the library</description>

Expand All @@ -37,12 +37,26 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity6</artifactId>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>


<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package ru.saneci.booklibrary.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
@EnableWebSecurity
public class SecurityConfiguration {

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http
.authorizeHttpRequests(authorisationCustomizer -> authorisationCustomizer
.requestMatchers("/admin/**").hasAnyRole("ADMIN", "MANAGER")
.requestMatchers("/auth/**", "/error").permitAll()
.anyRequest().hasAnyRole("USER", "ADMIN", "MANAGER")
)
.formLogin(loginCustomizer -> loginCustomizer
.loginPage("/auth/login")
.loginProcessingUrl("/auth/login/process")
.defaultSuccessUrl("/", true)
.failureUrl("/auth/login?error")
)
.logout(logoutCustomizer -> logoutCustomizer
.logoutUrl("/logout")
.logoutSuccessUrl("/auth/login")
)
.build();
}

@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package ru.saneci.booklibrary.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
@RequestMapping("/admin")
public class AdminController {

@GetMapping("/dashboard")
public String getDashboard() {
return "admin/dashboard";
}
}
59 changes: 59 additions & 0 deletions src/main/java/ru/saneci/booklibrary/controller/AuthController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package ru.saneci.booklibrary.controller;

import jakarta.validation.Valid;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import ru.saneci.booklibrary.domain.Person;
import ru.saneci.booklibrary.domain.Role;
import ru.saneci.booklibrary.service.PersonService;
import ru.saneci.booklibrary.util.BindingResultLogger;
import ru.saneci.booklibrary.util.PersonValidator;

@Controller
@RequestMapping("/auth")
public class AuthController {

private final Logger log = LoggerFactory.getLogger(AuthController.class);
private final BindingResultLogger brLogger = BindingResultLogger.getLogger(AuthController.class);
private final PersonValidator personValidator;
private final PersonService personService;

@Autowired
public AuthController(PersonValidator personValidator, PersonService personService) {
this.personValidator = personValidator;
this.personService = personService;
}

@GetMapping("/login")
public String getLoginPage() {
return "auth/login";
}

@GetMapping("/registration")
public String getRegistrationPage(@ModelAttribute("person") Person person) {
return "auth/registration";
}

@PostMapping("/registration")
public String performRegistration(@ModelAttribute("person") @Valid Person person, BindingResult bindingResult) {
log.debug("performRegistration: start processing");
personValidator.validate(person, bindingResult);
if (bindingResult.hasErrors()) {
brLogger.warn("performRegistration", bindingResult);
return "auth/registration";
}
// TODO: remove setRole after https://github.com/users/saneci/projects/3/views/1?pane=issue&itemId=56174894
person.setRole(Role.ROLE_USER);
personService.save(person);
log.debug("performRegistration: finish processing");

return "redirect:/auth/login";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import ru.saneci.booklibrary.domain.Person;
import ru.saneci.booklibrary.domain.Role;
import ru.saneci.booklibrary.service.PersonService;
import ru.saneci.booklibrary.util.BindingResultLogger;

Expand Down Expand Up @@ -89,6 +90,7 @@ public String createNewReader(@ModelAttribute("person") @Valid Person person, Bi
brLogger.warn("createNewReader", bindingResult);
return NEW_PEOPLE_VIEW;
}
crutch(person);
personService.save(person);
log.debug("createNewReader: finish processing");

Expand All @@ -103,6 +105,7 @@ public String updateReader(@ModelAttribute("person") @Valid Person person,
brLogger.warn("updateReader", bindingResult);
return NEW_PEOPLE_VIEW;
}
crutch(person);
personService.update(person);
log.debug("updateReader: finish processing");

Expand All @@ -117,4 +120,11 @@ public String deleteReader(@PathVariable("id") Long id) {

return REDIRECT_TO_PEOPLE;
}

// TODO: remove after https://github.com/users/saneci/projects/3/views/1?pane=issue&itemId=56174894
private void crutch(Person person) {
person.setRole(Role.ROLE_READER);
person.setUsername("");
person.setPassword("");
}
}
61 changes: 57 additions & 4 deletions src/main/java/ru/saneci/booklibrary/domain/Person.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,38 +2,59 @@

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.OneToMany;
import jakarta.persistence.Table;
import jakarta.persistence.Transient;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Positive;
import org.hibernate.annotations.Cascade;
import org.hibernate.annotations.CascadeType;
import org.hibernate.validator.constraints.Length;

import java.util.List;

@Entity
@Table(name ="person")
@Table(name = "person")
public class Person {

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

@Pattern(regexp = "(([A-Я][а-я]+ [А-Я][а-я]+ [А-Я][а-я]+)|([A-Z]\\w+ [A-Z]\\w+( [A-Z]\\w+)?))",
message = "ФИО должно быть в следующем формате: Имя Фамилия Отчество")
@Column(name = "name")
private String name;

@Positive
@Min(value = 1900, message = "Год рождения должен быть больше чем 1899")
@Column(name = "birthday_year")
private int birthdayYear;

@Column(name = "username")
// TODO: uncomment after https://github.com/users/saneci/projects/3/views/1?pane=issue&itemId=56174894
// @NotBlank(message = "Логин не должен быть пустым")
@Pattern(regexp = "[^а-яА-Я]*", message = "Логин не должен содержать кириллицу")
private String username;

@Column(name = "password")
@Length(min = 6, message = "Пароль должен содержать минимум 6 символов")
@Pattern(regexp = "[^а-яА-Я]*", message = "Пароль не должен содержать кириллицу")
private String password;

@Transient
private String passwordRepeat;

@Column(name = "role")
@Enumerated(EnumType.STRING)
private Role role;

@OneToMany(mappedBy = "person")
@Cascade(CascadeType.PERSIST)
private List<Book> bookList;
Expand Down Expand Up @@ -70,6 +91,38 @@ public void setBirthdayYear(int birthdayYear) {
this.birthdayYear = birthdayYear;
}

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;
}

public String getPasswordRepeat() {
return passwordRepeat;
}

public void setPasswordRepeat(String passwordRepeat) {
this.passwordRepeat = passwordRepeat;
}

public Role getRole() {
return role;
}

public void setRole(Role role) {
this.role = role;
}

public List<Book> getBookList() {
return bookList;
}
Expand Down
7 changes: 7 additions & 0 deletions src/main/java/ru/saneci/booklibrary/domain/Role.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package ru.saneci.booklibrary.domain;

public enum Role {

// TODO: remove ROLE_READER after https://github.com/users/saneci/projects/3/views/1?pane=issue&itemId=56174894
ROLE_ADMIN, ROLE_USER, ROLE_MANAGER, ROLE_READER
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,6 @@ public interface PersonRepository extends JpaRepository<Person, Long> {

@Query("select p from Person p left join fetch p.bookList where p.id = ?1")
Optional<Person> findPersonWithBooksById(Long id);

Optional<Person> findPersonByUsername(String username);
}
57 changes: 57 additions & 0 deletions src/main/java/ru/saneci/booklibrary/security/PersonDetails.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package ru.saneci.booklibrary.security;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import ru.saneci.booklibrary.domain.Person;

import java.util.Collection;
import java.util.List;

public class PersonDetails implements UserDetails {

private final transient Person person;

public PersonDetails(Person person) {
this.person = person;
}

@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return List.of(new SimpleGrantedAuthority(person.getRole().name()));
}

@Override
public String getPassword() {
return this.person.getPassword();
}

@Override
public String getUsername() {
return this.person.getUsername();
}

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

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

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

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

public Person getPerson() {
return person;
}
}
Loading

0 comments on commit 8ccad63

Please sign in to comment.