반응형
1. AccountController의 emailLoginForm메서드, sendEmailLoginLink 메서드, loginByEmail 메서드 추가
//패스워드 변경을 위한 이메일 로그인 연결
@GetMapping("/email-login")
public String emailLoginForm() {
return "account/email-login";
}
//패스워드 변경을 위한 이메일 로그인
@PostMapping("/email-login")
public String sendEmailLoginLink(String email, Model model, RedirectAttributes attributes) {
Account account = accountRepository.findByEmail(email);
if (account == null) {
model.addAttribute("error", "유효한 이메일 주소가 아닙니다.");
return "account/email-login";
}
if (!account.canSendConfirmEmail()) {
model.addAttribute("error", "이메일로 메일 발송은 1시간마다 한 번 사용 가능합니다.");
//return "account/email-login";
}
accountService.sendLoginLink(account);
attributes.addFlashAttribute("message", "이메일 인증 메일을 발송했습니다.");
return "redirect:/email-login";
}
//이메일을 통한 링크로 패스워드 변경
@GetMapping("/login-by-email")
public String loginByEmail(String token, String email, Model model) {
Account account = accountRepository.findByEmail(email);
String view = "account/logged-in-by-email";
//계정이 null이거나 유효한 토큰이 아닐 경우 -> 로그인 불가
if (account == null || !account.isValidToken(token)) {
model.addAttribute("error", "로그인할 수 없습니다.");
return view;
}
//유효한 토큰일 경우 로그인 처리
accountService.login(account);
return view;
}
2. AccountService의 sendLoginLink메서드 추가
public void sendLoginLink(Account account) {
//토큰을 이메일로 로그인 가능하게 하는 토큰을 만든다.
account.generateEmailCheckToken();
SimpleMailMessage mailMessage = new SimpleMailMessage();
mailMessage.setTo(account.getEmail());
mailMessage.setSubject("Demo, 로그인 링크");
mailMessage.setText("/login-by-email?token=" + account.getEmailCheckToken() +
"&email=" + account.getEmail());
javaMailSender.send(mailMessage);
}
3. 스프링 시큐리티 예외 추가
SecurityConfig 변경 전
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
// 오버라이딩을 통해 원하는 요청은 시큐리티 체크를 하지 않도록 설정
return http.authorizeRequests()
// 모듈에서 걸러내야 하는 요청들
.mvcMatchers("/", "/login", "/sign-up", "/check-email-token", "/email-login", "/check-email-login",
"login-link","/login-by-email")
// 재전송 기능 만들때에는 "/check-email" permitall에서 제외, 로그인한 사용자만 접근해야하므로
.permitAll()
// .mvcMatchers("/", "/login", "/sign-up", "/check-email", "/check-email-token",
// "/email-login", "/check-email-login", "login-link", "/profile/*").permitAll()
// -> 프로필에 붙어있는 모든 요청은 get만 허용
.mvcMatchers(HttpMethod.GET, "/profile/*").permitAll()
// 그외에 나머지 요청의 경우 모두 로그인이 필요하다.
.anyRequest().authenticated()
// 스프링 시큐리티가 제공하는 로그인 페이지 그대로 사용한다면
// http.formLogin();
// 커스텀한 로그인 페이지를 통해 로그인 처리를 한다면
.and().formLogin().loginPage("/login").permitAll()
// permitAll을 주는 이유는, 로그인한 사용자도, 로그인 안한 사용자도 접근 가능하도록 하기 위해
// 로그아웃 처리를 위해 -> 로그아웃 성공시 index 페이지로 이동
.and().logout().logoutSuccessUrl("/").and()
// 로그인 기억처리를 위해 -> userDetails에서 토큰을 가져온다. -> 빈을 이용한 의존성 주입
// 토큰값을 읽어오고 저장하는 인터페이스 필요 -> tokenRepository()
.rememberMe().userDetailsService(accountService).tokenRepository(tokenRepository()).and().build();
// 키만을 이용해 인증 (안전하지않음)
// .rememberMe().key("sdfsfsdfsd");
}
변경 후 (/check-email-login 추가)
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
// 오버라이딩을 통해 원하는 요청은 시큐리티 체크를 하지 않도록 설정
return http.authorizeRequests()
// 모듈에서 걸러내야 하는 요청들
.mvcMatchers("/", "/login", "/sign-up", "/check-email-token",
"/email-login", "/check-email-login", "/login-link", "/login-by-email")
// 재전송 기능 만들때에는 "/check-email" permitall에서 제외, 로그인한 사용자만 접근해야하므로
.permitAll()
// .mvcMatchers("/", "/login", "/sign-up", "/check-email", "/check-email-token",
// "/email-login", "/check-email-login", "login-link", "/profile/*").permitAll()
// -> 프로필에 붙어있는 모든 요청은 get만 허용
.mvcMatchers(HttpMethod.GET, "/profile/*").permitAll()
// 그외에 나머지 요청의 경우 모두 로그인이 필요하다.
.anyRequest().authenticated()
// 스프링 시큐리티가 제공하는 로그인 페이지 그대로 사용한다면
// http.formLogin();
// 커스텀한 로그인 페이지를 통해 로그인 처리를 한다면
.and().formLogin().loginPage("/login").permitAll()
// permitAll을 주는 이유는, 로그인한 사용자도, 로그인 안한 사용자도 접근 가능하도록 하기 위해
// 로그아웃 처리를 위해 -> 로그아웃 성공시 index 페이지로 이동
.and().logout().logoutSuccessUrl("/").and()
// 로그인 기억처리를 위해 -> userDetails에서 토큰을 가져온다. -> 빈을 이용한 의존성 주입
// 토큰값을 읽어오고 저장하는 인터페이스 필요 -> tokenRepository()
.rememberMe().userDetailsService(accountService).tokenRepository(tokenRepository()).and().build();
// 키만을 이용해 인증 (안전하지않음)
// .rememberMe().key("sdfsfsdfsd");
}
4. email-login 페이지, logged-in-by-email 페이지 추가
email-login
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head th:replace="fragments.html :: head"></head>
<body class="bg-light">
<div th:replace="fragments.html :: main-nav"></div>
<div class="container">
<div class="py-5 text-center">
<p class="lead">데모</p>
<h2>패스워드 재설정</h2>
</div>
<div class="row justify-content-center">
<div th:if="${error}" class="alert alert-danger alert-dismissible fade show mt-3" role="alert">
<span th:text="${error}">완료</span>
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<div th:if="${message}" class="alert alert-info alert-dismissible fade show mt-3" role="alert">
<span th:text="${message}">완료</span>
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<form class="needs-validation col-sm-6" action="#" th:action="@{/email-login}" method="post" novalidate>
<div class="form-group">
<label for="email">가입 할 때 사용한 이메일</label>
<input id="email" type="email" name="email" class="form-control"
placeholder="your@email.com" aria-describedby="emailHelp" required>
<small id="emailHelp" class="form-text text-muted">
가입할 때 사용한 이메일을 입력하세요.
</small>
<small class="invalid-feedback">이메일을 입력하세요.</small>
</div>
<div class="form-group">
<button class="btn btn-success btn-block" type="submit"
aria-describedby="submitHelp">로그인 링크 보내기</button>
<small id="submitHelp" class="form-text text-muted">
데모에 처음 오신거라면 <a href="#" th:href="@{/findpassword}">계정을 먼저 만드세요.</a>
</small>
</div>
</form>
</div>
<div th:replace="fragments.html :: footer"></div>
</div>
<script th:replace="fragments.html :: form-validation"></script>
</body>
</html>
logged-in-by-email
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head th:replace="fragments.html :: head"></head>
<body class="bg-light">
<nav th:replace="fragments.html :: main-nav"></nav>
<div class="container">
<div class="py-5 text-center" th:if="${error}">
<p class="lead">데모 패스워드 찾기</p>
<div class="alert alert-danger" role="alert" th:text="${error}">
로그인 할 수 없습니다.
</div>
</div>
<div class="py-5 text-center" th:if="${error == null}">
<p class="lead">데모 패스워드 찾기</p>
<h2>이메일을 통해 임시 로그인 했습니다. <a th:href="@{/settings/password}">패스워드를 변경</a>하세요.</h2>
</div>
</div>
</body>
</html>
반응형
'Server Programming > Spring Boot Full-Stack Programming' 카테고리의 다른 글
[Spring Boot] 공부하기 (0) | 2022.09.29 |
---|---|
[스프링 풀스택 클론 코딩 - 계정 설정] (2-10) 패스워드 찾기 (0) | 2022.09.05 |
[스프링 풀스택 클론 코딩 - 계정 설정] (2-9) 닉네임 변경 (0) | 2022.09.05 |
인텔리J 단축키 (0) | 2022.09.05 |
[스프링 풀스택 클론 코딩 - 계정 설정] (2-8) ModelMapper 적용 (0) | 2022.09.05 |