Added auth system, file upload system, file upload tests
This commit is contained in:
1 parent
2ba79f8058
commit
cb9790fdd6
13 files changed
+332
-6
No files matched your search
+4
-1
@@ -20,13 +20,16 @@ repositories {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
implementation 'org.springframework.data:spring-data-jpa'
|
||||||
implementation 'org.springframework.boot:spring-boot-starter-security'
|
implementation 'org.springframework.boot:spring-boot-starter-security'
|
||||||
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
|
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
|
||||||
implementation 'org.springframework.boot:spring-boot-starter-web'
|
implementation 'org.springframework.boot:spring-boot-starter-web'
|
||||||
implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity5'
|
implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity5'
|
||||||
|
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
|
||||||
compileOnly 'org.projectlombok:lombok'
|
compileOnly 'org.projectlombok:lombok'
|
||||||
developmentOnly 'org.springframework.boot:spring-boot-devtools'
|
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'
|
annotationProcessor 'org.projectlombok:lombok'
|
||||||
providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat'
|
providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat'
|
||||||
testImplementation 'org.springframework.boot:spring-boot-starter-test'
|
testImplementation 'org.springframework.boot:spring-boot-starter-test'
|
||||||
|
|||||||
@@ -1,4 +1,16 @@
|
|||||||
package site.easystartup.easystartupcore.config;
|
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 {
|
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);
|
||||||
|
}
|
||||||
@@ -1,14 +1,11 @@
|
|||||||
server.port=80
|
server.port=80
|
||||||
|
|
||||||
spring.main.banner-mode=off
|
spring.jpa.hibernate.ddl-auto=create
|
||||||
logging.level.org.springframework=ERROR
|
|
||||||
|
|
||||||
spring.jpa.hibernate.ddl-auto=none
|
|
||||||
|
|
||||||
spring.sql.init.mode=always
|
spring.sql.init.mode=always
|
||||||
|
|
||||||
spring.sql.init.platform=postgres
|
spring.sql.init.platform=postgres
|
||||||
spring.datasource.url=jdbc:postgresql://localhost:5432/testdb
|
spring.datasource.url=jdbc:postgresql://localhost:5432/testdb
|
||||||
spring.datasource.username=postgres
|
spring.datasource.username=postgres
|
||||||
spring.datasource.password=root
|
spring.datasource.password=root
|
||||||
|
|
||||||
spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true
|
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 18 KiB |
@@ -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());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Reference in new issue
Block a user