2024. 4. 26. 15:41ㆍ개발일지
이번 포스트는
회원 가입, 수정, 삭제, 조회, 로그인, 아이디 찾기, 비밀번호 찾기 등에 관한 내용을 작성할 것이다.
spring security 를 사용하여 로그인을 구현하고 각 페이지에 권한을 주어 관리자, 회원에 맞게 접근 할 수 있게 하였다.
build
나는 springsecurity6 버전을 사용하였다. (springboot 3.2.3버전)
implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity6'
testImplementation 'org.springframework.security:spring-security-test'
MemberDTO
/*
작성자 : 정아름
작성일 : 24.02.19
작성내용 : 회원 구현
확인사항 : 테스트 해야함
*/
package com.example.basic.DTO;
import com.example.basic.Constant.RoleType;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;
import lombok.*;
import java.time.LocalDateTime;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class MemberDTO {
//회원 번호
private Integer memberId;
//회원 이메일
@NotBlank (message = "이메일은 필수입니다.")
@Email
private String memberEmail;
//회원 비밀번호
@NotBlank (message = "비밀번호는 필수입니다.")
@Pattern(regexp="(?=.*[0-9])(?=.*[a-zA-Z])(?=.*\\W)(?=\\S+$).{8,20}",
message = "비밀번호는 영문 대,소문자와 숫자, 특수기호가 적어도 1개 이상씩 포함된 8자 ~ 20자의 비밀번호여야 합니다.")
private String memberPassword;
//회원 이름
@NotBlank (message = "이름은 필수입니다.")
@Size(min = 2, max = 10, message = "이름은 2자 이상 10자 이하여야 합니다.")
private String memberName;
//회원 전화번호
private String memberPhone;
//회원 주소
@NotBlank (message = "주소는 필수입니다.")
private String memberAddress;
//회원 분류
private RoleType roleType;
//등록일
private LocalDateTime regDate;
//수정일
private LocalDateTime modDate;
}
이메일 형식 유효성 검사(구멍이 많다고 한다. Pattern으로도 변경했는데 또한, 확실한 방법은 아니라고 한다.)
@NotBlank
@NotBlank는 null 과 "" 과 " " 모두 허용하지 않는다.
@Size
필드 크기가 min 과 max 사이여야 값을 저장할 수 있도록 유효성을 검사해준다.
SecurityConfig
package com.example.basic.Config;
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.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
//비밀번호 보안 작업
@Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
//authenticate 인증 매니저 생성(인증과정 처리)
@Bean
AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
//권한범위(인증후 응답)
http.authorizeHttpRequests((auth)->{
//모든 이용자가 접근가능
auth.requestMatchers("/").permitAll();
auth.requestMatchers("/salad").permitAll();
auth.requestMatchers("/mypage").hasRole("USER");
auth.requestMatchers("/member/insert").permitAll();
auth.requestMatchers("/member/list").hasRole("ADMIN");
auth.requestMatchers("/member/detail").hasAnyRole("USER", "ADMIN");
auth.requestMatchers("/member/insert").permitAll();
auth.requestMatchers("/member/update").hasAnyRole("USER", "ADMIN");
auth.requestMatchers("/member/delete").hasAnyRole("USER", "ADMIN");
//auth.requestMatchers("/cart/**").permitAll();
auth.requestMatchers("/cart/list").permitAll();
auth.requestMatchers("/cart/insert/**").permitAll();
auth.requestMatchers("/cart/update").hasAnyRole("USER", "ADMIN");
auth.requestMatchers("/cart/delete").hasAnyRole("USER", "ADMIN");
auth.requestMatchers("/order/insert/**").hasAnyRole("USER", "ADMIN");
auth.requestMatchers("/order/list").hasAnyRole("USER", "ADMIN");
auth.requestMatchers("/order/detail").hasAnyRole("USER", "ADMIN");
auth.requestMatchers("/order/delete").hasAnyRole("USER", "ADMIN");
auth.requestMatchers("/board/**").permitAll();
auth.requestMatchers("/board/list").permitAll();
auth.requestMatchers("/board/detail").permitAll();
auth.requestMatchers("/board/insert").hasAnyRole("USER", "ADMIN");
auth.requestMatchers("/board/update").hasAnyRole("USER", "ADMIN");
auth.requestMatchers("/board/delete").hasAnyRole("USER", "ADMIN");
auth.requestMatchers("/boardcmt/insert").permitAll();
auth.requestMatchers("/boardcmt/update").permitAll();
auth.requestMatchers("/boardcmt/delete").permitAll();
auth.requestMatchers("/info/**").permitAll();
auth.requestMatchers("/notice/list").permitAll();
auth.requestMatchers("/notice/detail").permitAll();
auth.requestMatchers("/notice/insert").hasRole("ADMIN");
auth.requestMatchers("/notice/update").hasRole("ADMIN");
auth.requestMatchers("/notice/delete").hasRole("ADMIN");
auth.requestMatchers("/salad/list").permitAll();
auth.requestMatchers("/product/list").permitAll();
auth.requestMatchers("/product/detail").permitAll();
auth.requestMatchers("/product/insert").permitAll();
auth.requestMatchers("/product/update").hasRole("ADMIN");
auth.requestMatchers("/product/delete").hasRole("ADMIN");
auth.requestMatchers("/product/admin").hasRole("ADMIN");
auth.requestMatchers("/review/list").permitAll();
auth.requestMatchers("/review/detail").permitAll();
auth.requestMatchers("/review/insert").hasAnyRole("USER", "ADMIN");
auth.requestMatchers("/review/update").hasAnyRole("USER", "ADMIN");
auth.requestMatchers("/review/delete").hasAnyRole("USER", "ADMIN");
auth.requestMatchers("/reviewcmt/insert").permitAll();
auth.requestMatchers("/reviewcmt/update").permitAll();
auth.requestMatchers("/reviewcmt/delete").permitAll();
auth.requestMatchers("/login","/logout","/find/email","/find/password").permitAll();
auth.requestMatchers("/css/**", "/js/**", "/image/**", "/images/**").permitAll();
});
//로그인설정(기본 /login)
//로그인 페이지는 모든 사용자가 접근 가능하고, 로그인 성공시 index 페이지로 이동
http.formLogin(login-> login
.loginPage("/login")
.loginProcessingUrl("/login")
.usernameParameter("memberEmail")
.passwordParameter("memberPassword")
//로그인 성공 시 index.html 로 이동
.defaultSuccessUrl("/", true)
//로그인 실패
.failureUrl("/login?error=true")
.permitAll()
);
//로그아웃설정(기본 /logout)
http.logout(logout->logout
.logoutUrl("/logout")
.logoutSuccessUrl("/")
//로그아웃 시 사용자 세션 삭제
.invalidateHttpSession(true)
);
//사용자 인증처리 컴포넌트 서비스 등록
//http.userDetailsService(loginService);
//csrf 사용X
http.csrf(AbstractHttpConfigurer::disable);
return http.build();
}
}
antMatchers()
페이지에 접근할 수 있는 권한을 설정한다.
loginPage
로그인 페이지
loginProcessingUrl
구현한 로그인 페이지
defaultSuccessUrl
로그인 성공 시 제공할 페이지
failureUrl
로그인 실패 시 제공할 페이지
csrf().disable()
사이트 간 요청 위조(Cross-Site Request Forgery) 공격 방지 기능 키기
LoginService - 로그인, 회원 이메일 조회 기능
package com.example.basic.Service;
import com.example.basic.DTO.MemberDTO;
import com.example.basic.Entity.MemberEntity;
import com.example.basic.Repository.LoginRepository;
import lombok.RequiredArgsConstructor;
import org.modelmapper.ModelMapper;
import org.springframework.security.core.userdetails.User;
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.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
@Service
@RequiredArgsConstructor
public class LoginService implements UserDetailsService {
private final LoginRepository loginRepository;
private final ModelMapper modelMapper;
private final PasswordEncoder passwordEncoder;
//UsernameNotFoundException-사용자가 존재하지 않으면 예외발생(오류)
@Override
public UserDetails loadUserByUsername(String memberEmail) throws UsernameNotFoundException {
//회원 이메일이 존재하는지 조회
Optional<MemberEntity> memberEntity = loginRepository.findByMemberEmail(memberEmail);
//조회한 데이터가 있으면
if (memberEntity.isPresent()) {
return User.withUsername(memberEntity.get().getMemberEmail())
//passwordEncoder 비밀번호 보안작업
.password(passwordEncoder.encode(memberEntity.get().getMemberPassword()))
.roles(memberEntity.get().getRoleType().name())
.build();
} else { //조회된 데이터가 없으면
throw new UsernameNotFoundException("존재하지 않는 회원입니다.");
}
}
//이메일 찾기
public Map<String, String> findEmail(String memberPassword, String memberName) {
//비밀번호와 이름이 존재하는지 조회
Optional<MemberEntity> memberEntity = loginRepository.
findByMemberPasswordAndMemberName(memberPassword, memberName);
//조회한 데이터가 없으면
if (memberEntity.isEmpty()) {
//예외처리
throw new UsernameNotFoundException("비밀번호 또는 이름이 일치하지 않습니다.");
}
Map<String, String> result = new HashMap<>();
result.put("memberEmail", memberEntity.get().getMemberEmail());
return result;
}
}
DB에서 유저 정보를 가져오고, 그걸 UserDetails로 담아서 리턴한다.
User 클래스 (org.springframework.security.core.userdetails.User)의 빌더를 사용해서 username에 아이디, password에 비밀번호, roles에 권한(역할)을 넣어주면 UserDetails가 리턴된다.
MemberService - 회원가입, 수정, 삭제, 조회 기능
package com.example.basic.Service;
import com.example.basic.DTO.MemberDTO;
import com.example.basic.Entity.CartEntity;
import com.example.basic.Entity.MemberEntity;
import com.example.basic.Repository.CartRepository;
import com.example.basic.Repository.MemberRepository;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpSession;
import lombok.RequiredArgsConstructor;
import org.modelmapper.ModelMapper;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import java.util.Optional;
@Service
@RequiredArgsConstructor
public class MemberService {
private final MemberRepository memberRepository;
private final CartRepository cartRepository;
private final ModelMapper modelMapper;
//삽입
public void memberInsert(MemberDTO memberDTO) throws IllegalAccessException {
//기존 사용하는 아이디를 조회
Optional<MemberEntity> member = Optional.ofNullable(memberRepository
.findByMemberEmail(memberDTO.getMemberEmail()));
if (member.isEmpty()) {
//읽어온 값이 없으면(기존 사용하는 아이디가 존재하지 않으면)
MemberEntity memberEntity = modelMapper.map(memberDTO,
MemberEntity.class);
//저장
memberRepository.save(memberEntity);
} else {
throw new IllegalAccessException("이미 가입된 회원입니다.");
}
}
//수정
public void memberUpdate(MemberDTO memberDTO) {
MemberEntity memberEntity = modelMapper.map(memberDTO,
MemberEntity.class);
memberRepository.save(memberEntity);
}
//삭제
public void memberDelete(Integer memberId) {
//회원의 장바구니 찾기
CartEntity cart = cartRepository.findByMemberId(memberId);
//장바구니 삭제
cartRepository.deleteById(cart.getCartId());
//회원 계정 삭제
memberRepository.deleteById(memberId);
}
//전체 조회
public Page<MemberDTO> memberList(Pageable pageable) {
int cutPage = pageable.getPageNumber() - 1;
int pageCnt = 10;
Pageable page = PageRequest.of(cutPage, pageCnt,
Sort.by(Sort.Direction.DESC, "memberId"));
Page<MemberEntity> memberEntities = memberRepository.findAll(page);
Page<MemberDTO> memberDTOS = memberEntities.map(data ->
modelMapper.map(data, MemberDTO.class));
return memberDTOS;
}
//개별 조회
public MemberDTO memberDetail(Integer memberId) {
MemberEntity memberEntity = memberRepository.findById(memberId).orElseThrow();
MemberDTO memberDTO = modelMapper.map(memberEntity, MemberDTO.class);
return memberDTO;
}
//마이페이지 조회
public MemberDTO detail(String memberEmail) {
MemberEntity member = memberRepository.findByMemberEmail(memberEmail);
MemberDTO memberDTO = modelMapper.map(member, MemberDTO.class);
return memberDTO;
}
}
@RequiredArgsConstructor
생성자 자동 생성 및 final 변수를 의존관계를 자동으로 설정해 준다.
LoginController
/*
설명 : login 페이지 영역
입력값 : /login
출력값 : login/form
작성일 : 24.03.05
작성자 : 정아름
수정사항 : 아이디 찾기 & 비밀번호 찾기 - 이메일 전송 방식으로 수정.
*/
package com.example.basic.Controller;
import com.example.basic.DTO.MemberDTO;
import com.example.basic.Service.LoginService;
import com.example.basic.Service.MemberService;
import lombok.RequiredArgsConstructor;
import org.springframework.security.crypto.password.PasswordEncoder;
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.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
@Controller
@RequiredArgsConstructor
public class LoginController {
private final LoginService loginService;
//로그인 페이지
@GetMapping("/login")
public String loginForm() {
return "login/form";
}
//아이디 찾기
@GetMapping("/find/email")
public String email() {
return "login/email";
}
@PostMapping("/find/email")
public String emailProc(Model model, String memberName, String memberPassword,
BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
return "redirect:/find/email";
}
//회원 비밀번호, 이름 조회
MemberDTO memberDTO = (MemberDTO) loginService.findEmail(memberPassword, memberName);
if (memberDTO == null) {
bindingResult.reject("Not found", "비밀번호 또는 이름이 일치하지 않습니다.");
//이메일 찾기 페이지로 이동
return "redirect:/find/email";
}
model.addAttribute("data", memberDTO);
//조회하는 값이 있으면 이메일 확인 페이지로 이동
return "login/resultemail";
}
}
MemberController
/*
설명 : 회원관리의 목록, 수정, 삭제, 조회로 이동하는 페이지 영역
입력값 : /member/list, /member/insert, /member/update, /member/delete, /member/detail
출력값 : member/list, member/insert, member/update, member/detail
작성일 : 24.02.22
작성자 : 정아름
수정사항 : 회원관리의 전체 목록 페이지는 page 처리 하기로 함
*/
package com.example.basic.Controller;
import com.example.basic.Constant.RoleType;
import com.example.basic.DTO.BoardDTO;
import com.example.basic.DTO.MemberDTO;
import com.example.basic.DTO.ReviewDTO;
import com.example.basic.Service.*;
import com.example.basic.Util.PaginationUtil;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.web.PageableDefault;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.crypto.password.PasswordEncoder;
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.PostMapping;
import java.util.List;
import java.util.Map;
@Controller
@RequiredArgsConstructor
public class MemberController {
//주입
private final MemberService memberService;
private final BoardService boardService;
private final ReviewService reviewService;
//전체 조회
@GetMapping("/member/list")
public String listForm(@PageableDefault(page = 1) Pageable pageable,
Model model) {
//페이지처리
Page<MemberDTO> memberDTOS = memberService.memberList(pageable);
Map<String, Integer> page = PaginationUtil.Pagination(memberDTOS);
model.addAllAttributes(page);
model.addAttribute("list", memberDTOS);
return "member/list";
}
//삽입
@GetMapping("/member/insert")
public String insertForm() {
return "member/insert";
}
@PostMapping("/member/insert")
public String insertProc(@Valid MemberDTO memberDTO, BindingResult bindingResult) {
//회원 검증
if (bindingResult.hasErrors()) {
//오류가 있으면 회원 가입페이지로 이동
return "redirect:/member/insert";
}
try {
memberDTO.setRoleType(RoleType.USER);
memberService.memberInsert(memberDTO);
} catch (Exception e) {
return "redirect:/member/insert";
}
return "redirect:/login";
}
//수정
@GetMapping("/member/update")
public String updateForm(Integer id, Model model) {
MemberDTO memberDTO = memberService.memberDetail(id);
model.addAttribute("data", memberDTO);
return "member/update";
}
@PostMapping("/member/update")
public String updateProc(MemberDTO memberDTO) {
memberService.memberUpdate(memberDTO);
return "redirect:/";
}
//삭제
@GetMapping("/member/delete")
public String deleteProc(Integer id) {
memberService.memberDelete(id);
return "redirect:/";
}
//개별 조회
@GetMapping("/member/detail")
public String readProc(Integer id, Model model) {
MemberDTO memberDTO = memberService.memberDetail(id);
model.addAttribute("data", memberDTO);
return "member/detail";
}
//마이페이지
@GetMapping("/mypage")
public String myPageForm(@AuthenticationPrincipal User user,
Model model) {
//보안 인증 된 유저의 이메일로 회원 정보 찾기
String memberEmail = user.getUsername();
MemberDTO memberDTO = memberService.detail(memberEmail);
List<BoardDTO> boardDTO = boardService.memberBoard(memberDTO.getMemberId());
List<ReviewDTO> reviewDTO = reviewService.memberReview(memberDTO.getMemberId());
model.addAttribute("board", boardDTO);
model.addAttribute("review", reviewDTO);
model.addAttribute("data", memberDTO);
return "member/mypage";
}
}
@RequiredArgsConstructor
생성자 자동 생성 및 final 변수를 의존관계를 자동으로 설정해 준다.
@GetMapping
@RequestMapping(Method=RequestMethod.GET)과 같다.
@PostMapping
@RequestMapping(Method=RequestMethod.POST)과 같다.
@AuthenticationPrincipal
세션 정보 UserDetails에 접근할 수 있는 정보
추가사항
회원 아이디나 비번을 찾을 때 바로 알려주는 로직으로 짰다가 security 까지 쓰는데 좀 그래서...
방법을 찾던 중,
임시 비밀 번호를 생성하여 javamailsender로 메일 전송해주는 로직으로 변경하였다.
MailService
package com.example.basic.Service;
import com.example.basic.DTO.EmailDTO;
import com.example.basic.Entity.MemberEntity;
import com.example.basic.Repository.LoginRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import org.thymeleaf.spring6.SpringTemplateEngine;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
@Service
@RequiredArgsConstructor
@Slf4j
public class MailService {
private final LoginRepository loginRepository;
private final JavaMailSender javaMailSender;
//임시 비밀번호 생성
public String createRandomPw() {
String[] strings = new String[]{
"0", "1", "2", "3", "4", "5", "6", "7", "8", "9",
"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N",
"O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z",
"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n",
"o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"
};
//임시 비밀번호 변수
String password = "";
//랜덤으로 값을 6개를 뽑아 조합
for (int i = 0; i < 6; i++) {
int random = (int) (strings.length * Math.random());
password += strings[random];
}
return password;
}
//html 메일 전송
public void sendEmail(EmailDTO emailDTO) {
//단순 문자 메일 보낼 수 있는 객체
SimpleMailMessage message = new SimpleMailMessage();
try {
//메일 제목
message.setSubject(emailDTO.getTitle());
//수신자 메일 주소
message.setText(emailDTO.getEmail());
//메일 내용
message.setText(emailDTO.getContent());
javaMailSender.send(message);
log.info("SUCCESS");
} catch (Exception e) {
log.info("FAIL");
throw new RuntimeException(e);
}
}
//비밀번호 찾기
public Map<String, String> findPassword(String memberEmail, String memberName) {
//이메일과 이름이 존재하는지 조회
Optional<MemberEntity> memberEntity = loginRepository.
findByMemberEmailAndMemberName(memberEmail, memberName);
//조회한 데이터가 없으면
if (memberEntity.isEmpty()) {
//예외처리
throw new UsernameNotFoundException("이메일 또는 이름이 일치하지 않습니다.");
}
Map<String, String> result = new HashMap<>();
result.put("memberPassword", memberEntity.get().getMemberPassword());
return result;
}
public void memberCheck(String memberEmail) {
Optional<MemberEntity> member = loginRepository.findByMemberEmail(memberEmail);
if (member.isEmpty() && !member.get().getMemberEmail().equals(memberEmail)) {
throw new UsernameNotFoundException("존재하지 않는 회원입니다.");
}
}
}
MailController
package com.example.basic.Controller;
import com.example.basic.DTO.EmailDTO;
import com.example.basic.DTO.MemberDTO;
import com.example.basic.Service.MailService;
import com.example.basic.Service.MemberService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
@Controller
@RequiredArgsConstructor
public class MailController {
private final MailService mailService;
private final MemberService memberService;
//비밀번호 찾기
@GetMapping("/find/password")
public String findPassword() {
return "login/password";
}
@PostMapping("/find/password")
public String sendPassword(String memberEmail, String memberName) {
//회원 이메일, 이름 조회
MemberDTO memberDTO = (MemberDTO) mailService.findPassword(memberEmail, memberName);
//임시 비밀번호 생성
String password = mailService.createRandomPw();
//임시 비밀번호로 변경
memberDTO.setMemberPassword(password);
memberService.memberUpdate(memberDTO);
//회원 정보가 있으면
if (memberDTO != null) {
String content = "login/sendmail";
EmailDTO emailDTO = new EmailDTO();
//이메일 전송
emailDTO.setTitle("임시 비밀번호 발송(Test)");
emailDTO.setEmail(memberDTO.getMemberEmail());
emailDTO.setContent("임시 비밀번호는 '" + password +
"' 입니다. 로그인 후 반드시 비밀번호를 변경하세요!");
//emailDTO.setContent(content);
mailService.sendEmail(emailDTO);
}
//조회하는 값이 있으면 비밀번호 확인 페이지로 이동
return "login/resultpassword";
}
}
View - 회원가입, 로그인 페이지만 올림. 프론트 보단 백 위주로 작성했기에 기타 설명은 생략하겠다.
member/insert
<!--
파일명 : member/insert
작성자 : 정아름
작성일 : 24.02.21
수정사항 : 글씨체 적용
-->
<!DOCTYPE html>
<html lang="ko"
xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorate="~{layouts/main}"
xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<head>
<meta charset="UTF-8">
<title>SalPick</title>
<!-- bootstrap -->
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
<!-- google fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Nanum+Pen+Script&display=swap" rel="stylesheet">
<!-- google icon -->
<link rel="stylesheet"
href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@24,400,0,0"/>
<style>
body {
font-family: Verdana, sans-serif;
font-size: 15px;
}
h2 {
font-family: "Nanum Pen Script", cursive;
font-weight: 400;
font-size: 50px;
font-style: normal;
}
</style>
</head>
<body>
<div layout:fragment="content">
<div class="row">
<!-- 여백 -->
<div class="col-sm-3"></div>
<!-- 회원 가입 페이지 -->
<div class="col-sm-6">
<div class="container mt-5 mb-5">
<h2>회원 가입</h2>
<form th:action="@{/member/insert}" method="post">
<div class="mb-3 mt-3">
<label for="memberEmail" class="form-label">이메일 : </label>
<input type="email" class="form-control" name="memberEmail"
id="memberEmail">
<button onclick="validateEmail()">확인</button>
<div id="result"></div>
</div>
<div class="mb-3">
<label for="memberPassword" class="form-label">비밀번호 : </label>
<input type="password" class="form-control" name="memberPassword"
id="memberPassword">
</div>
<div class="mb-3">
<label for="memberName" class="form-label">회원명 : </label>
<input type="text" class="form-control" name="memberName"
id="memberName">
</div>
<div class="mb-3">
<label for="memberPhone" class="form-label">전화번호 : </label>
<input type="text" class="form-control" name="memberPhone"
id="memberPhone">
</div>
<div class="mb-3">
<label for="memberAddress" class="form-label">주소 : </label>
<input type="text" class="form-control" name="memberAddress"
id="memberAddress">
</div>
<button type="submit" class="btn btn-outline-warning float-end">
회원 가입
</button>
</form>
</div>
</div>
<!-- 여백 -->
<div class="col-sm-3"></div>
</div>
</div>
<th:block layout:fragment="script">
<script type="text/javascript">
function emailCheck(memberEmail) {
var memberEmail_regex = /^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-z]{2,4}$/i;
if (!memberEmail_regex.test(memberEmail)) {
return false;
} else {
return true;
}
}
function validateEmail() {
var emailInput = document.getElementById('memberEmail');
var resultDiv = document.getElementById('result');
var email = emailInput.value;
if (emailCheck(email)) {
alert(resultDiv.innerHTML = '유효한 이메일 주소입니다.');
} else {
alert(resultDiv.innerHTML = '유효하지 않은 이메일 주소입니다.');
}
}
</script>
</th:block>
</body>
</html>
view에서도 역시 이메일 형식을 확인할 수 있게 pattern 방식으로 유효성 검사를 하였다.
login/form
<!--
파일명 : login/form
작성자 : 정아름
작성일 : 24.02.22
수정사항 : 글씨체 적용함
폼 생성 완료. 배경 및 이미지, 아이콘 등 확인! - 완료. 더 추가할 거 있나 확인바람.
-->
<!DOCTYPE html>
<html lang="ko"
xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
xmlns:sec="http://www.thymeleaf.org/extras/spring-security"
layout:decorate="~{layouts/main}">
<head>
<meta charset="UTF-8">
<title>SalPick</title>
<!-- bootstrap -->
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
<!-- google fonts -->
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Sofia">
<!-- google icon -->
<link rel="stylesheet"
href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@24,400,0,0"/>
<style>
.wrapper {
display: grid;
place-items: center;
min-height: 100dvh;
font-family: Ubuntu, sans-serif;
font-size: 15px;
font-style: normal;
background-color: ivory;
}
h2 {
font-size: 30px;
font-style: oblique;
font-family: Sofia, sans-serif;
font-weight: bold;
text-shadow: 3px 3px 3px #ababab;
}
</style>
</head>
<body>
<div layout:fragment="content">
<div class="row wrapper">
<div class="col-4"></div>
<div class="col-4">
<div class="container p-2 my-2 bg-success text-white text-center">
<h2>login</h2>
</div>
<div class="container p-5 my-5 border border-2 bg-white">
<form th:action="@{/login}" method="post">
<div th:if="${param.error}">
<div class="alert alert-danger">
사용자 ID 또는 비밀번호를 확인해 주세요.
</div>
</div>
<div class="mb-3">
<label for="memberEmail"> Email</label>
<input type="email" class="form-control" id="memberEmail" name="memberEmail">
</div>
<div class="mb-3">
<label for="memberPassword"> Password </label>
<input type="password" class="form-control" id="memberPassword" name="memberPassword">
</div>
<div class="form-check mb-3">
<label class="form-check-label">
<input class="form-check-input" type="checkbox" name="remember"> 자동로그인
</label>
</div>
<br>
<button type="submit" class="btn btn-outline-success float-end">
로그인
</button>
<br>
</form>
</div>
<div class="container">
<ul class="nav justify-content-center">
<li class="nav-item">
<a class="nav-link" th:href="@{/member/insert}">회원 가입</a>
</li>
<li class="nav-item">
<a class="nav-link" th:href="@{/find/email}">아이디 찾기</a>
</li>
<li class="nav-item">
<a class="nav-link" th:href="@{/find/password}">비밀번호 찾기</a>
</li>
</ul>
</div>
</div>
<div class="col-4"></div>
</div>
</div>
</body>
</html>
화면 캡쳐
'개발일지' 카테고리의 다른 글
Team project - 쇼핑몰 구현(3) (0) | 2024.04.29 |
---|---|
Team project - 쇼핑몰 구현(2) (0) | 2024.04.29 |
Team project - 쇼핑몰 구현 (0) | 2024.04.22 |
Spring Security - 회원가입 강화 (0) | 2024.04.16 |
PaginationUtil (0) | 2024.04.11 |