Added auth system, file upload system, file upload tests

This commit is contained in:
redtea committed 2022-08-29 00:05:38 +03:00
1 parent 2ba79f8058
commit cb9790fdd6
13 files changed
+332 -6

No files matched your search

+4 -1
View File
@@ -20,13 +20,16 @@ repositories {
}
dependencies {
implementation 'org.springframework.data:spring-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity5'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
compileOnly 'org.projectlombok:lombok'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
runtimeOnly 'mysql:mysql-connector-java'
runtimeOnly 'org.postgresql:postgresql'
implementation 'com.zaxxer:HikariCP:5.0.1'
annotationProcessor 'org.projectlombok:lombok'
providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
@@ -1,4 +1,16 @@
package site.easystartup.easystartupcore.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration
@ComponentScan(basePackages = {"site.easystartup.easystartupcore"})
public class AppConfig {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
@@ -0,0 +1,12 @@
package site.easystartup.easystartupcore.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class MvcConfig implements WebMvcConfigurer {
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/login").setViewName("login");
}
}
@@ -0,0 +1,45 @@
package site.easystartup.easystartupcore.config;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
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.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.password.PasswordEncoder;
import javax.sql.DataSource;
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor(onConstructor_ = @Autowired)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
private final DataSource dataSource;
private final PasswordEncoder passwordEncoder;
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/", "/home", "/registration").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.and()
.logout()
.permitAll();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.jdbcAuthentication()
.dataSource(dataSource)
.passwordEncoder(passwordEncoder)
.usersByUsernameQuery("SELECT `username`, `password`, `active` FROM usr WHERE `username` = ?;")
.authoritiesByUsernameQuery("SELECT u.username, ur.roles FROM usr u INNER JOIN user_role ur ON u.id = ur.user_id WHERE username = ?;");
}
}
@@ -0,0 +1,45 @@
package site.easystartup.easystartupcore.controller;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import site.easystartup.easystartupcore.domain.Role;
import site.easystartup.easystartupcore.domain.User;
import site.easystartup.easystartupcore.repos.UserRepo;
import java.util.Collections;
@Controller
@RequiredArgsConstructor(onConstructor_ = @Autowired)
public class RegistrationController {
private final PasswordEncoder passwordEncoder;
private final UserRepo userRepo;
@GetMapping("/registration")
public String registration() {
return "registration";
}
@PostMapping("/registration")
public String addUser(User user, Model model) {
User userFromDb = userRepo.findByUsername(user.getUsername());
if(userFromDb != null) {
model.addAttribute("message", "Пользователь уже существует!");
return "registration";
}
user.setActive(true);
user.setRoles(Collections.singleton(Role.USER));
user.setPassword(passwordEncoder.encode(user.getPassword()));
userRepo.save(user);
return "redirect:/login";
}
}
@@ -0,0 +1,5 @@
package site.easystartup.easystartupcore.domain;
public enum Role {
USER, PREMIUM, MODER, ADMIN;
}
@@ -0,0 +1,24 @@
package site.easystartup.easystartupcore.domain;
import lombok.*;
import javax.persistence.*;
import java.util.Set;
@Data
@Entity
@Table(name = "usr")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private String username;
private String password;
private boolean active;
@ElementCollection(targetClass = Role.class, fetch = FetchType.EAGER)
@CollectionTable(name = "user_role", joinColumns = @JoinColumn(name = "user_id"))
@Enumerated(EnumType.STRING)
private Set<Role> roles;
}
@@ -0,0 +1,8 @@
package site.easystartup.easystartupcore.repos;
import org.springframework.data.jpa.repository.JpaRepository;
import site.easystartup.easystartupcore.domain.User;
public interface UserRepo extends JpaRepository<User, Long> {
User findByUsername(String username);
}
+2 -5
View File
@@ -1,14 +1,11 @@
server.port=80
spring.main.banner-mode=off
logging.level.org.springframework=ERROR
spring.jpa.hibernate.ddl-auto=none
spring.jpa.hibernate.ddl-auto=create
spring.sql.init.mode=always
spring.sql.init.platform=postgres
spring.datasource.url=jdbc:postgresql://localhost:5432/testdb
spring.datasource.username=postgres
spring.datasource.password=root
spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true
Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

