Начал 5-6 лабу, вроде пока все рабоатет, добавил роли
This commit is contained in:
parent
5453c9291b
commit
a4a6a74775
@ -19,10 +19,17 @@ dependencies {
|
|||||||
implementation 'org.springframework.boot:spring-boot-starter-web'
|
implementation 'org.springframework.boot:spring-boot-starter-web'
|
||||||
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.3.0'
|
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.3.0'
|
||||||
implementation 'org.modelmapper:modelmapper:3.2.0'
|
implementation 'org.modelmapper:modelmapper:3.2.0'
|
||||||
// implementation 'com.fasterxml.jackson.core:jackson-databind:2.12.5'
|
|
||||||
testImplementation 'org.springframework.boot:spring-boot-starter-test'
|
testImplementation 'org.springframework.boot:spring-boot-starter-test'
|
||||||
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
|
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
|
||||||
implementation 'com.h2database:h2:2.2.224'
|
implementation 'com.h2database:h2:2.2.224'
|
||||||
|
|
||||||
|
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
|
||||||
|
implementation 'nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect:3.3.0'
|
||||||
|
runtimeOnly 'org.webjars.npm:bootstrap:5.3.3'
|
||||||
|
runtimeOnly 'org.webjars.npm:bootstrap-icons:1.11.3'
|
||||||
|
|
||||||
|
implementation 'org.springframework.boot:spring-boot-starter-security'
|
||||||
|
implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity6'
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.named('test') {
|
tasks.named('test') {
|
||||||
|
@ -0,0 +1,18 @@
|
|||||||
|
package com.example.nekontakte.core.api;
|
||||||
|
|
||||||
|
import org.springframework.web.bind.annotation.ControllerAdvice;
|
||||||
|
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||||
|
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
|
@ControllerAdvice
|
||||||
|
public class GlobalController {
|
||||||
|
|
||||||
|
public GlobalController() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@ModelAttribute("servletPath")
|
||||||
|
String getRequestServletPath(HttpServletRequest request) {
|
||||||
|
return request.getServletPath();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,18 @@
|
|||||||
|
package com.example.nekontakte.core.api;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
import org.springframework.data.domain.Page;
|
||||||
|
|
||||||
|
public class PageAttributesMapper {
|
||||||
|
private PageAttributesMapper() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <E, D> Map<String, Object> toAttributes(Page<E> page, Function<E, D> mapper) {
|
||||||
|
return Map.of(
|
||||||
|
"items", page.getContent().stream().map(mapper::apply).toList(),
|
||||||
|
"currentPage", page.getNumber(),
|
||||||
|
"totalPages", page.getTotalPages());
|
||||||
|
}
|
||||||
|
}
|
@ -5,6 +5,17 @@ public class Constants {
|
|||||||
|
|
||||||
public static final String API_URL = "/api";
|
public static final String API_URL = "/api";
|
||||||
|
|
||||||
|
public static final int DEFUALT_PAGE_SIZE = 5;
|
||||||
|
|
||||||
|
public static final String REDIRECT_VIEW = "redirect:";
|
||||||
|
|
||||||
|
public static final String ADMIN_PREFIX = "/admin";
|
||||||
|
|
||||||
|
public static final String LOGIN_URL = "/login";
|
||||||
|
public static final String LOGOUT_URL = "/logout";
|
||||||
|
|
||||||
|
public static final String DEFAULT_PASSWORD = "123456";
|
||||||
|
|
||||||
private Constants() {
|
private Constants() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,23 @@
|
|||||||
package com.example.nekontakte.core.configurations;
|
package com.example.nekontakte.core.configurations;
|
||||||
|
|
||||||
import org.modelmapper.ModelMapper;
|
import org.modelmapper.ModelMapper;
|
||||||
|
import org.modelmapper.PropertyMap;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
import com.example.nekontakte.core.model.BaseEntity;
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
public class MapperConfiguration {
|
public class MapperConfiguration {
|
||||||
@Bean
|
@Bean
|
||||||
ModelMapper modelMapper() {
|
ModelMapper modelMapper() {
|
||||||
return new ModelMapper();
|
final ModelMapper mapper = new ModelMapper();
|
||||||
|
mapper.addMappings(new PropertyMap<Object, BaseEntity>() {
|
||||||
|
@Override
|
||||||
|
protected void configure() {
|
||||||
|
skip(destination.getId());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return mapper;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ package com.example.nekontakte.core.configurations;
|
|||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.lang.NonNull;
|
import org.springframework.lang.NonNull;
|
||||||
import org.springframework.web.servlet.config.annotation.CorsRegistry;
|
import org.springframework.web.servlet.config.annotation.CorsRegistry;
|
||||||
|
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
|
||||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@ -13,4 +14,9 @@ public class WebConfig implements WebMvcConfigurer {
|
|||||||
.allowedMethods("GET", "POST", "PUT", "DELETE");
|
.allowedMethods("GET", "POST", "PUT", "DELETE");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addViewControllers(ViewControllerRegistry registry) {
|
||||||
|
registry.addViewController("/login").setViewName("login");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,53 @@
|
|||||||
|
package com.example.nekontakte.core.errors;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.core.annotation.AnnotationUtils;
|
||||||
|
import org.springframework.web.bind.annotation.ControllerAdvice;
|
||||||
|
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||||
|
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||||
|
import org.springframework.web.servlet.ModelAndView;
|
||||||
|
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
|
@ControllerAdvice
|
||||||
|
public class AdviceController {
|
||||||
|
private final Logger log = LoggerFactory.getLogger(AdviceController.class);
|
||||||
|
|
||||||
|
private static Throwable getRootCause(Throwable throwable) {
|
||||||
|
Throwable rootCause = throwable;
|
||||||
|
while (rootCause.getCause() != null && rootCause.getCause() != rootCause) {
|
||||||
|
rootCause = rootCause.getCause();
|
||||||
|
}
|
||||||
|
return rootCause;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Map<String, Object> getAttributes(HttpServletRequest request, Throwable throwable) {
|
||||||
|
final Throwable rootCause = getRootCause(throwable);
|
||||||
|
final StackTraceElement firstError = rootCause.getStackTrace()[0];
|
||||||
|
return Map.of(
|
||||||
|
"message", rootCause.getMessage(),
|
||||||
|
"url", request.getRequestURL(),
|
||||||
|
"exception", rootCause.getClass().getName(),
|
||||||
|
"file", firstError.getFileName(),
|
||||||
|
"method", firstError.getMethodName(),
|
||||||
|
"line", firstError.getLineNumber());
|
||||||
|
}
|
||||||
|
|
||||||
|
@ExceptionHandler(value = Exception.class)
|
||||||
|
public ModelAndView defaultErrorHandler(HttpServletRequest request, Throwable throwable) throws Throwable {
|
||||||
|
if (AnnotationUtils.findAnnotation(throwable.getClass(),
|
||||||
|
ResponseStatus.class) != null) {
|
||||||
|
throw throwable;
|
||||||
|
}
|
||||||
|
|
||||||
|
log.error("{}", throwable.getMessage());
|
||||||
|
throwable.printStackTrace();
|
||||||
|
final ModelAndView model = new ModelAndView();
|
||||||
|
model.addAllObjects(getAttributes(request, throwable));
|
||||||
|
model.setViewName("error");
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,63 @@
|
|||||||
|
package com.example.nekontakte.core.security;
|
||||||
|
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
|
||||||
|
import org.springframework.security.config.Customizer;
|
||||||
|
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.configurers.AbstractHttpConfigurer;
|
||||||
|
import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer.FrameOptionsConfig;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||||
|
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||||
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
|
import org.springframework.security.web.SecurityFilterChain;
|
||||||
|
|
||||||
|
import com.example.nekontakte.core.configurations.Constants;
|
||||||
|
import com.example.nekontakte.users.api.UserSignupController;
|
||||||
|
import com.example.nekontakte.users.model.UserRole;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableWebSecurity
|
||||||
|
public class SecurityConfiguration {
|
||||||
|
@Bean
|
||||||
|
SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
|
||||||
|
httpSecurity.headers(headers -> headers.frameOptions(FrameOptionsConfig::sameOrigin));
|
||||||
|
httpSecurity.csrf(AbstractHttpConfigurer::disable);
|
||||||
|
httpSecurity.cors(Customizer.withDefaults());
|
||||||
|
|
||||||
|
httpSecurity.authorizeHttpRequests(requests -> requests
|
||||||
|
.requestMatchers("/css/**", "/webjars/**", "/*.svg")
|
||||||
|
.permitAll());
|
||||||
|
|
||||||
|
httpSecurity.authorizeHttpRequests(requests -> requests
|
||||||
|
.requestMatchers(Constants.ADMIN_PREFIX + "/**").hasRole(UserRole.ADMIN.name())
|
||||||
|
.requestMatchers("/h2-console/**").hasRole(UserRole.ADMIN.name())
|
||||||
|
.requestMatchers(UserSignupController.URL).anonymous()
|
||||||
|
.requestMatchers(Constants.LOGIN_URL).anonymous()
|
||||||
|
.anyRequest().authenticated());
|
||||||
|
|
||||||
|
httpSecurity.formLogin(formLogin -> formLogin
|
||||||
|
.loginPage(Constants.LOGIN_URL));
|
||||||
|
|
||||||
|
httpSecurity.rememberMe(rememberMe -> rememberMe.key("uniqueAndSecret"));
|
||||||
|
|
||||||
|
httpSecurity.logout(logout -> logout
|
||||||
|
.deleteCookies("JSESSIONID"));
|
||||||
|
|
||||||
|
return httpSecurity.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
DaoAuthenticationProvider authenticationProvider(UserDetailsService userDetailsService) {
|
||||||
|
final DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
|
||||||
|
authProvider.setUserDetailsService(userDetailsService);
|
||||||
|
authProvider.setPasswordEncoder(passwordEncoder());
|
||||||
|
return authProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
PasswordEncoder passwordEncoder() {
|
||||||
|
return new BCryptPasswordEncoder();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,64 @@
|
|||||||
|
package com.example.nekontakte.core.security;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.springframework.security.core.GrantedAuthority;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
|
|
||||||
|
import com.example.nekontakte.users.model.UserEntity;
|
||||||
|
|
||||||
|
public class UserPrincipal implements UserDetails {
|
||||||
|
private final long id;
|
||||||
|
private final String username;
|
||||||
|
private final String password;
|
||||||
|
private final Set<? extends GrantedAuthority> roles;
|
||||||
|
private final boolean active;
|
||||||
|
|
||||||
|
public UserPrincipal(UserEntity user) {
|
||||||
|
this.id = user.getId();
|
||||||
|
this.username = user.getUsername();
|
||||||
|
this.password = user.getPassword();
|
||||||
|
this.roles = Set.of(user.getRole());
|
||||||
|
this.active = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getUsername() {
|
||||||
|
return username;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getPassword() {
|
||||||
|
return password;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<? extends GrantedAuthority> getAuthorities() {
|
||||||
|
return roles;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEnabled() {
|
||||||
|
return active;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isAccountNonExpired() {
|
||||||
|
return isEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isAccountNonLocked() {
|
||||||
|
return isEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isCredentialsNonExpired() {
|
||||||
|
return isEnabled();
|
||||||
|
}
|
||||||
|
}
|
@ -13,6 +13,8 @@ public class UserDTO {
|
|||||||
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
|
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
|
||||||
private Integer id;
|
private Integer id;
|
||||||
|
|
||||||
|
private String role;
|
||||||
|
|
||||||
@NotBlank
|
@NotBlank
|
||||||
private String name;
|
private String name;
|
||||||
|
|
||||||
@ -118,4 +120,11 @@ public class UserDTO {
|
|||||||
this.status = status;
|
this.status = status;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getRole() {
|
||||||
|
return role;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRole(String role) {
|
||||||
|
this.role = role;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,65 @@
|
|||||||
|
package com.example.nekontakte.users.api;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import org.modelmapper.ModelMapper;
|
||||||
|
import org.springframework.stereotype.Controller;
|
||||||
|
import org.springframework.ui.Model;
|
||||||
|
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 com.example.nekontakte.core.configurations.Constants;
|
||||||
|
import com.example.nekontakte.users.model.UserEntity;
|
||||||
|
import com.example.nekontakte.users.service.UserService;
|
||||||
|
|
||||||
|
import jakarta.validation.Valid;
|
||||||
|
|
||||||
|
@Controller
|
||||||
|
@RequestMapping(UserSignupController.URL)
|
||||||
|
public class UserSignupController {
|
||||||
|
public static final String URL = "/signup";
|
||||||
|
|
||||||
|
private static final String SIGNUP_VIEW = "signup";
|
||||||
|
private static final String USER_ATTRIBUTE = "user";
|
||||||
|
|
||||||
|
private final UserService userService;
|
||||||
|
private final ModelMapper modelMapper;
|
||||||
|
|
||||||
|
public UserSignupController(
|
||||||
|
UserService userService,
|
||||||
|
ModelMapper modelMapper) {
|
||||||
|
this.userService = userService;
|
||||||
|
this.modelMapper = modelMapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
private UserEntity toEntity(UserSignupDto dto) {
|
||||||
|
return modelMapper.map(dto, UserEntity.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping
|
||||||
|
public String getSignup(Model model) {
|
||||||
|
model.addAttribute(USER_ATTRIBUTE, new UserSignupDto());
|
||||||
|
return SIGNUP_VIEW;
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping
|
||||||
|
public String signup(
|
||||||
|
@ModelAttribute(name = USER_ATTRIBUTE) @Valid UserSignupDto user,
|
||||||
|
BindingResult bindingResult,
|
||||||
|
Model model) {
|
||||||
|
if (bindingResult.hasErrors()) {
|
||||||
|
return SIGNUP_VIEW;
|
||||||
|
}
|
||||||
|
if (!Objects.equals(user.getPassword(), user.getPasswordConfirm())) {
|
||||||
|
bindingResult.rejectValue("password", "signup:passwords", "Пароли не совпадают.");
|
||||||
|
model.addAttribute(USER_ATTRIBUTE, user);
|
||||||
|
return SIGNUP_VIEW;
|
||||||
|
}
|
||||||
|
userService.create(toEntity(user));
|
||||||
|
return Constants.REDIRECT_VIEW + Constants.LOGIN_URL + "?signup";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,40 @@
|
|||||||
|
package com.example.nekontakte.users.api;
|
||||||
|
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
import jakarta.validation.constraints.Size;
|
||||||
|
|
||||||
|
public class UserSignupDto {
|
||||||
|
@NotBlank
|
||||||
|
@Size(min = 3, max = 20)
|
||||||
|
private String login;
|
||||||
|
@NotBlank
|
||||||
|
@Size(min = 3, max = 20)
|
||||||
|
private String password;
|
||||||
|
@NotBlank
|
||||||
|
@Size(min = 3, max = 20)
|
||||||
|
private String passwordConfirm;
|
||||||
|
|
||||||
|
public String getLogin() {
|
||||||
|
return login;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLogin(String login) {
|
||||||
|
this.login = login;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPassword() {
|
||||||
|
return password;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPassword(String password) {
|
||||||
|
this.password = password;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPasswordConfirm() {
|
||||||
|
return passwordConfirm;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPasswordConfirm(String passwordConfirm) {
|
||||||
|
this.passwordConfirm = passwordConfirm;
|
||||||
|
}
|
||||||
|
}
|
@ -27,6 +27,7 @@ public class UserEntity extends BaseEntity {
|
|||||||
@Temporal(value = TemporalType.DATE)
|
@Temporal(value = TemporalType.DATE)
|
||||||
@Column(nullable = false)
|
@Column(nullable = false)
|
||||||
private Date birthday;
|
private Date birthday;
|
||||||
|
private UserRole role;
|
||||||
@Column
|
@Column
|
||||||
private String city;
|
private String city;
|
||||||
@Column
|
@Column
|
||||||
@ -61,6 +62,14 @@ public class UserEntity extends BaseEntity {
|
|||||||
setIsAdmin(isAdmin);
|
setIsAdmin(isAdmin);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public UserRole getRole() {
|
||||||
|
return role;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRole(UserRole role) {
|
||||||
|
this.role = role;
|
||||||
|
}
|
||||||
|
|
||||||
public Set<PostEntity> getPosts() {
|
public Set<PostEntity> getPosts() {
|
||||||
return posts;
|
return posts;
|
||||||
}
|
}
|
||||||
@ -149,7 +158,8 @@ public class UserEntity extends BaseEntity {
|
|||||||
status,
|
status,
|
||||||
city,
|
city,
|
||||||
birthday,
|
birthday,
|
||||||
avatarImg);
|
avatarImg,
|
||||||
|
role);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -168,6 +178,7 @@ public class UserEntity extends BaseEntity {
|
|||||||
Objects.equals(other.getStatus(), status) &&
|
Objects.equals(other.getStatus(), status) &&
|
||||||
Objects.equals(other.getCity(), city) &&
|
Objects.equals(other.getCity(), city) &&
|
||||||
Objects.equals(other.getBirthday(), birthday) &&
|
Objects.equals(other.getBirthday(), birthday) &&
|
||||||
Objects.equals(other.getAvatarImg(), avatarImg);
|
Objects.equals(other.getAvatarImg(), avatarImg) &&
|
||||||
|
Objects.equals(other.getRole(), role);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -0,0 +1,15 @@
|
|||||||
|
package com.example.nekontakte.users.model;
|
||||||
|
|
||||||
|
import org.springframework.security.core.GrantedAuthority;
|
||||||
|
|
||||||
|
public enum UserRole implements GrantedAuthority {
|
||||||
|
ADMIN,
|
||||||
|
USER;
|
||||||
|
|
||||||
|
private static final String PREFIX = "ROLE_";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getAuthority() {
|
||||||
|
return PREFIX + this.name();
|
||||||
|
}
|
||||||
|
}
|
@ -2,9 +2,13 @@ package com.example.nekontakte.users.service;
|
|||||||
|
|
||||||
import org.springframework.data.domain.Page;
|
import org.springframework.data.domain.Page;
|
||||||
import org.springframework.data.domain.PageRequest;
|
import org.springframework.data.domain.PageRequest;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||||
|
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import com.example.nekontakte.core.errors.NotFoundException;
|
import com.example.nekontakte.core.errors.NotFoundException;
|
||||||
|
import com.example.nekontakte.core.security.UserPrincipal;
|
||||||
import com.example.nekontakte.users.model.UserEntity;
|
import com.example.nekontakte.users.model.UserEntity;
|
||||||
import com.example.nekontakte.users.repository.UserRepository;
|
import com.example.nekontakte.users.repository.UserRepository;
|
||||||
|
|
||||||
@ -14,7 +18,7 @@ import java.util.List;
|
|||||||
import java.util.stream.StreamSupport;
|
import java.util.stream.StreamSupport;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
public class UserService {
|
public class UserService implements UserDetailsService {
|
||||||
private final UserRepository repository;
|
private final UserRepository repository;
|
||||||
|
|
||||||
private boolean checkUsernameIsExist(String username) {
|
private boolean checkUsernameIsExist(String username) {
|
||||||
@ -79,4 +83,17 @@ public class UserService {
|
|||||||
repository.delete(existsEntity);
|
repository.delete(existsEntity);
|
||||||
return existsEntity;
|
return existsEntity;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
public UserEntity getByUsername(String username) {
|
||||||
|
return repository.findByUsernameIgnoreCase(username)
|
||||||
|
.orElseThrow(() -> new IllegalArgumentException("Invalid login"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
|
||||||
|
final UserEntity existsUser = getByUsername(username);
|
||||||
|
return new UserPrincipal(existsUser);
|
||||||
|
}
|
||||||
}
|
}
|
71
nekontakte/src/main/resources/templates/default.html
Normal file
71
nekontakte/src/main/resources/templates/default.html
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="ru" data-bs-theme="dark" xmlns:th="http://www.thymeleaf.org"
|
||||||
|
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
|
||||||
|
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity6">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title layout:title-pattern="$LAYOUT_TITLE - $CONTENT_TITLE">My shop</title>
|
||||||
|
<script type="text/javascript" src="/webjars/bootstrap/5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
|
<link rel="stylesheet" href="/webjars/bootstrap/5.3.3/dist/css/bootstrap.min.css" />
|
||||||
|
<link rel="stylesheet" href="/webjars/bootstrap-icons/1.11.3/font/bootstrap-icons.min.css" />
|
||||||
|
<link rel="stylesheet" href="/css/style.css" />
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body class="h-100 d-flex flex-column">
|
||||||
|
<nav class="navbar navbar-expand-md my-navbar" data-bs-theme="dark">
|
||||||
|
<div class="container-fluid">
|
||||||
|
<a class="navbar-brand" href="/">
|
||||||
|
<i class="bi bi-cart2 d-inline-block align-top me-1 logo"></i>
|
||||||
|
MyShop
|
||||||
|
</a>
|
||||||
|
<th:block sec:authorize="isAuthenticated()" th:with="userName=${#authentication.name}">
|
||||||
|
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#main-navbar"
|
||||||
|
aria-controls="main-navbar" aria-expanded="false" aria-label="Toggle navigation">
|
||||||
|
<span class="navbar-toggler-icon"></span>
|
||||||
|
</button>
|
||||||
|
<div class="collapse navbar-collapse" id="main-navbar">
|
||||||
|
<ul class="navbar-nav me-auto link" th:with="activeLink=${#objects.nullSafe(servletPath, '')}">
|
||||||
|
<th:block sec:authorize="hasRole('ADMIN')">
|
||||||
|
<a class="nav-link" href="/admin/user"
|
||||||
|
th:classappend="${activeLink.startsWith('/admin/user') ? 'active' : ''}">
|
||||||
|
Пользователи
|
||||||
|
</a>
|
||||||
|
<a class="nav-link" href="/admin/type"
|
||||||
|
th:classappend="${activeLink.startsWith('/admin/type') ? 'active' : ''}">
|
||||||
|
Типы заказов
|
||||||
|
</a>
|
||||||
|
<a class="nav-link" href="/admin/subscription"
|
||||||
|
th:classappend="${activeLink.startsWith('/admin/subscription') ? 'active' : ''}">
|
||||||
|
Списки рассылки
|
||||||
|
</a>
|
||||||
|
<a class="nav-link" href="/h2-console/" target="_blank">Консоль H2</a>
|
||||||
|
</th:block>
|
||||||
|
<a class="nav-link" href="/123" target="_blank">Ошибка 1</a>
|
||||||
|
<a class="nav-link" href="/admin/123" target="_blank">Ошибка 2</a>
|
||||||
|
</ul>
|
||||||
|
<ul class="navbar-nav" th:if="${not #strings.isEmpty(userName)}">
|
||||||
|
<form th:action="@{/logout}" method="post">
|
||||||
|
<button type="submit" class="navbar-brand nav-link" onclick="return confirm('Вы уверены?')">
|
||||||
|
Выход ([[${userName}]])
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
<a class="navbar-brand" href="/cart">
|
||||||
|
<i class="bi bi-cart2 d-inline-block align-top me-1 logo"></i>
|
||||||
|
[[${#numbers.formatDecimal(totalCart, 1, 2)}]] ₽
|
||||||
|
</a>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</th:block>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
<main class="container-fluid p-2" layout:fragment="content">
|
||||||
|
</main>
|
||||||
|
<footer class="my-footer mt-auto d-flex flex-shrink-0 justify-content-center align-items-center">
|
||||||
|
Автор, [[${#dates.year(#dates.createNow())}]]
|
||||||
|
</footer>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
37
nekontakte/src/main/resources/templates/error.html
Normal file
37
nekontakte/src/main/resources/templates/error.html
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="ru" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{default}">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<title>Ошибка</title>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<main layout:fragment="content">
|
||||||
|
<ul class="list-group mb-2">
|
||||||
|
<th:block th:if="${#strings.isEmpty(message)}">
|
||||||
|
<li class="list-group-item">
|
||||||
|
Неизвестная ошибка
|
||||||
|
</li>
|
||||||
|
</th:block>
|
||||||
|
<th:block th:if="${not #strings.isEmpty(message)}">
|
||||||
|
<li class="list-group-item">
|
||||||
|
<strong>Ошибка:</strong> [[${message}]]
|
||||||
|
</li>
|
||||||
|
</th:block>
|
||||||
|
<th:block th:if="${not #strings.isEmpty(url)}">
|
||||||
|
<li class="list-group-item">
|
||||||
|
<strong>Адрес:</strong> [[${url}]]
|
||||||
|
</li>
|
||||||
|
<li class="list-group-item">
|
||||||
|
<strong>Класс исключения:</strong> [[${exception}]]
|
||||||
|
</li>
|
||||||
|
<li class="list-group-item">
|
||||||
|
[[${method}]] ([[${file}]]:[[${line}]])
|
||||||
|
</li>
|
||||||
|
</th:block>
|
||||||
|
</ul>
|
||||||
|
<a class="btn btn-primary button-fixed-width" href="/">На главную</a>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
44
nekontakte/src/main/resources/templates/login.html
Normal file
44
nekontakte/src/main/resources/templates/login.html
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="ru" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{default}">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<title>Вход</title>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<main layout:fragment="content">
|
||||||
|
<form action="#" th:action="@{/login}" method="post">
|
||||||
|
<div th:if="${param.error}" class="alert alert-danger">
|
||||||
|
Неверный логин или пароль
|
||||||
|
</div>
|
||||||
|
<div th:if="${param.logout}" class="alert alert-success">
|
||||||
|
Выход успешно произведен
|
||||||
|
</div>
|
||||||
|
<div th:if="${param.signup}" class="alert alert-success">
|
||||||
|
Пользователь успешно создан
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="username" class="form-label">Имя пользователя</label>
|
||||||
|
<input type="text" id="username" name="username" class="form-control" required minlength="3"
|
||||||
|
maxlength="20">
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="password" class="form-label">Пароль</label>
|
||||||
|
<input type="password" id="password" name="password" class="form-control" required minlength="3"
|
||||||
|
maxlength="20">
|
||||||
|
</div>
|
||||||
|
<div class="form-check mb-3">
|
||||||
|
<input class="form-check-input" type="checkbox" id="remember-me" name="remember-me" checked>
|
||||||
|
<label class="form-check-label" for="remember-me">Запомнить меня</label>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3 d-flex flex-row">
|
||||||
|
<button class="btn btn-primary me-2 button-fixed-width" type="submit">Войти</button>
|
||||||
|
<a class="btn btn-secondary button-fixed-width" href="/signup">Регистрация</a>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
|
|
||||||
|
</html>
|
37
nekontakte/src/main/resources/templates/signup.html
Normal file
37
nekontakte/src/main/resources/templates/signup.html
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="ru" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{default}">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<title>Вход</title>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<main layout:fragment="content">
|
||||||
|
<form action="#" th:action="@{/signup}" th:object="${user}" method="post">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="login" class="form-label">Имя пользователя</label>
|
||||||
|
<input type="text" th:field="*{login}" id="login" class="form-control">
|
||||||
|
<div th:if="${#fields.hasErrors('login')}" th:errors="*{login}" class="invalid-feedback"></div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="password" class="form-label">Пароль</label>
|
||||||
|
<input type="password" th:field="*{password}" id="password" class="form-control">
|
||||||
|
<div th:if="${#fields.hasErrors('password')}" th:errors="*{password}" class="invalid-feedback"></div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="passwordConfirm" class="form-label">Пароль (подтверждение)</label>
|
||||||
|
<input type="password" th:field="*{passwordConfirm}" id="passwordConfirm" class="form-control">
|
||||||
|
<div th:if="${#fields.hasErrors('passwordConfirm')}" th:errors="*{passwordConfirm}"
|
||||||
|
class="invalid-feedback"></div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3 d-flex flex-row">
|
||||||
|
<button class="btn btn-primary me-2 button-fixed-width" type="submit">Регистрация</button>
|
||||||
|
<a class="btn btn-secondary button-fixed-width" href="/">Отмена</a>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
|
|
||||||
|
</html>
|
Loading…
Reference in New Issue
Block a user