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 {
|
||||
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);
|
||||
}
|
||||
@@ -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 |
@@ -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