Шестая лабораторная работа. Аунтефикация в MVC + обработка токенов через JWT.
This commit is contained in:
parent
f4e33caffe
commit
4a6f146039
@ -30,6 +30,12 @@ dependencies {
|
||||
implementation 'org.webjars:bootstrap:5.1.3'
|
||||
implementation 'org.webjars:jquery:3.6.0'
|
||||
implementation 'org.webjars:font-awesome:6.1.0'
|
||||
|
||||
implementation 'org.springframework.boot:spring-boot-starter-security'
|
||||
implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity6'
|
||||
implementation 'com.auth0:java-jwt:4.4.0'
|
||||
implementation group: 'io.jsonwebtoken', name: 'jjwt', version: '0.9.1'
|
||||
implementation 'javax.xml.bind:jaxb-api:2.3.1'
|
||||
}
|
||||
|
||||
tasks.named('test') {
|
||||
|
Binary file not shown.
@ -0,0 +1,14 @@
|
||||
package com.example.ipLab.StoreDataBase.Configurations;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
|
||||
@Configuration
|
||||
public class PasswordEncoderConfiguration {
|
||||
@Bean
|
||||
public PasswordEncoder createPasswordEncoder(){
|
||||
return new BCryptPasswordEncoder();
|
||||
}
|
||||
}
|
@ -0,0 +1,87 @@
|
||||
package com.example.ipLab.StoreDataBase.Configurations;
|
||||
|
||||
import com.example.ipLab.StoreDataBase.Configurations.jwt.JwtFilter;
|
||||
import com.example.ipLab.StoreDataBase.Controllers.UserController;
|
||||
import com.example.ipLab.StoreDataBase.MVC.UserLoginMVCController;
|
||||
import com.example.ipLab.StoreDataBase.Model.UserRole;
|
||||
import com.example.ipLab.StoreDataBase.Service.UserService;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
|
||||
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.WebSecurityCustomizer;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
@EnableMethodSecurity(securedEnabled = true)
|
||||
public class SecurityConfiguration {
|
||||
private final Logger log = LoggerFactory.getLogger(SecurityConfiguration.class);
|
||||
private static final String LOGIN_URL = "/login";
|
||||
public static final String SPA_URL_MASK = "/{path:[^\\.]*}";
|
||||
private UserService userService;
|
||||
private JwtFilter jwtFilter;
|
||||
|
||||
public SecurityConfiguration(UserService userService) {
|
||||
this.userService = userService;
|
||||
this.jwtFilter = new JwtFilter(userService);
|
||||
createAdminOnStartup();
|
||||
createTestUsersOnStartup();
|
||||
}
|
||||
|
||||
private void createAdminOnStartup() {
|
||||
final String admin = "admin";
|
||||
if (userService.getUserByLogin(admin) == null) {
|
||||
log.info("Admin user successfully created");
|
||||
userService.addUser(admin, admin, admin, UserRole.ADMIN, 2L);
|
||||
}
|
||||
}
|
||||
|
||||
private void createTestUsersOnStartup() {
|
||||
final String[] userNames = {"user1", "user2", "user3"};
|
||||
final Long[] userId = {19052L, 19053L, 20652L};
|
||||
int cnt = 0;
|
||||
for (String user : userNames) {
|
||||
if (userService.getUserByLogin(user) == null) {
|
||||
userService.addUser(user, user, user, UserRole.USER, userId[cnt]);
|
||||
}
|
||||
cnt++;
|
||||
}
|
||||
}
|
||||
|
||||
@Bean
|
||||
public SecurityFilterChain apiFilterChain(HttpSecurity http) throws Exception {
|
||||
http.cors()
|
||||
.and()
|
||||
.csrf().disable()
|
||||
.authorizeHttpRequests((a) -> a
|
||||
.requestMatchers(HttpMethod.DELETE, "/api/**").hasRole("ADMIN")
|
||||
.requestMatchers(HttpMethod.PUT, "/api/**").hasRole("ADMIN")
|
||||
.requestMatchers(HttpMethod.POST, "/api/**").hasRole("ADMIN")
|
||||
.requestMatchers("/api/**").authenticated()
|
||||
.requestMatchers(HttpMethod.POST, UserController.URL_LOGIN).permitAll()
|
||||
.requestMatchers(HttpMethod.GET, "/img/**").permitAll())
|
||||
.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class)
|
||||
.anonymous().and().authorizeHttpRequests((a) ->
|
||||
a.requestMatchers(LOGIN_URL, UserLoginMVCController.SIGNUP_URL, "/h2-console/**")
|
||||
.permitAll().requestMatchers("/users").hasRole("ADMIN").anyRequest().authenticated())
|
||||
.formLogin()
|
||||
.loginPage(LOGIN_URL).permitAll()
|
||||
.defaultSuccessUrl("/", true)
|
||||
.and()
|
||||
.logout().permitAll()
|
||||
.logoutSuccessUrl("/login")
|
||||
.and()
|
||||
.userDetailsService(userService);
|
||||
return http.build();
|
||||
}
|
||||
@Bean
|
||||
public WebSecurityCustomizer webSecurityCustomizer() {
|
||||
return (web) -> web.ignoring().requestMatchers("/css/**", "/js/**", "/templates/**", "/webjars/**", "/styles/**");
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
package com.example.ipLab.StoreDataBase.Configurations.jwt;
|
||||
|
||||
public class JwtException extends RuntimeException{
|
||||
public JwtException(Throwable throwable) {
|
||||
super(throwable);
|
||||
}
|
||||
|
||||
public JwtException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
@ -0,0 +1,92 @@
|
||||
package com.example.ipLab.StoreDataBase.Configurations.jwt;
|
||||
|
||||
import com.example.ipLab.StoreDataBase.Service.UserService;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.ServletRequest;
|
||||
import jakarta.servlet.ServletResponse;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
|
||||
import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class JwtFilter extends AbstractPreAuthenticatedProcessingFilter {
|
||||
private static final String AUTHORIZATION = "Authorization";
|
||||
public static final String TOKEN_BEGIN_STR = "Bearer ";
|
||||
|
||||
private final UserService userService;
|
||||
|
||||
public JwtFilter(UserService userService) {
|
||||
this.userService = userService;
|
||||
}
|
||||
|
||||
private String getTokenFromRequest(HttpServletRequest request) {
|
||||
String bearer = request.getHeader(AUTHORIZATION);
|
||||
if (StringUtils.hasText(bearer) && bearer.startsWith(TOKEN_BEGIN_STR)) {
|
||||
return bearer.substring(TOKEN_BEGIN_STR.length());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void raiseException(ServletResponse response, int status, String message) throws IOException {
|
||||
if (response instanceof final HttpServletResponse httpResponse) {
|
||||
httpResponse.setContentType(MediaType.APPLICATION_JSON_VALUE);
|
||||
httpResponse.setStatus(status);
|
||||
final byte[] body = new ObjectMapper().writeValueAsBytes(message);
|
||||
response.getOutputStream().write(body);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doFilter(ServletRequest request,
|
||||
ServletResponse response,
|
||||
FilterChain chain) throws IOException, ServletException {
|
||||
if (request instanceof final HttpServletRequest httpRequest) {
|
||||
final String token = getTokenFromRequest(httpRequest);
|
||||
if (StringUtils.hasText(token)) {
|
||||
try {
|
||||
final UserDetails user = userService.loadUserByToken(token);
|
||||
final UsernamePasswordAuthenticationToken auth =
|
||||
new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities());
|
||||
SecurityContextHolder.getContext().setAuthentication(auth);
|
||||
} catch (JwtException e) {
|
||||
raiseException(response, HttpServletResponse.SC_UNAUTHORIZED, e.getMessage());
|
||||
return;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
raiseException(response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
|
||||
String.format("Internal error: %s", e.getMessage()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
HttpServletRequest httpRequest = (HttpServletRequest) request;
|
||||
if (httpRequest.getRequestURI().startsWith("/api/")) {
|
||||
// Для URL, начинающихся с /api/, выполняем проверку наличия токена
|
||||
super.doFilter(request, response, chain);
|
||||
} else {
|
||||
// Для остальных URL выполняем авторизацию
|
||||
chain.doFilter(request, response);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object getPreAuthenticatedPrincipal(HttpServletRequest request) {
|
||||
String token = getTokenFromRequest(request);
|
||||
// Возвращаем токен как принципала
|
||||
return token;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object getPreAuthenticatedCredentials(HttpServletRequest request) {
|
||||
return new WebAuthenticationDetailsSource().buildDetails(request);
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
package com.example.ipLab.StoreDataBase.Configurations.jwt;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration
|
||||
@ConfigurationProperties(prefix = "jwt", ignoreInvalidFields = true)
|
||||
public class JwtProperties {
|
||||
private String devToken = "";
|
||||
private Boolean isDev = true;
|
||||
|
||||
public String getDevToken() {
|
||||
return devToken;
|
||||
}
|
||||
|
||||
public void setDevToken(String devToken) {
|
||||
this.devToken = devToken;
|
||||
}
|
||||
|
||||
public Boolean isDev() {
|
||||
return isDev;
|
||||
}
|
||||
|
||||
public void setDev(Boolean dev) {
|
||||
isDev = dev;
|
||||
}
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
package com.example.ipLab.StoreDataBase.Configurations.jwt;
|
||||
|
||||
import io.jsonwebtoken.Claims;
|
||||
import io.jsonwebtoken.JwtBuilder;
|
||||
import io.jsonwebtoken.Jwts;
|
||||
import io.jsonwebtoken.SignatureAlgorithm;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.ZoneId;
|
||||
import java.util.Date;
|
||||
@Component
|
||||
public class JwtsProvider {
|
||||
@Value("jwt.secret")
|
||||
private String secret;
|
||||
|
||||
public String generateToken(String login, String role) {
|
||||
Date date = Date.from(LocalDate.now().plusDays(15).atStartOfDay(ZoneId.systemDefault()).toInstant());
|
||||
|
||||
JwtBuilder builder = Jwts.builder()
|
||||
.setSubject(login)
|
||||
.setExpiration(date)
|
||||
.signWith(SignatureAlgorithm.HS512, secret);
|
||||
Claims claims = Jwts.claims();
|
||||
claims.put("role", role);
|
||||
builder.addClaims(claims);
|
||||
return builder.compact();
|
||||
}
|
||||
|
||||
public boolean validateToken(String token) {
|
||||
try {
|
||||
Jwts.parser().setSigningKey(secret).parseClaimsJws(token);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public String getLogin(String token) {
|
||||
return Jwts.parser()
|
||||
.setSigningKey(secret)
|
||||
.parseClaimsJws(token)
|
||||
.getBody()
|
||||
.getSubject();
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
package com.example.ipLab.StoreDataBase.Controllers;
|
||||
|
||||
import com.example.ipLab.StoreDataBase.DTO.UserDTO;
|
||||
import com.example.ipLab.StoreDataBase.Service.UserService;
|
||||
import jakarta.validation.Valid;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
public class UserController {
|
||||
public static final String URL_LOGIN = "/jwt/login";
|
||||
|
||||
private final UserService userService;
|
||||
|
||||
public UserController(UserService userService) {
|
||||
this.userService = userService;
|
||||
}
|
||||
|
||||
@PostMapping(URL_LOGIN)
|
||||
public String login(@RequestBody @Valid UserDTO userDto) {
|
||||
return userService.loginAndGetToken(userDto);
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
package com.example.ipLab.StoreDataBase.DTO;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
|
||||
public class LoginDTO {
|
||||
@NotBlank
|
||||
private String login;
|
||||
@NotBlank
|
||||
private String password;
|
||||
@NotBlank
|
||||
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;
|
||||
}
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
package com.example.ipLab.StoreDataBase.DTO;
|
||||
|
||||
import com.example.ipLab.StoreDataBase.Model.User;
|
||||
import com.example.ipLab.StoreDataBase.Model.UserRole;
|
||||
|
||||
public class UserDTO {
|
||||
private long id;
|
||||
private String login;
|
||||
private UserRole role;
|
||||
private String password;
|
||||
|
||||
public UserDTO() {
|
||||
}
|
||||
|
||||
public UserDTO(User user) {
|
||||
this.id = user.getId();
|
||||
this.login = user.getLogin();
|
||||
this.role = user.getRole();
|
||||
this.password = user.getPassword();
|
||||
}
|
||||
|
||||
public long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getLogin() {
|
||||
return login;
|
||||
}
|
||||
|
||||
public UserRole getRole() {
|
||||
return role;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
public void setLogin(String login) {
|
||||
this.login = login;
|
||||
}
|
||||
|
||||
public void setRole(UserRole role) {
|
||||
this.role = role;
|
||||
}
|
||||
|
||||
public void setPassword(String password) {
|
||||
this.password = password;
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
package com.example.ipLab.StoreDataBase.Exceptions;
|
||||
|
||||
public class UserNotFoundException extends RuntimeException{
|
||||
public UserNotFoundException(String login){
|
||||
super(String.format("User with login: %s hasn't been found", login));
|
||||
}
|
||||
}
|
@ -3,10 +3,13 @@ package com.example.ipLab.StoreDataBase.MVC;
|
||||
import com.example.ipLab.StoreDataBase.DTO.CustomerDTO;
|
||||
import com.example.ipLab.StoreDataBase.DTO.OrderedDTO;
|
||||
import com.example.ipLab.StoreDataBase.DTO.ProductDTO;
|
||||
import com.example.ipLab.StoreDataBase.Model.CustomUser;
|
||||
import com.example.ipLab.StoreDataBase.Model.UserRole;
|
||||
import com.example.ipLab.StoreDataBase.Service.CustomerService;
|
||||
import com.example.ipLab.StoreDataBase.Service.OrderService;
|
||||
import com.example.ipLab.StoreDataBase.Service.ProductService;
|
||||
import jakarta.validation.Valid;
|
||||
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.validation.BindingResult;
|
||||
@ -26,11 +29,19 @@ public class OrderedMVCController {
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
public String getOrdereds(Model model) {
|
||||
public String getOrdereds(Model model, @AuthenticationPrincipal CustomUser user) {
|
||||
if (user.getRole() == UserRole.USER){
|
||||
model.addAttribute("orders",
|
||||
orderedService.getOrdersByCustomerId(user.getUserID()).stream()
|
||||
.map(OrderedDTO::new)
|
||||
.toList());
|
||||
}
|
||||
else {
|
||||
model.addAttribute("orders",
|
||||
orderedService.getAllOrders().stream()
|
||||
.map(OrderedDTO::new)
|
||||
.toList());
|
||||
}
|
||||
return "order";
|
||||
}
|
||||
|
||||
|
@ -1,13 +1,24 @@
|
||||
package com.example.ipLab.StoreDataBase.MVC;
|
||||
|
||||
import com.example.ipLab.StoreDataBase.Configurations.SecurityConfiguration;
|
||||
import com.example.ipLab.StoreDataBase.DTO.ProductDTO;
|
||||
import com.example.ipLab.StoreDataBase.Model.CustomUser;
|
||||
import com.example.ipLab.StoreDataBase.Model.User;
|
||||
import com.example.ipLab.StoreDataBase.Model.UserRole;
|
||||
import com.example.ipLab.StoreDataBase.Service.ProductService;
|
||||
import com.example.ipLab.StoreDataBase.Service.UserService;
|
||||
import jakarta.validation.Valid;
|
||||
import org.springframework.security.authentication.AnonymousAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.validation.BindingResult;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.security.Principal;
|
||||
|
||||
@Controller
|
||||
@RequestMapping("/product")
|
||||
public class ProductMVCController {
|
||||
@ -18,11 +29,21 @@ public class ProductMVCController {
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
public String getProducts(Model model) {
|
||||
|
||||
public String getProducts(Model model, @AuthenticationPrincipal CustomUser user) {
|
||||
if (user.getRole() == UserRole.USER){
|
||||
model.addAttribute("products",
|
||||
productService.getAllProductsWithStores().stream()
|
||||
.map(ProductDTO::new)
|
||||
.toList());
|
||||
}
|
||||
else{
|
||||
model.addAttribute("products",
|
||||
productService.getAllProducts().stream()
|
||||
.map(ProductDTO::new)
|
||||
.toList());
|
||||
}
|
||||
|
||||
return "product";
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,51 @@
|
||||
package com.example.ipLab.StoreDataBase.MVC;
|
||||
|
||||
import com.example.ipLab.StoreDataBase.DTO.LoginDTO;
|
||||
import com.example.ipLab.StoreDataBase.Model.User;
|
||||
import com.example.ipLab.StoreDataBase.Model.UserRole;
|
||||
import com.example.ipLab.StoreDataBase.Service.UserService;
|
||||
import com.example.ipLab.StoreDataBase.util.validation.ValidationException;
|
||||
import jakarta.validation.Valid;
|
||||
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;
|
||||
|
||||
@Controller
|
||||
@RequestMapping(UserLoginMVCController.SIGNUP_URL)
|
||||
public class UserLoginMVCController {
|
||||
public static final String SIGNUP_URL = "/signup";
|
||||
|
||||
private final UserService userService;
|
||||
|
||||
public UserLoginMVCController(UserService userService) {
|
||||
this.userService = userService;
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
public String showSignupForm(Model model) {
|
||||
model.addAttribute("userDto", new LoginDTO());
|
||||
return "signup";
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
public String signup(@ModelAttribute("userDto") @Valid LoginDTO userSignupDto,
|
||||
BindingResult bindingResult,
|
||||
Model model) {
|
||||
if (bindingResult.hasErrors()) {
|
||||
model.addAttribute("errors", bindingResult.getAllErrors());
|
||||
return "signup";
|
||||
}
|
||||
try {
|
||||
final User user = userService.addUser(
|
||||
userSignupDto.getLogin(), userSignupDto.getPassword(), userSignupDto.getPasswordConfirm(), UserRole.USER, Long.parseLong("1"));
|
||||
return "redirect:/login?created=" + user.getLogin();
|
||||
} catch (ValidationException e) {
|
||||
model.addAttribute("errors", e.getMessage());
|
||||
return "signup";
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
package com.example.ipLab.StoreDataBase.MVC;
|
||||
|
||||
import com.example.ipLab.StoreDataBase.DTO.ProductDTO;
|
||||
import com.example.ipLab.StoreDataBase.DTO.UserDTO;
|
||||
import com.example.ipLab.StoreDataBase.Model.UserRole;
|
||||
import com.example.ipLab.StoreDataBase.Service.UserService;
|
||||
import org.springframework.security.access.annotation.Secured;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
|
||||
@Controller
|
||||
@RequestMapping("/user")
|
||||
public class UserMVCController {
|
||||
private final UserService userService;
|
||||
|
||||
public UserMVCController(UserService userService) {
|
||||
this.userService = userService;
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
@Secured({UserRole.AsString.ADMIN})
|
||||
public String getUsers(Model model){
|
||||
model.addAttribute("users",
|
||||
userService.getAllUsers().stream()
|
||||
.map(UserDTO::new)
|
||||
.toList());
|
||||
return "users";
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
package com.example.ipLab.StoreDataBase.Model;
|
||||
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.userdetails.User;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
public class CustomUser extends User {
|
||||
private final Long userID;
|
||||
private final UserRole role;
|
||||
|
||||
public CustomUser(String username, String password,
|
||||
Collection<? extends GrantedAuthority> authorities, Long userID, UserRole role) {
|
||||
super(username, password, authorities);
|
||||
this.userID = userID;
|
||||
this.role = role;
|
||||
}
|
||||
|
||||
public Long getUserID() {
|
||||
return userID;
|
||||
}
|
||||
|
||||
public UserRole getRole() {
|
||||
return role;
|
||||
}
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
package com.example.ipLab.StoreDataBase.Model;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
@Entity
|
||||
@Table(name = "users")
|
||||
public class User {
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.AUTO)
|
||||
private Long id;
|
||||
@Column
|
||||
@NotBlank
|
||||
private String login;
|
||||
@Column
|
||||
@NotBlank
|
||||
private String password;
|
||||
@Column
|
||||
@NotBlank
|
||||
private Long userId;
|
||||
|
||||
private UserRole role;
|
||||
|
||||
public User() {
|
||||
}
|
||||
|
||||
public User(String login, String password, Long userId) {
|
||||
this(login, password, UserRole.USER, userId);
|
||||
}
|
||||
|
||||
public User(String login, String password, UserRole role, Long userId) {
|
||||
this.login = login;
|
||||
this.password = password;
|
||||
this.role = role;
|
||||
this.userId = userId;
|
||||
}
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
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 UserRole getRole() {
|
||||
return role;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
User user = (User) o;
|
||||
return Objects.equals(id, user.id) && Objects.equals(login, user.login);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(id, login);
|
||||
}
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
package com.example.ipLab.StoreDataBase.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();
|
||||
}
|
||||
|
||||
public static final class AsString {
|
||||
public static final String ADMIN = PREFIX + "ADMIN";
|
||||
public static final String USER = PREFIX + "USER";
|
||||
}
|
||||
}
|
@ -1,7 +1,13 @@
|
||||
package com.example.ipLab.StoreDataBase.Repositories;
|
||||
|
||||
import com.example.ipLab.StoreDataBase.Model.Ordered;
|
||||
import com.example.ipLab.StoreDataBase.Model.Product;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
public interface OrderedRepository extends JpaRepository<Ordered, Long> {
|
||||
@Query("SELECT o FROM Ordered o WHERE o.customer.id = ?1")
|
||||
Collection<Ordered> findOrdersByCustomerId(Long clientId);
|
||||
}
|
||||
|
@ -0,0 +1,8 @@
|
||||
package com.example.ipLab.StoreDataBase.Repositories;
|
||||
|
||||
import com.example.ipLab.StoreDataBase.Model.User;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
public interface UserRepository extends JpaRepository<User, Long> {
|
||||
User findOneByLogin(String login);
|
||||
}
|
@ -52,6 +52,11 @@ public class OrderService {
|
||||
return orderedRepository.findAll();
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public List<Ordered> getOrdersByCustomerId(Long customerId){
|
||||
return orderedRepository.findOrdersByCustomerId(customerId).stream().toList();
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public Ordered updateOrder(Long id, int quantity){
|
||||
final Ordered order = getOrder(id);
|
||||
|
@ -36,7 +36,6 @@ public class ProductService {
|
||||
public Product getProduct(Long id){
|
||||
return productRepository.findById(id).orElseThrow(() -> new ProductNotFoundException(id));
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public List<Product> getAllProducts(){
|
||||
return productRepository.findAll();
|
||||
|
@ -0,0 +1,97 @@
|
||||
package com.example.ipLab.StoreDataBase.Service;
|
||||
|
||||
import com.example.ipLab.StoreDataBase.Configurations.jwt.JwtException;
|
||||
import com.example.ipLab.StoreDataBase.Configurations.jwt.JwtsProvider;
|
||||
import com.example.ipLab.StoreDataBase.DTO.UserDTO;
|
||||
import com.example.ipLab.StoreDataBase.Exceptions.CustomerNotFoundException;
|
||||
import com.example.ipLab.StoreDataBase.Exceptions.UserNotFoundException;
|
||||
import com.example.ipLab.StoreDataBase.Model.CustomUser;
|
||||
import com.example.ipLab.StoreDataBase.Model.Customer;
|
||||
import com.example.ipLab.StoreDataBase.Model.User;
|
||||
import com.example.ipLab.StoreDataBase.Model.UserRole;
|
||||
import com.example.ipLab.StoreDataBase.Repositories.UserRepository;
|
||||
import com.example.ipLab.StoreDataBase.util.validation.ValidationException;
|
||||
import com.example.ipLab.StoreDataBase.util.validation.ValidatorUtil;
|
||||
import jakarta.transaction.Transactional;
|
||||
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.password.PasswordEncoder;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
@Service
|
||||
public class UserService implements UserDetailsService {
|
||||
private final UserRepository userRepository;
|
||||
private final PasswordEncoder passwordEncoder;
|
||||
private final ValidatorUtil validatorUtil;
|
||||
private final JwtsProvider jwtProvider;
|
||||
|
||||
public UserService(UserRepository userRepository,
|
||||
PasswordEncoder passwordEncoder,
|
||||
ValidatorUtil validatorUtil,
|
||||
JwtsProvider jwtProvider) {
|
||||
this.userRepository = userRepository;
|
||||
this.passwordEncoder = passwordEncoder;
|
||||
this.validatorUtil = validatorUtil;
|
||||
this.jwtProvider = jwtProvider;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public User addUser(String login, String password, String passwordConfirm, UserRole role, Long userId){
|
||||
if (getUserByLogin(login) != null){
|
||||
throw new ValidationException(String.format("User with login %s already exists", login));
|
||||
}
|
||||
if (!Objects.equals(password, passwordConfirm)) {
|
||||
throw new ValidationException("Passwords not equals");
|
||||
}
|
||||
final User user = new User(login, passwordEncoder.encode(password), role, userId);
|
||||
validatorUtil.validate(user);
|
||||
return userRepository.save(user);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public User getUserByLogin(String login){
|
||||
return userRepository.findOneByLogin(login);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public List<User> getAllUsers(){
|
||||
return userRepository.findAll();
|
||||
}
|
||||
|
||||
public UserDetails loadUserByToken(String token) throws UsernameNotFoundException {
|
||||
if (!jwtProvider.validateToken(token)) {
|
||||
throw new JwtException("Bad token");
|
||||
}
|
||||
final String userLogin = jwtProvider.getLogin(token);
|
||||
if (userLogin.isEmpty()) {
|
||||
throw new JwtException("Token is not contain Login");
|
||||
}
|
||||
return loadUserByUsername(userLogin);
|
||||
}
|
||||
|
||||
public String loginAndGetToken(UserDTO userDto) {
|
||||
final User user = getUserByLogin(userDto.getLogin());
|
||||
if (user == null) {
|
||||
throw new UserNotFoundException(userDto.getLogin());
|
||||
}
|
||||
if (!passwordEncoder.matches(userDto.getPassword(), user.getPassword())) {
|
||||
throw new UserNotFoundException(user.getLogin());
|
||||
}
|
||||
return jwtProvider.generateToken(user.getLogin(), user.getRole().name());
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
|
||||
final User userEntity = getUserByLogin(username);
|
||||
if (userEntity == null) {
|
||||
throw new UsernameNotFoundException(username);
|
||||
}
|
||||
return new CustomUser(
|
||||
userEntity.getLogin(), userEntity.getPassword(), Collections.singleton(userEntity.getRole()), userEntity.getId(), userEntity.getRole());
|
||||
}
|
||||
}
|
@ -13,6 +13,7 @@ import org.springframework.web.bind.annotation.ControllerAdvice;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.nio.file.AccessDeniedException;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@ControllerAdvice(annotations = RestController.class)
|
||||
@ -35,6 +36,14 @@ public class AdviceController {
|
||||
return handleException(validationException);
|
||||
}
|
||||
|
||||
@ExceptionHandler({
|
||||
AccessDeniedException.class
|
||||
})
|
||||
public ResponseEntity<Object> handleAccessDeniedException(Throwable e) {
|
||||
return new ResponseEntity<>(e.getMessage(), HttpStatus.FORBIDDEN);
|
||||
}
|
||||
|
||||
|
||||
@ExceptionHandler(Exception.class)
|
||||
public ResponseEntity<Object> handleUnknownException(Throwable e) {
|
||||
e.printStackTrace();
|
||||
|
@ -6,4 +6,8 @@ public class ValidationException extends RuntimeException{
|
||||
public ValidationException(Set<String> errors){
|
||||
super(String.join("\n", errors));
|
||||
}
|
||||
|
||||
public ValidationException(String error){
|
||||
super(error);
|
||||
}
|
||||
}
|
||||
|
@ -2,11 +2,18 @@ package com.example.ipLab;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.servlet.config.annotation.CorsRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
@Configuration
|
||||
public class WebConfiguration implements WebMvcConfigurer {
|
||||
public static final String REST_API = "/api";
|
||||
|
||||
@Override
|
||||
public void addViewControllers(ViewControllerRegistry registry) {
|
||||
WebMvcConfigurer.super.addViewControllers(registry);
|
||||
registry.addViewController("login");
|
||||
}
|
||||
@Override
|
||||
public void addCorsMappings(CorsRegistry registry){
|
||||
registry.addMapping("/**").allowedMethods("*");
|
||||
|
@ -9,3 +9,6 @@ spring.jpa.hibernate.ddl-auto=update
|
||||
spring.h2.console.enabled=true
|
||||
spring.h2.console.settings.trace=false
|
||||
spring.h2.console.settings.web-allow-others=false
|
||||
jwt.dev-token=my-secret-jwt
|
||||
jwt.dev=true
|
||||
jwt.secret = my-secret-jwt
|
||||
|
BIN
backend/ipLab/src/main/resources/static/img/logo.png
Normal file
BIN
backend/ipLab/src/main/resources/static/img/logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 9.7 KiB |
@ -5,7 +5,7 @@
|
||||
<head>
|
||||
</head>
|
||||
<body>
|
||||
<div layout:fragment="content">
|
||||
<div sec:authorize="hasRole('ROLE_ADMIN')" layout:fragment="content">
|
||||
<div th:text="${errors}" class="margin-bottom alert-danger"></div>
|
||||
<form action="#" th:action="@{/store/add}" method="post">
|
||||
<div class="mb-3">
|
||||
|
@ -5,7 +5,7 @@
|
||||
<head>
|
||||
</head>
|
||||
<body>
|
||||
<div layout:fragment="content">
|
||||
<div sec:authorize="hasRole('ROLE_ADMIN')" layout:fragment="content">
|
||||
<div>
|
||||
<a class="btn btn-success button-fixed"
|
||||
th:href="@{/customer/edit/}">
|
||||
@ -17,6 +17,7 @@
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">#</th>
|
||||
<th scope="col">ID</th>
|
||||
<th scope="col">Фамилия</th>
|
||||
<th scope="col">Имя</th>
|
||||
<th scope="col">Отчество</th>
|
||||
@ -26,6 +27,7 @@
|
||||
<tbody>
|
||||
<tr th:each="customer, iterator: ${customers}">
|
||||
<th scope="row" th:text="${iterator.index} + 1"/>
|
||||
<td th:text="${customer.Id}"/>
|
||||
<td th:text="${customer.lastName}"/>
|
||||
<td th:text="${customer.firstName}"/>
|
||||
<td th:text="${customer.middleName}"/>
|
||||
|
@ -23,7 +23,7 @@
|
||||
</button>
|
||||
<div class="d-flex flex-row ml-3 ms-3 mt-auto mb-auto align-items-center">
|
||||
<a>
|
||||
<img src="/logo.png" alt="*" width="60" height="60" class="align-text-top"></img>
|
||||
<img src="/img/logo.png" alt="*" width="60" height="60" class="align-text-top"/>
|
||||
</a>
|
||||
<div id="logoName">
|
||||
<a href="/">boxStore</a>
|
||||
@ -31,7 +31,7 @@
|
||||
</div>
|
||||
<div class="navbar-collapse collapse justify-content-end" id="navbarNav">
|
||||
<ul class="navbar-nav" id="headerNavigation">
|
||||
<a class="nav-link headNav" href="/customer"
|
||||
<a sec:authorize="hasRole('ROLE_ADMIN')" class="nav-link headNav" href="/customer"
|
||||
th:classappend="${#strings.equals(activeLink, '/customer')} ? 'active' : ''">Клиенты</a>
|
||||
<a class="nav-link headNav" href="/store"
|
||||
th:classappend="${#strings.equals(activeLink, '/store')} ? 'active' : ''">Магазины</a>
|
||||
@ -39,8 +39,10 @@
|
||||
th:classappend="${#strings.equals(activeLink, '/product')} ? 'active' : ''">Товары</a>
|
||||
<a class="nav-link headNav" href="/order"
|
||||
th:classappend="${#strings.equals(activeLink, '/order')} ? 'active' : ''">Заказы</a>
|
||||
<a class="nav-link headNav" href="/store/addToStore"
|
||||
<a sec:authorize="hasRole('ROLE_ADMIN')" class="nav-link headNav" href="/store/addToStore"
|
||||
th:classappend="${#strings.equals(activeLink, '/order')} ? 'active' : ''">Доставка</a>
|
||||
<a sec:authorize="!isAuthenticated()" class="nav-link headNav" href="/login">Войти</a>
|
||||
<a sec:authorize="isAuthenticated()" class="nav-link headNav" href="/logout">Выйти</a>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -6,7 +6,7 @@
|
||||
</head>
|
||||
<body>
|
||||
<div layout:fragment="content">
|
||||
<h2>Добро пожаловать!</h2>
|
||||
<h2>Добро пожаловать, <span sec:authentication="name"></span>!</h2>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
43
backend/ipLab/src/main/resources/templates/login.html
Normal file
43
backend/ipLab/src/main/resources/templates/login.html
Normal file
@ -0,0 +1,43 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en"
|
||||
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
|
||||
layout:decorate="~{default}">
|
||||
<head>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container-fluid" layout:fragment="content">
|
||||
<div th:if="${param.error}" class="alert alert-danger margin-bottom">
|
||||
Пользователь не найден или пароль указан не верно
|
||||
</div>
|
||||
<div th:if="${param.logout}" class="alert alert-success margin-bottom">
|
||||
Выход успешно произведен
|
||||
</div>
|
||||
<div th:if="${param.created}" class="alert alert-success margin-bottom">
|
||||
Пользователь '<span th:text="${param.created}"></span>' успешно создан
|
||||
</div>
|
||||
<div class="row justify-content-center align-items-center vh-100">
|
||||
<div class="col-sm-6 col-md-4">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Авторизация</h5>
|
||||
<form th:action="@{/login}" method="post">
|
||||
<div class="mb-3">
|
||||
<label htmlFor="login">Логин</label>
|
||||
<input type="text" name="username" id="username" class="form-control"
|
||||
placeholder="Логин" required="true" autofocus="true"/>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label htmlFor="login">Пароль</label>
|
||||
<input type="password" name="password" id="password" class="form-control"
|
||||
placeholder="Пароль" required="true"/>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary button-fixed">Войти</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
@ -6,7 +6,7 @@
|
||||
</head>
|
||||
<body>
|
||||
<div layout:fragment="content">
|
||||
<div>
|
||||
<div sec:authorize="hasRole('ROLE_ADMIN')">
|
||||
<a class="btn btn-success button-fixed"
|
||||
th:href="@{/order/edit/}">
|
||||
<i class="fa-solid fa-plus"></i> Добавить
|
||||
|
@ -6,7 +6,7 @@
|
||||
</head>
|
||||
<body>
|
||||
<div layout:fragment="content">
|
||||
<div>
|
||||
<div sec:authorize="hasRole('ROLE_ADMIN')">
|
||||
<a class="btn btn-success button-fixed "
|
||||
th:href="@{/product/edit/}">
|
||||
<i class="fa-solid fa-plus"></i> Добавить
|
||||
@ -19,7 +19,7 @@
|
||||
<th scope="col">#</th>
|
||||
<th scope="col">Название товара</th>
|
||||
<th scope="col">Название магазина</th>
|
||||
<th scope="col"></th>
|
||||
<th sec:authorize="hasRole('ROLE_ADMIN')" scope="col"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@ -27,7 +27,7 @@
|
||||
<th scope="row" th:text="${iterator.index} + 1"/>
|
||||
<td th:text="${product.productName}"/>
|
||||
<td th:text="${product.storeName}"/>
|
||||
<td style="width: 10%">
|
||||
<td sec:authorize="hasRole('ROLE_ADMIN')" style="width: 10%">
|
||||
<div class="btn-group" role="group" aria-label="Basic example">
|
||||
<a class="btn btn-warning button-fixed button-sm"
|
||||
th:href="@{/product/edit/{id}(id=${product.id})}">
|
||||
|
28
backend/ipLab/src/main/resources/templates/signup.html
Normal file
28
backend/ipLab/src/main/resources/templates/signup.html
Normal file
@ -0,0 +1,28 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en"
|
||||
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
|
||||
layout:decorate="~{default}">
|
||||
<body>
|
||||
<div class="container container-padding" layout:fragment="content">
|
||||
<div th:if="${errors}" th:text="${errors}" class="margin-bottom alert alert-danger"></div>
|
||||
<form action="#" th:action="@{/signup}" th:object="${LoginDTO}" method="post">
|
||||
<div class="mb-3">
|
||||
<input type="text" class="form-control" th:field="${LoginDTO.login}"
|
||||
placeholder="Логин" required="true" autofocus="true" maxlength="64"/>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<input type="password" class="form-control" th:field="${LoginDTO.password}"
|
||||
placeholder="Пароль" required="true" minlength="6" maxlength="64"/>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<input type="password" class="form-control" th:field="${LoginDTO.passwordConfirm}"
|
||||
placeholder="Пароль (подтверждение)" required="true" minlength="6" maxlength="64"/>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<button type="submit" class="btn btn-success button-fixed">Создать</button>
|
||||
<a class="btn btn-primary button-fixed" href="/login">Назад</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
@ -6,7 +6,7 @@
|
||||
</head>
|
||||
<body>
|
||||
<div layout:fragment="content">
|
||||
<div>
|
||||
<div sec:authorize="hasRole('ROLE_ADMIN')">
|
||||
<a class="btn btn-success button-fixed"
|
||||
th:href="@{/store/edit/}">
|
||||
<i class="fa-solid fa-plus"></i> Добавить
|
||||
@ -18,14 +18,14 @@
|
||||
<tr>
|
||||
<th scope="col">#</th>
|
||||
<th scope="col">Название магазина</th>
|
||||
<th scope="col"></th>
|
||||
<th sec:authorize="hasRole('ROLE_ADMIN')" scope="col"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr th:each="store, iterator: ${stores}">
|
||||
<th scope="row" th:text="${iterator.index} + 1"/>
|
||||
<td th:text="${store.storeName}"/>
|
||||
<td style="width: 10%">
|
||||
<td sec:authorize="hasRole('ROLE_ADMIN')" style="width: 10%">
|
||||
<div class="btn-group" role="group" aria-label="Basic example">
|
||||
<a class="btn btn-warning button-fixed button-sm"
|
||||
th:href="@{/store/edit/{id}(id=${store.id})}">
|
||||
|
27
backend/ipLab/src/main/resources/templates/users.html
Normal file
27
backend/ipLab/src/main/resources/templates/users.html
Normal file
@ -0,0 +1,27 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Title</title>
|
||||
</head>
|
||||
<body>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-success table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">#</th>
|
||||
<th scope="col">Логин</th>
|
||||
<th scope="col">Роль</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr th:each="user, iterator: ${users}">
|
||||
<th scope="row" th:text="${iterator.index} + 1"/>
|
||||
<td th:text="${user.login}"/>
|
||||
<td th:text="${user.role}"/>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
Loading…
Reference in New Issue
Block a user