목차 | |
1. | security |
1. security
◈ build.gradle (boot_JPA)
plugins {
id 'java'
id 'war'
id 'org.springframework.boot' version '3.2.11'
id 'io.spring.dependency-management' version '1.1.6'
}
group = 'com.ezen'
version = '0.0.1-SNAPSHOT'
java {
toolchain {
languageVersion = JavaLanguageVersion.of(17)
}
}
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
// https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-security
implementation 'org.springframework.boot:spring-boot-starter-security'
// https://mvnrepository.com/artifact/org.thymeleaf.extras/thymeleaf-extras-springsecurity6
implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity6'
// https://mvnrepository.com/artifact/nz.net.ultraq.thymeleaf/thymeleaf-layout-dialect
implementation 'nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect'
implementation 'org.springframework.boot:spring-boot-starter-web'
// https://mvnrepository.com/artifact/org.bgee.log4jdbc-log4j2/log4jdbc-log4j2-jdbc4.1
implementation 'org.bgee.log4jdbc-log4j2:log4jdbc-log4j2-jdbc4.1:1.16'
// https://mvnrepository.com/artifact/org.apache.tika/tika-core
implementation 'org.apache.tika:tika-core:2.4.1'
// https://mvnrepository.com/artifact/org.apache.tika/tika-parsers
implementation 'org.apache.tika:tika-parsers:2.4.1'
// https://mvnrepository.com/artifact/net.coobird/thumbnailator
implementation 'net.coobird:thumbnailator:0.4.17'
compileOnly 'org.projectlombok:lombok'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
runtimeOnly 'com.mysql:mysql-connector-j'
annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'
annotationProcessor 'org.projectlombok:lombok'
/*providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat'*/
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
tasks.named('test') {
useJUnitPlatform()
}
◈ SecurityConfig.java
package com.ezen.boot_JPA.config;
import com.ezen.boot_JPA.security.CustomUserService;
import com.ezen.boot_JPA.security.LoginFailureHandler;
import com.ezen.boot_JPA.security.LoginSuccessHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
@EnableWebSecurity
@Configuration
public class SecurityConfig {
@Bean
PasswordEncoder passwordEncoder () {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http
.csrf(csrf -> csrf.disable())
.authorizeHttpRequests((authorize)->authorize
.requestMatchers("/js/**", "/dist/**", "/upload/**", "/",
"/index", "/user/join", "/user/login", "/board/list",
"/board/detail/**", "/comment/list/**").permitAll()
.requestMatchers("/user/list").hasAnyRole("ADMIN")
.anyRequest().authenticated()
)
.formLogin(login -> login
.usernameParameter("email")
.passwordParameter("pwd")
.loginPage("/user/login")
.successHandler(authenticationSuccessHandler())
.failureHandler(authenticationFailuresHandler())
.permitAll()
)
.logout(logout -> logout
.logoutUrl("/user/logout")
.invalidateHttpSession(true)
.deleteCookies("JSESSIONID")
.logoutSuccessUrl("/")
)
.build();
}
@Bean
UserDetailsService customUserService(){
return new CustomUserService();
}
@Bean
AuthenticationManager authenticationManger(AuthenticationConfiguration authenticationConfiguration) throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
@Bean
AuthenticationSuccessHandler authenticationSuccessHandler(){
return new LoginSuccessHandler();
}
@Bean
AuthenticationFailureHandler authenticationFailuresHandler(){
return new LoginFailureHandler();
}
}
◈ CustomUserService.java
package com.ezen.boot_JPA.security;
import com.ezen.boot_JPA.dto.UserDTO;
import com.ezen.boot_JPA.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
@Slf4j
public class CustomUserService implements UserDetailsService {
@Autowired
public UserService userService;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// username이 DB에 있는지 확인
// user 테이블이 email 객체를 가져오기
UserDTO userDTO = userService.selectEmail(username);
log.info(">>> login User >>> {}", userDTO);
if(userDTO == null){
throw new UsernameNotFoundException(username);
}
return new AuthMember(userDTO);
}
}
◈ LoginSuccessHandler.java
package com.ezen.boot_JPA.security;
import com.ezen.boot_JPA.service.UserService;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.DefaultRedirectStrategy;
import org.springframework.security.web.RedirectStrategy;
import org.springframework.security.web.WebAttributes;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.savedrequest.HttpSessionRequestCache;
import org.springframework.security.web.savedrequest.RequestCache;
import org.springframework.security.web.savedrequest.SavedRequest;
import org.springframework.stereotype.Component;
import java.io.IOException;
@Slf4j
@Component
public class LoginSuccessHandler implements AuthenticationSuccessHandler {
@Getter
@Setter
private String authUrl;
@Getter
@Setter
private String authEmail;
// 성공 후 가야하는 경로 생성 (객체)
private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
// request 객체의 저장공간 : 직전 갔던 경로 저장
private RequestCache requestCache = new HttpSessionRequestCache();
@Autowired
@Getter
public UserService userService;
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
setAuthEmail(authentication.getName());
setAuthUrl("/board/list");
boolean isOk = userService.updateLastLogin(getAuthEmail());
HttpSession ses = request.getSession();
if(!isOk || ses == null){
return;
}else{
ses.removeAttribute(WebAttributes.AUTHENTICATION_EXCEPTION);
}
SavedRequest savedRequest = requestCache.getRequest(request, response);
log.info(">>> saveRequest >>> {}", savedRequest);
redirectStrategy.sendRedirect(request, response,
(savedRequest != null ? savedRequest.getRedirectUrl() : getAuthUrl()));
}
}
◈ LoginFailureHandler.java
package com.ezen.boot_JPA.security;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.InternalAuthenticationServiceException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
@Slf4j
@Component
public class LoginFailureHandler extends SimpleUrlAuthenticationFailureHandler {
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
String errorMessage;
if(exception instanceof BadCredentialsException){
errorMessage = "아이디 또는 비밀번호가 맞지 않습니다. 다시 확인해주세요.";
}else if(exception instanceof InternalAuthenticationServiceException){
errorMessage = "내부 시스템 문제로 로그인 처리를 할 수 없습니다. 관리자에게 문의해주세요.";
}else if(exception instanceof UsernameNotFoundException) {
errorMessage = "계정 확인 후 로그인 해주세요.";
}else if(exception instanceof AuthenticationCredentialsNotFoundException){
errorMessage = "인증 요청이 거부되었습니다. 관리자에게 문의해주세요.";
}else{
errorMessage = "관리자에게 문의해주세요.";
}
errorMessage = URLEncoder.encode(errorMessage, StandardCharsets.UTF_8);
setDefaultFailureUrl("/user/login?error=true&exception="+errorMessage);
super.onAuthenticationFailure(request, response, exception);
}
}
◈ User.java
package com.ezen.boot_JPA.entity;
import com.ezen.boot_JPA.dto.AuthUserDTO;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import lombok.*;
import java.time.LocalDateTime;
@Entity
@Getter
@Setter
@ToString
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class User extends TimeBase{
@Id
@Column(length = 200, nullable = false)
private String email;
@Column(length = 250, nullable = false)
private String pwd;
@Column(name = "nick_name", length = 100)
private String nickName;
@Column(name = "last_login")
private LocalDateTime lastLogin;
}
◈ UserDTO.java
package com.ezen.boot_JPA.dto;
import lombok.*;
import java.time.LocalDateTime;
import java.util.List;
@Getter
@Setter
@ToString
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class UserDTO {
private String email;
private String pwd;
private String nickName;
private LocalDateTime lastLogin;
private LocalDateTime regAt, modAt;
private List<AuthUserDTO> authList;
}
◈ AuthUser.java
package com.ezen.boot_JPA.entity;
import jakarta.persistence.*;
import lombok.*;
@Entity
@Table(name = "auth_user")
@Getter
@Setter
@ToString
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class AuthUser {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
@Column(length = 200, nullable = false)
private String email;
@Column(length = 50, nullable = false)
private String auth;
}
◈ AuthUserDTO.java
package com.ezen.boot_JPA.dto;
import lombok.*;
@Getter
@Setter
@ToString
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class AuthUserDTO {
private long id;
private String email;
private String auth;
}
◈ UserController.java
package com.ezen.boot_JPA.controller;
import com.ezen.boot_JPA.dto.UserDTO;
import com.ezen.boot_JPA.service.CommentService;
import com.ezen.boot_JPA.service.UserService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
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 org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import java.security.Principal;
@Slf4j
@RequestMapping("/user/*")
@RequiredArgsConstructor
@Controller
public class UserController {
private final UserService userService;
private final PasswordEncoder passwordEncoder;
@GetMapping("/join")
public void join(){}
@PostMapping("/join")
public String join(UserDTO userDTO){
log.info(">>> userDTO >>> {}", userDTO);
// password 암호화
userDTO.setPwd(passwordEncoder.encode(userDTO.getPwd()));
String email = userService.register(userDTO);
log.info(">>> email >>> {}", email);
return (email == null ? "/user/join" : "/index");
}
@GetMapping("/login")
public void login(@RequestParam(value = "error", required = false) String error,
@RequestParam(value = "exception", required = false) String exception,
Model model){
/* 에러의 예외값을 담아 화면으로 전달 */
model.addAttribute("error", error);
model.addAttribute("exception", exception);
}
@GetMapping("/list")
public void list(Model model){
model.addAttribute("userList", userService.getList());
}
@GetMapping("/modify")
public void modfiy(Principal principal, Model model){
model.addAttribute("userDTO", userService.selectEmail(principal.getName()));
}
@PostMapping("/modify")
public String modify(UserDTO userDTO){
if(userDTO.getPwd().length() > 0){
userDTO.setPwd(passwordEncoder.encode(userDTO.getPwd()));
}
String email = userService.modify(userDTO);
return "redirect:/user/logout";
}
@GetMapping("/remove")
public String remove(@RequestParam("email") String email){
userService.remove(email);
return "redirect:/user/logout";
}
}
◈ UserService.java
package com.ezen.boot_JPA.service;
import com.ezen.boot_JPA.dto.AuthUserDTO;
import com.ezen.boot_JPA.dto.UserDTO;
import com.ezen.boot_JPA.entity.AuthUser;
import com.ezen.boot_JPA.entity.User;
import java.util.List;
public interface UserService {
/* convert 작업
* UserDTO(화면) => User(저장) 변환
* */
default User convertDtoToEntity(UserDTO userDTO){
return User.builder()
.email(userDTO.getEmail())
.pwd(userDTO.getPwd())
.nickName(userDTO.getNickName())
.lastLogin(userDTO.getLastLogin())
.build();
}
default AuthUser convertDtoToAuthEntity(UserDTO userDTO){
return AuthUser.builder()
.email(userDTO.getEmail())
.auth("ROLE_USER")
.build();
}
/* user(entity) => userDTO */
default AuthUserDTO convertEntityToAuthDto(AuthUser authUser){
return AuthUserDTO.builder()
.id(authUser.getId())
.email(authUser.getEmail())
.auth(authUser.getAuth())
.build();
}
/* user(entity) => userDTO */
default UserDTO convertEntityToDto(User user, List<AuthUserDTO> authUserDTOList) {
return UserDTO.builder()
.email(user.getEmail())
.pwd(user.getPwd())
.nickName(user.getNickName())
.lastLogin(user.getLastLogin())
.regAt(user.getRegAt())
.modAt(user.getModAt())
.authList(authUserDTOList)
.build();
}
String register(UserDTO userDTO);
UserDTO selectEmail(String username);
boolean updateLastLogin(String authEmail);
List<UserDTO> getList();
String modify(UserDTO userDTO);
void remove(String email);
}
◈ UserServiceImpl.java
package com.ezen.boot_JPA.service;
import com.ezen.boot_JPA.dto.UserDTO;
import com.ezen.boot_JPA.entity.AuthUser;
import com.ezen.boot_JPA.entity.User;
import com.ezen.boot_JPA.repository.AuthUserRepository;
import com.ezen.boot_JPA.repository.UserRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
@Slf4j
@Service
@RequiredArgsConstructor
public class UserServiceImpl implements UserService{
private final UserRepository userRepository;
private final AuthUserRepository authUserRepository;
@Override
public String register(UserDTO userDTO) {
String email = userRepository.save(convertDtoToEntity(userDTO)).getEmail();
if(email != null){
authUserRepository.save(convertDtoToAuthEntity(userDTO));
}
return email;
}
@Transactional
@Override
public UserDTO selectEmail(String username) {
// findById : ID(PK)를 조건으로 해당 객체를 검색 후 리턴(Optional)
// 내가 검색하고자 하는 조건이 ID가 아닐 경우. Repository 등록 후 사용
// optional.isPresent() : 값이 있는지 확인 true / false
// optional.isEmpty() : 값이 비었는지 확인 true / false
Optional<User> optional = userRepository.findById(username);
List<AuthUser> authUserList = authUserRepository.findByEmail(username);
if(optional.isPresent()){
UserDTO userDTO = convertEntityToDto(optional.get(),
authUserList
.stream()
.map(u -> convertEntityToAuthDto(u))
.toList()
);
log.info(">>> userDTO >>> {}", userDTO);
return userDTO;
}
return null;
}
@Override
public boolean updateLastLogin(String authEmail) {
Optional<User> optional = userRepository.findById(authEmail);
if(optional.isPresent()){
User user = optional.get();
// LocalDateTime.now() => 현재 날짜 시간
user.setLastLogin(LocalDateTime.now());
String email = userRepository.save(user).getEmail();
return email == null ? false : true;
}
return false;
}
@Override
public List<UserDTO> getList() {
// findAll : select * from user;
List<User> userList = userRepository.findAll();
List<UserDTO> userDTOList = new ArrayList<>();
for(User user : userList){
List<AuthUser> authUserList = authUserRepository.findByEmail(user.getEmail());
UserDTO userDTO = convertEntityToDto(user,
authUserList.stream()
.map(u -> convertEntityToAuthDto(u))
.toList());
userDTOList.add(userDTO);
}
return userDTOList;
}
@Override
public String modify(UserDTO userDTO) {
Optional<User> optional = userRepository.findById(userDTO.getEmail());
if(optional.isPresent()){
User user = optional.get();
if(userDTO.getPwd().length() > 0){
user.setPwd(userDTO.getPwd());
}
user.setNickName(userDTO.getNickName());
return userRepository.save(user).getEmail();
}
return null;
}
@Override
public void remove(String email) {
Optional <User> optional = userRepository.findById(email);
if(optional.isPresent()){
User user = optional.get();
List<AuthUser> authUserList = authUserRepository.findByEmail(user.getEmail());
for(AuthUser authUser : authUserList){
authUserRepository.deleteById(authUser.getId());
}
}
userRepository.deleteById(email);
}
}
◈ AuthUserRepository.java
package com.ezen.boot_JPA.repository;
import com.ezen.boot_JPA.entity.AuthUser;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
public interface AuthUserRepository extends JpaRepository<AuthUser, Long> {
List<AuthUser> findByEmail(String email);
}
◈ UserRepositroy.java
package com.ezen.boot_JPA.repository;
import com.ezen.boot_JPA.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;
public interface UserRepository extends JpaRepository<User, String> {
}
◈ AuthMember.java
package com.ezen.boot_JPA.security;
import com.ezen.boot_JPA.dto.UserDTO;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import java.util.Collection;
import java.util.stream.Collectors;
@Slf4j
@Getter
public class AuthMember extends User {
private UserDTO userDTO;
public AuthMember(String username, String password, Collection<? extends GrantedAuthority> authorities, UserDTO userDTO) {
super(username, password, authorities);
this.userDTO = userDTO;
}
public AuthMember(UserDTO userDTO) {
super(userDTO.getEmail(), userDTO.getPwd(),
userDTO.getAuthList()
.stream()
.map(auth -> new SimpleGrantedAuthority(auth.getAuth()))
.collect(Collectors.toList()));
this.userDTO = userDTO;
}
}
◈ header.html
<!DOCTYPE html>
<html lang="en"
xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/extra/spring-security">
<!--/* th:fragment="이름" : 레이아웃에서 사용할 조각 */-->
<div th:fragment="header">
<nav class="navbar navbar-expand-lg bg-body-tertiary">
<div class="container-fluid">
<a class="navbar-brand" href="/">Boot</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
<li class="nav-item">
<a class="nav-link active" aria-current="page" href="/">Home</a>
</li>
<li class="nav-item" sec:authorize="isAuthenticated()">
<a class="nav-link" href="/board/register">BoardRegister</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/board/list">BoardList</a>
</li>
<li class="nav-item" sec:authorize="isAnonymous()">
<a class="nav-link" href="/user/join">Join</a>
</li>
<li class="nav-item" sec:authorize="isAnonymous()">
<a class="nav-link" href="/user/login">Login</a>
</li>
<li class="nav-item" sec:authorize="isAuthenticated()">
<a class="nav-link" href="/user/logout">Logout</a>
</li>
<li class="nav-item" sec:authorize="hasAnyAuthority('ROLE_ADMIN')">
<a class="nav-link" href="/user/list">UserList</a>
</li>
<li class="nav-item" sec:authorize="isAuthenticated()">
<a class="nav-link" href="/user/modify">[[${#authentication.name}]]회원정보수정</a>
</li>
</ul>
</div>
</div>
</nav>
</div>
◈ join.html
<!DOCTYPE html>
<html lang="en"
xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorate="~{layout/layout}">
<div layout:fragment="content">
<div class="container-md">
<h1>User join Page</h1>
<hr>
<form action="/user/join" method="post">
<div class="mb-3">
<label for="e" class="form-label">Email</label>
<input type="text" class="form-control" name="email" id="e" placeholder="email...">
</div>
<div class="mb-3">
<label for="p" class="form-label">Password</label>
<input type="text" class="form-control" name="pwd" id="p" placeholder="password...">
</div>
<div class="mb-3">
<label for="n" class="form-label">Nickname</label>
<input type="text" class="form-control" name="nickName" id="n" placeholder="nickname...">
</div>
<button type="submit" class="btn btn-primary">submit</button>
</form>
</div>
</div>
◈ login.html
<!DOCTYPE html>
<html lang="en"
xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorate="~{layout/layout}">
<div layout:fragment="content">
<div class="container-md">
<h1>User login Page</h1>
<hr>
<form action="/user/login" method="post">
<div class="mb-3">
<label for="e" class="form-label">Email</label>
<input type="text" class="form-control" name="email" id="e" placeholder="email...">
</div>
<div class="mb-3">
<label for="p" class="form-label">Password</label>
<input type="text" class="form-control" name="pwd" id="p" placeholder="password...">
</div>
<div class="mb-3" th:if="${error}">
<p class="text-danger">[[${exception}]]</p>
</div>
<button type="submit" class="btn btn-primary">submit</button>
</form>
</div>
</div>
◈ list.html
<!DOCTYPE html>
<html lang="en"
xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorate="~{layout/layout}">
<div layout:fragment="content">
<h1>User List Page</h1>
<hr>
<div class="col" th:each="uvo:${userList}">
<div class="card border-dark mb-3" style="max-width: 540px;">
<div class="row g-0">
<div class="col-md-4">
<img src="/image/boo.png" class="img-fluid rounded-start" alt="...">
</div>
<div class="col-md-8">
<div class="card-body">
<h5 class="card-title">[[${uvo.nickName}]]</h5>
<p class="card-text">[[${uvo.email}]] ([[${uvo.regAt}]]) </p>
<p class="card-text">Last Login : [[${uvo.lastLogin}]]</p>
<span class="badge rounded-pill text-bg-success" th:each="authList:${uvo.authList}">[[${authList.auth}]]</span>
</div>
</div>
</div>
</div>
</div>
</div>
◈ modify.html
<!DOCTYPE html>
<html lang="en"
xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorate="~{layout/layout}">
<div layout:fragment="content">
<h1>User List Page</h1>
<hr>
<div class="mt-4 p-5 rounded" th:with="auth=${#authentication.getPrincipal()}">
<div class="row">
<div class="col">
<div class="card border-dark mb-3" style="max-width: 540px;" >
<div class="row g-0">
<div class="col-md-4">
<img src="/image/boo.png" class="img-fluid rounded-start" alt="...">
</div>
<div class="col-md-8">
<form action="/user/modify" method="post">
<h5 class="card-body">
<input type="email" name="email" class="form-control" th:value="${userDTO.email}" readonly>
<input type="text" name="nickName" class="form-control" th:value="${userDTO.nickName}">
<input type="text" name="pwd" class="form-control" placeholder="Enter Password">
</h5>
<span class="badge rounded-pill text-bg-success" th:each="authList:${userDTO.authList}">[[${authList.auth}]]</span>
<button type="submit" class="btn btn-primary btn-sm">modify</button>
<a th:href="@{/user/remove(email=${userDTO.email})}"><button type="button" class="btn btn-danger btn-sm">delete</button></a>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
▷ 출력
'Java > JPA' 카테고리의 다른 글
JPA 기초(search) - AWS 풀스택 과정 86일차 (0) | 2024.11.27 |
---|---|
JPA 기초(file) - AWS 풀스택 과정 84일차 (0) | 2024.11.25 |
JPA 기초(comment) - AWS 풀스택 과정 83일차 (0) | 2024.11.22 |
JPA 설정 및 기초 - AWS 풀스택 과정 82일차 (0) | 2024.11.21 |