+65
View File
@@ -0,0 +1,65 @@
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org"
xmlns:sec="https://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
<head>
<meta charset="utf-8">
<title>Вход</title>
<style>
div.error {
color: #ff5967;
font-weight: bold;
}
div.logout {
color: #2c84ec;
font-weight: bold;
}
</style>
</head>
<body class="text-center">
<main>
<form th:action="@{/login}" method="post">
<h1>Пожалуйста, войдите</h1>
<div>
<input type="text" class="form-control" minlength="3" size="25" id="username" name="username" required placeholder="UserName">
<label for="username">Логин</label>
</div>
<div>
<input type="password" name="password" minlength="3" id="password" required placeholder="Password123">
<label for="password">Пароль</label>
</div>
<div>
<label>
<input type="checkbox" value="remember-me"> Запомнить меня
</label>
</div>
<button type="submit">Войти</button>
<div th:if="${param.error}" class="error">
Неправильный логин или пароль.
</div>
<div th:if="${param.logout}" class="logout">
Вы вышли из аккаунта.
</div>
<p>© 2022</p>
</form>
<a href="registration">Зарегистрироваться</a>
</main>
</body>
</html>
@@ -0,0 +1,42 @@
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org"
xmlns:sec="https://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
<head>
<title>Регистрация</title>
</head>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org"
xmlns:sec="https://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
<head>
<meta charset="utf-8">
</head>
<body class="text-center">
<main class="form-signin w-100 m-auto">
<form th:action="@{/registration}" method="post">
<h1>Пожалуйста, зарегистрируйтесь</h1>
<div>
<input minlength="3" size="25" name="username" id="username" required placeholder="UserName">
<label for="username">Логин</label>
</div>
<div>
<input type="password" name="password" minlength="3" id="password" required placeholder="Password123">
<label for="password">Пароль</label>
</div>
<button type="submit">Зарегистрироваться</button>
<div>
<p th:text="${message}"></p>
</div>
<p>© 2022</p>
</form>
</main>
</body>
</html>
@@ -0,0 +1,68 @@
package site.easystartup.easystartupcore;
import java.nio.file.Paths;
import java.util.stream.Stream;
import org.hamcrest.Matchers;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.test.web.servlet.MockMvc;
import site.easystartup.easystartupcore.storage.StorageFileNotFoundException;
import site.easystartup.easystartupcore.storage.StorageService;
import static org.mockito.BDDMockito.given;
import static org.mockito.BDDMockito.then;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.multipart;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.model;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@AutoConfigureMockMvc
@SpringBootTest
public class FileUploadTests {
@Autowired
private MockMvc mvc;
@MockBean
private StorageService storageService;
@Test
public void shouldListAllFiles() throws Exception {
given(this.storageService.loadAll())
.willReturn(Stream.of(Paths.get("first.txt"), Paths.get("second.txt")));
this.mvc.perform(get("/")).andExpect(status().isOk())
.andExpect(model().attribute("files",
Matchers.contains("http://localhost/files/first.txt",
"http://localhost/files/second.txt")));
}
@Test
public void shouldSaveUploadedFile() throws Exception {
MockMultipartFile multipartFile = new MockMultipartFile("file", "test.txt",
"text/plain", "Spring Framework".getBytes());
this.mvc.perform(multipart("/").file(multipartFile))
.andExpect(status().isFound())
.andExpect(header().string("Location", "/"));
then(this.storageService).should().save(multipartFile);
}
@SuppressWarnings("unchecked")
@Test
public void should404WhenMissingFile() throws Exception {
given(this.storageService.load("test.txt"))
.willThrow(StorageFileNotFoundException.class);
this.mvc.perform(get("/files/test.txt")).andExpect(status().isNotFound());
}
}