From 218b8a261f9c568b40110efd63197e5889a4c25c Mon Sep 17 00:00:00 2001 From: Brandon Autrey Date: Fri, 26 Jun 2020 20:11:19 +0300 Subject: [PATCH 1/4] enable security --- backend/pom.xml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/backend/pom.xml b/backend/pom.xml index 382cfa7..a5bc386 100644 --- a/backend/pom.xml +++ b/backend/pom.xml @@ -91,16 +91,16 @@ - - - - + + org.springframework.boot + spring-boot-starter-security + - - - - - + + org.springframework.security + spring-security-test + test + From 88abeeb3a95947b68b4e419caab468fdf2fad2a9 Mon Sep 17 00:00:00 2001 From: Brandon Autrey Date: Mon, 29 Jun 2020 23:39:52 +0300 Subject: [PATCH 2/4] format java --- .../java/com/tartu/library/book/domain/model/BorrowLog.java | 1 + .../library/person/domain/repository/PersonRepository.java | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/backend/src/main/java/com/tartu/library/book/domain/model/BorrowLog.java b/backend/src/main/java/com/tartu/library/book/domain/model/BorrowLog.java index 0b1619e..0b1d44d 100644 --- a/backend/src/main/java/com/tartu/library/book/domain/model/BorrowLog.java +++ b/backend/src/main/java/com/tartu/library/book/domain/model/BorrowLog.java @@ -18,6 +18,7 @@ public class BorrowLog { @OneToOne Person borrower; BookStatus status; @CreationTimestamp private LocalDateTime createDateTime; + @Id @GeneratedValue(strategy = GenerationType.AUTO) private UUID id; diff --git a/backend/src/main/java/com/tartu/library/person/domain/repository/PersonRepository.java b/backend/src/main/java/com/tartu/library/person/domain/repository/PersonRepository.java index a5b6757..1625af9 100644 --- a/backend/src/main/java/com/tartu/library/person/domain/repository/PersonRepository.java +++ b/backend/src/main/java/com/tartu/library/person/domain/repository/PersonRepository.java @@ -10,7 +10,7 @@ @Repository public interface PersonRepository extends JpaRepository { @Query( - "select case when count(p)> 0 then true else false end from Person p where p.name like %?1%") + "select case when count(p)> 0 then true else false end from AuthUser p where p.name like %?1%") boolean existsByName(String name); @Query("select p from Person p where p.name like %?1%") From 6801612bb678d3c061393d745007ca29cd58de61 Mon Sep 17 00:00:00 2001 From: Brandon Autrey Date: Tue, 30 Jun 2020 00:16:31 +0300 Subject: [PATCH 3/4] auth user --- .../library/auth/domain/model/AuthUser.java | 56 +++++++++++++++++++ .../tartu/library/auth/domain/model/Role.java | 35 ++++++++++++ .../domain/repository/AuthUserRepository.java | 8 +++ .../domain/repository/RoleRepository.java | 6 ++ .../library/auth/rest/UserController.java | 20 +++++++ .../auth/security/SecurityConfigurator.java | 54 ++++++++++++++++++ .../library/auth/service/UserService.java | 35 ++++++++++++ .../domain/repository/PersonRepository.java | 2 +- 8 files changed, 215 insertions(+), 1 deletion(-) create mode 100644 backend/src/main/java/com/tartu/library/auth/domain/model/AuthUser.java create mode 100644 backend/src/main/java/com/tartu/library/auth/domain/model/Role.java create mode 100644 backend/src/main/java/com/tartu/library/auth/domain/repository/AuthUserRepository.java create mode 100644 backend/src/main/java/com/tartu/library/auth/domain/repository/RoleRepository.java create mode 100644 backend/src/main/java/com/tartu/library/auth/rest/UserController.java create mode 100644 backend/src/main/java/com/tartu/library/auth/security/SecurityConfigurator.java create mode 100644 backend/src/main/java/com/tartu/library/auth/service/UserService.java diff --git a/backend/src/main/java/com/tartu/library/auth/domain/model/AuthUser.java b/backend/src/main/java/com/tartu/library/auth/domain/model/AuthUser.java new file mode 100644 index 0000000..d3ef464 --- /dev/null +++ b/backend/src/main/java/com/tartu/library/auth/domain/model/AuthUser.java @@ -0,0 +1,56 @@ +package com.tartu.library.auth.domain.model; + +import com.tartu.library.person.domain.model.Person; +import lombok.AccessLevel; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; + +import javax.persistence.*; +import java.util.Collection; +import java.util.Set; +import java.util.UUID; + +@Entity +@Data +@NoArgsConstructor(force = true, access = AccessLevel.PROTECTED) +public class AuthUser implements UserDetails { + String username; + String password; + @OneToOne Person person; + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private UUID id; + + @Transient private String passwordConfirm; + + @ManyToMany(fetch = FetchType.EAGER) + private Set roles; + + @Override + public Collection getAuthorities() { + return roles; + } + + @Override + public boolean isAccountNonExpired() { + return true; + } + + @Override + public boolean isAccountNonLocked() { + return true; + } + + @Override + public boolean isCredentialsNonExpired() { + return true; + } + + @Override + public boolean isEnabled() { + return true; + } +} diff --git a/backend/src/main/java/com/tartu/library/auth/domain/model/Role.java b/backend/src/main/java/com/tartu/library/auth/domain/model/Role.java new file mode 100644 index 0000000..e37df60 --- /dev/null +++ b/backend/src/main/java/com/tartu/library/auth/domain/model/Role.java @@ -0,0 +1,35 @@ +package com.tartu.library.auth.domain.model; + +import lombok.NoArgsConstructor; +import org.springframework.security.core.GrantedAuthority; + +import javax.persistence.*; +import java.util.Set; + +@Entity +@NoArgsConstructor +public class Role implements GrantedAuthority { + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Id + Long id; + + private String name; + + @Transient + @ManyToMany(mappedBy = "roles") + private Set people; + + public Role(Long id) { + this.id = id; + } + + public Role(Long id, String name) { + this.id = id; + this.name = name; + } + + @Override + public String getAuthority() { + return name; + } +} diff --git a/backend/src/main/java/com/tartu/library/auth/domain/repository/AuthUserRepository.java b/backend/src/main/java/com/tartu/library/auth/domain/repository/AuthUserRepository.java new file mode 100644 index 0000000..897276e --- /dev/null +++ b/backend/src/main/java/com/tartu/library/auth/domain/repository/AuthUserRepository.java @@ -0,0 +1,8 @@ +package com.tartu.library.auth.domain.repository; + +import com.tartu.library.auth.domain.model.AuthUser; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface AuthUserRepository extends JpaRepository { + AuthUser findByUsername(String username); +} diff --git a/backend/src/main/java/com/tartu/library/auth/domain/repository/RoleRepository.java b/backend/src/main/java/com/tartu/library/auth/domain/repository/RoleRepository.java new file mode 100644 index 0000000..1337c7a --- /dev/null +++ b/backend/src/main/java/com/tartu/library/auth/domain/repository/RoleRepository.java @@ -0,0 +1,6 @@ +package com.tartu.library.auth.domain.repository; + +import com.tartu.library.auth.domain.model.Role; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface RoleRepository extends JpaRepository {} diff --git a/backend/src/main/java/com/tartu/library/auth/rest/UserController.java b/backend/src/main/java/com/tartu/library/auth/rest/UserController.java new file mode 100644 index 0000000..905b3e3 --- /dev/null +++ b/backend/src/main/java/com/tartu/library/auth/rest/UserController.java @@ -0,0 +1,20 @@ +package com.tartu.library.auth.rest; + +import com.tartu.library.auth.domain.model.AuthUser; +import com.tartu.library.auth.service.UserService; +import org.springframework.beans.factory.annotation.Autowired; +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 +@RequestMapping("/api/users") +public class UserController { + @Autowired UserService userService; + + @PostMapping() + public void createUser(@RequestBody AuthUser authUser) { + userService.save(authUser); + } +} diff --git a/backend/src/main/java/com/tartu/library/auth/security/SecurityConfigurator.java b/backend/src/main/java/com/tartu/library/auth/security/SecurityConfigurator.java new file mode 100644 index 0000000..1cb38a6 --- /dev/null +++ b/backend/src/main/java/com/tartu/library/auth/security/SecurityConfigurator.java @@ -0,0 +1,54 @@ +package com.tartu.library.auth.security; + +import com.tartu.library.auth.service.UserService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpMethod; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; + +@Configuration +@EnableGlobalMethodSecurity(securedEnabled = true) +public class SecurityConfigurator extends WebSecurityConfigurerAdapter { + + @Qualifier("userService") + @Autowired + UserService userService; + + @Bean + public BCryptPasswordEncoder bCryptPasswordEncoder() { + return new BCryptPasswordEncoder(); + } + + @Override + protected void configure(HttpSecurity http) throws Exception { + http.csrf() + .disable() + .cors() + .and() + .authorizeRequests() + .antMatchers("/h2-console/**") + .permitAll() + .antMatchers("/api/users") + .permitAll() + .antMatchers("/api/authenticate") + .permitAll() + .antMatchers(HttpMethod.OPTIONS, "/api/**") + .permitAll() + .antMatchers("/api/**") + .authenticated() + .and() + .httpBasic(); + http.headers().frameOptions().disable(); + } + + @Autowired + public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { + auth.userDetailsService(userService).passwordEncoder(bCryptPasswordEncoder()); + } +} diff --git a/backend/src/main/java/com/tartu/library/auth/service/UserService.java b/backend/src/main/java/com/tartu/library/auth/service/UserService.java new file mode 100644 index 0000000..8ce5398 --- /dev/null +++ b/backend/src/main/java/com/tartu/library/auth/service/UserService.java @@ -0,0 +1,35 @@ +package com.tartu.library.auth.service; + +import com.tartu.library.auth.domain.model.AuthUser; +import com.tartu.library.auth.domain.repository.AuthUserRepository; +import com.tartu.library.auth.domain.repository.RoleRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.HashSet; + +@Service +public class UserService implements UserDetailsService { + @Autowired BCryptPasswordEncoder bCryptPasswordEncoder; + @Autowired RoleRepository roleRepository; + @Autowired AuthUserRepository authUserRepository; + + public void save(AuthUser authUser) { + authUser.setPassword(bCryptPasswordEncoder.encode(authUser.getPassword())); + authUser.setRoles(new HashSet<>(roleRepository.findAll())); + authUserRepository.save(authUser); + } + + @Override + @Transactional(readOnly = true) + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + AuthUser authUser = authUserRepository.findByUsername(username); + if (authUser == null) throw new UsernameNotFoundException(username); + return authUser; + } +} diff --git a/backend/src/main/java/com/tartu/library/person/domain/repository/PersonRepository.java b/backend/src/main/java/com/tartu/library/person/domain/repository/PersonRepository.java index 1625af9..a5b6757 100644 --- a/backend/src/main/java/com/tartu/library/person/domain/repository/PersonRepository.java +++ b/backend/src/main/java/com/tartu/library/person/domain/repository/PersonRepository.java @@ -10,7 +10,7 @@ @Repository public interface PersonRepository extends JpaRepository { @Query( - "select case when count(p)> 0 then true else false end from AuthUser p where p.name like %?1%") + "select case when count(p)> 0 then true else false end from Person p where p.name like %?1%") boolean existsByName(String name); @Query("select p from Person p where p.name like %?1%") From f92a462606999e981e2f1e405660e16f029a1baa Mon Sep 17 00:00:00 2001 From: Brandon Autrey Date: Tue, 30 Jun 2020 16:53:15 +0300 Subject: [PATCH 4/4] authentication --- .../library/HypermediaConfiguration.java | 5 +++- .../com/tartu/library/LibraryApplication.java | 3 ++- .../library/auth/domain/model/AuthUser.java | 1 + .../auth/rest/AuthenticationController.java | 25 +++++++++++++++++++ .../auth/security/SecurityConfigurator.java | 18 +++++-------- .../book/rest/BookEntryRestController.java | 2 ++ .../book/rest/BookItemRestController.java | 5 ++++ backend/src/main/resources/data.sql | 10 ++++++++ 8 files changed, 55 insertions(+), 14 deletions(-) create mode 100644 backend/src/main/java/com/tartu/library/auth/rest/AuthenticationController.java create mode 100644 backend/src/main/resources/data.sql diff --git a/backend/src/main/java/com/tartu/library/HypermediaConfiguration.java b/backend/src/main/java/com/tartu/library/HypermediaConfiguration.java index 002909c..d71b522 100644 --- a/backend/src/main/java/com/tartu/library/HypermediaConfiguration.java +++ b/backend/src/main/java/com/tartu/library/HypermediaConfiguration.java @@ -27,7 +27,10 @@ public WebMvcConfigurer corsConfigurer() { return new WebMvcConfigurer() { @Override public void addCorsMappings(CorsRegistry registry) { - registry.addMapping("/**").allowedOrigins("http://localhost:8080"); + registry + .addMapping("/**") + .allowedOrigins("http://localhost:9000") + .allowedMethods("OPTIONS", "PUT", "DELETE", "GET", "POST", "PATCH"); } }; } diff --git a/backend/src/main/java/com/tartu/library/LibraryApplication.java b/backend/src/main/java/com/tartu/library/LibraryApplication.java index 268fb3c..86718e9 100644 --- a/backend/src/main/java/com/tartu/library/LibraryApplication.java +++ b/backend/src/main/java/com/tartu/library/LibraryApplication.java @@ -2,11 +2,12 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.ConfigurableApplicationContext; @SpringBootApplication public class LibraryApplication { public static void main(String[] args) { - SpringApplication.run(LibraryApplication.class, args); + ConfigurableApplicationContext ctx = SpringApplication.run(LibraryApplication.class, args); } } diff --git a/backend/src/main/java/com/tartu/library/auth/domain/model/AuthUser.java b/backend/src/main/java/com/tartu/library/auth/domain/model/AuthUser.java index d3ef464..bc8aa89 100644 --- a/backend/src/main/java/com/tartu/library/auth/domain/model/AuthUser.java +++ b/backend/src/main/java/com/tartu/library/auth/domain/model/AuthUser.java @@ -16,6 +16,7 @@ @Data @NoArgsConstructor(force = true, access = AccessLevel.PROTECTED) public class AuthUser implements UserDetails { + /** User is reserved word in Postgres */ String username; String password; @OneToOne Person person; diff --git a/backend/src/main/java/com/tartu/library/auth/rest/AuthenticationController.java b/backend/src/main/java/com/tartu/library/auth/rest/AuthenticationController.java new file mode 100644 index 0000000..07ab1db --- /dev/null +++ b/backend/src/main/java/com/tartu/library/auth/rest/AuthenticationController.java @@ -0,0 +1,25 @@ +package com.tartu.library.auth.rest; + +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.ArrayList; +import java.util.List; + +@RestController +public class AuthenticationController { + @GetMapping("/api/authenticate") + public List authenticate() { + Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal(); + List roles = new ArrayList<>(); + if (principal instanceof UserDetails) { + UserDetails details = (UserDetails) principal; + for (GrantedAuthority authority : details.getAuthorities()) + roles.add(authority.getAuthority()); + } + return roles; + } +} diff --git a/backend/src/main/java/com/tartu/library/auth/security/SecurityConfigurator.java b/backend/src/main/java/com/tartu/library/auth/security/SecurityConfigurator.java index 1cb38a6..2b64baa 100644 --- a/backend/src/main/java/com/tartu/library/auth/security/SecurityConfigurator.java +++ b/backend/src/main/java/com/tartu/library/auth/security/SecurityConfigurator.java @@ -32,18 +32,12 @@ protected void configure(HttpSecurity http) throws Exception { .cors() .and() .authorizeRequests() - .antMatchers("/h2-console/**") - .permitAll() - .antMatchers("/api/users") - .permitAll() - .antMatchers("/api/authenticate") - .permitAll() - .antMatchers(HttpMethod.OPTIONS, "/api/**") - .permitAll() - .antMatchers("/api/**") - .authenticated() - .and() - .httpBasic(); + .antMatchers("/h2-console/**").permitAll() + .antMatchers("/api/users").permitAll() + .antMatchers("/api/authenticate").permitAll() + .antMatchers(HttpMethod.OPTIONS, "/api/**").permitAll() + .antMatchers("/api/**").authenticated() + .and().httpBasic(); http.headers().frameOptions().disable(); } diff --git a/backend/src/main/java/com/tartu/library/book/rest/BookEntryRestController.java b/backend/src/main/java/com/tartu/library/book/rest/BookEntryRestController.java index d2fae40..8073a66 100644 --- a/backend/src/main/java/com/tartu/library/book/rest/BookEntryRestController.java +++ b/backend/src/main/java/com/tartu/library/book/rest/BookEntryRestController.java @@ -9,6 +9,7 @@ import org.springframework.hateoas.CollectionModel; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.security.access.annotation.Secured; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; @@ -40,6 +41,7 @@ public BookEntryDTO retrieveBook(@PathVariable UUID uuid) { } @PatchMapping("{uuid}") + @Secured({"ADMIN"}) public ResponseEntity modifyBook(@PathVariable UUID uuid) { logger.info(String.format("Modifying Book Entry (%s)", uuid.toString())); return new ResponseEntity<>(HttpStatus.BAD_REQUEST); diff --git a/backend/src/main/java/com/tartu/library/book/rest/BookItemRestController.java b/backend/src/main/java/com/tartu/library/book/rest/BookItemRestController.java index d9e4651..dbc2769 100644 --- a/backend/src/main/java/com/tartu/library/book/rest/BookItemRestController.java +++ b/backend/src/main/java/com/tartu/library/book/rest/BookItemRestController.java @@ -10,6 +10,7 @@ import org.springframework.hateoas.CollectionModel; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.security.access.annotation.Secured; import org.springframework.web.bind.annotation.*; import java.util.UUID; @@ -35,12 +36,14 @@ public BookItemDTO retrieveBookItem(@PathVariable UUID uuid) { } @DeleteMapping("{uuid}") + @Secured({"ADMIN"}) public ResponseEntity deleteBookItem(@PathVariable UUID uuid) { logger.info(String.format("Deleting Book Item (%s)", uuid.toString())); return new ResponseEntity<>(HttpStatus.BAD_REQUEST); } @PatchMapping("{uuid}/borrow") + @Secured({"AUTH_USER"}) public BookItemDTO borrowBook(@PathVariable UUID uuid, @RequestParam UUID person_uuid) throws InvalidBookStatusException { logger.info(String.format("Borrowing Book Item (%s)", uuid.toString())); @@ -48,12 +51,14 @@ public BookItemDTO borrowBook(@PathVariable UUID uuid, @RequestParam UUID person } @PatchMapping("{uuid}/return") + @Secured({"AUTH_USER"}) public BookItemDTO returnBook(@PathVariable UUID uuid) throws InvalidBookStatusException { logger.info(String.format("Returning Book Item (%s)", uuid.toString())); return bookService.returnBook(uuid); } @GetMapping("{uuid}/logs") + @Secured({"AUTH_USER"}) public CollectionModel retrieveBorrowLogs(@PathVariable UUID uuid) { logger.info(String.format("Retrieving Borrow Logs from Book Item (%s)", uuid.toString())); return bookService.retrieveBorrowLogsByBookItem(uuid); diff --git a/backend/src/main/resources/data.sql b/backend/src/main/resources/data.sql new file mode 100644 index 0000000..9093ee2 --- /dev/null +++ b/backend/src/main/resources/data.sql @@ -0,0 +1,10 @@ +insert into person (id, name) values (1, 'Admin'); + +insert into role (id, name) values (1, 'ADMIN'); +insert into role (id, name) values (2, 'AUTH_USER'); + +insert into auth_user (id, username, password, person_id) values (1, 'admin', '$2a$10$/BokLf7JuxdOZZK5hEUzauPOUnfyiWZ.P3SgnsBaE14FD.kjKd/U2', 1); +insert into auth_user (id, username, password) values (2, 'user', '$2a$10$vIQpONjeg31v..Dat4L6BO.u.hliQ5e0NrJQU.JMWhA/3R.ZRmZl2'); + +insert into auth_user_roles (auth_user_id, roles_id) values (1, 1); +insert into auth_user_roles (auth_user_id, roles_id) values (2, 2); \ No newline at end of file