728x90
320x100
[참고]
김영한님 스프링 강의를 바탕으로 진행되는 토이프로젝트의 과정을 기록하는 글입니다.
둥근 피드백은 언제나 환영입니다.
[오늘의 개발내용]
1. 로그인 구현을 위한 도메인(객체), 서비스, 컨트롤러 생성
2.로그인 인증 인터셉터 만들기
3.인터셉터 등록하기
4. 각종 html 생성
5. 각종 html 수정
[서론]
쿠키, 세션을 활용하여 로그인을 구현하는 방법은 크게 필터와 인터셉터가 있는데
필터는 서블릿 전에 작동되고 인터셉터는 서블릿 후에 작동된다.
필터는 서블릿에서 제공하는 기능이고 인터셉터는 스프링 MVC에서 제공하는 기능인데
둘 다 기능은 비슷하지만 인터셉터가 조금 더 제공되는 기능들이 많아 인터셉터를 사용하기로 한다.
1. 로그인 구현을 위한 도메인(객체), 서비스, 컨트롤러 생성
loginForm.java
package toyproject.bookbookclub.domain.login;
import jakarta.validation.constraints.NotEmpty;
import lombok.Data;
@Data
public class LoginForm {
@NotEmpty
private String loginId;
@NotEmpty
private String password;
}
- @NotEmpty : Bean Validation 을 사용했음.
loginService.java
package toyproject.bookbookclub.domain.login;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import toyproject.bookbookclub.domain.Members.Member;
import toyproject.bookbookclub.domain.Members.MemberRepository;
@Service
@RequiredArgsConstructor
public class LoginService {
private final MemberRepository memberRepository;
/**
* @return null이면 로그인 실패 */
public Member login(String loginId, String password) {
return memberRepository.findByLoginId(loginId)
.filter(m -> m.getPassword().equals(password))
.orElse(null);
}
}
(+)MemberRepository.java
public Optional<Member> findByLoginId(String loginId) {
return findAll().stream()
.filter(m -> m.getId().equals(loginId))
.findFirst();
}
- findByLoginId() 메서드 생성
loginController.java
package toyproject.bookbookclub.web.login;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
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 toyproject.bookbookclub.domain.Members.Member;
import toyproject.bookbookclub.domain.login.LoginForm;
import toyproject.bookbookclub.domain.login.LoginService;
@Slf4j
@Controller
@RequiredArgsConstructor
public class loginController {
private final LoginService loginService;
@GetMapping("/login")
public String loginForm(@ModelAttribute("loginForm") LoginForm form) {
return "login/loginForm";
}
@PostMapping("/login")
public String login(@Valid @ModelAttribute LoginForm form, BindingResult
bindingResult) {
if (bindingResult.hasErrors()) {
return "login/loginForm";
}
Member loginMember = loginService.login(form.getLoginId(),
form.getPassword());
log.info("login? {}", loginMember);
if (loginMember == null) {
bindingResult.reject("loginFail", "아이디 또는 비밀번호가 맞지 않습니다.");
return "login/loginForm";
}
//로그인 성공 처리 TODO
return "redirect:/timeline/allTimeline";
}
}
@GetMapping("/login")
public String loginForm(@ModelAttribute("loginForm") LoginForm form) {
return "login/loginForm";
}
-> 로그인 페이지 띄우는 컨트롤러
@PostMapping("/login")
public String login(@Valid @ModelAttribute LoginForm form, BindingResult
bindingResult) {
if (bindingResult.hasErrors()) {
return "login/loginForm";
}
Member loginMember = loginService.login(form.getLoginId(),
form.getPassword());
log.info("login? {}", loginMember);
if (loginMember == null) {
bindingResult.reject("loginFail", "아이디 또는 비밀번호가 맞지 않습니다.");
return "login/loginForm";
}
//로그인 성공 처리 TODO
return "redirect:/timeline/allTimeline";
}
-> 로그인 했을 때 띄우는 컨트롤러
- 로그인이 실패하면 로그인 페이지에서 이동하지 않고 에러페이지만 띄움
- 로그인이 성공하면 전체 타임라인 페이지로 이동.
2. 로그인 인증 인터셉터 만들기
LoginCheckInterceptor.java
package toyproject.bookbookclub.web.interceptor;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import toyproject.bookbookclub.web.SessionConst;
@Slf4j
public class LoginCheckInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String requestURI = request.getRequestURI();
log.info("인증 체크 인터셉터 실행 {}", requestURI);
HttpSession session = request.getSession(false);
if (session == null || session.getAttribute(SessionConst.LOGIN_MEMBER) == null) {
log.info("미인증 사용자 요청");
//로그인으로 redirect
response.sendRedirect("/login?redirectURL=" + requestURI);
return false;
}
return true;
}
}
(+) SessionConst.java
package toyproject.bookbookclub.web;
public class SessionConst {
public static final String LOGIN_MEMBER = "loginMember";
}
- HttpSession에 데이터를 보관하고 조회할 때, 같은 이름이 중복 되어 사용되므로, 상수를 하나 정의함.
3. 인터셉터 등록하기
WebConfig.java
package toyproject.bookbookclub.web;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import toyproject.bookbookclub.web.interceptor.LogInterceptor;
import toyproject.bookbookclub.web.interceptor.LoginCheckInterceptor;
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LogInterceptor())
.order(1)
.addPathPatterns("/**")
.excludePathPatterns("/css/**", "/*.ico", "/error");
registry.addInterceptor(new LoginCheckInterceptor())
.order(2)
.addPathPatterns("/**")
.excludePathPatterns(
"/", "/basic/members/join", "/login", "/logout",
"/css/**", "/*.ico", "/error"
);
}
}
- excludePathPatterns : 해당 주소는 제외하고 인터셉터를 건다.
- 북북클럽의 경우 첫 화면이 로그인 페이지가 될 것이고 로그인을 하지 않으면 볼 수 없는 구조로 되어 있기 때문에 위와 같이 설정하였다.
4. 각종 html 생성
home.html
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8">
<link th:href="@{/css/bootstrap.min.css}"
href="css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container" style="max-width: 600px">
<div class="py-5 text-center">
<h2>북북클럽</h2> </div>
<div class="row">
<div class="col">
<button class="w-100 btn btn-secondary btn-lg" type="button"
th:onclick="|location.href='@{/basic/members/join}'|">
회원 가입
</button>
</div>
<div class="col">
<button class="w-100 btn btn-dark btn-lg"
onclick="location.href='members.html'"
th:onclick="|location.href='@{/login}'|" type="button">
로그인
</button>
</div>
</div>
<hr class="my-4">
</div> <!-- /container -->
</body>
</html>
가장 첫화면이 될 것이고 회원가입클릭시 -> 회원가입 화면으로 이동 로그인 클릭시 -> 로그인 화면으로 이동하도록 하였다.
loginForm.html
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8">
<link th:href="@{/css/bootstrap.min.css}" href="../css/bootstrap.min.css" rel="stylesheet">
<style>
.container {
max-width: 560px;
}
.field-error {
border-color: #dc3545;
color: #dc3545;
}
</style>
</head>
<body>
<div class="container">
<div class="py-5 text-center"> <h2>로그인</h2>
</div>
<form action="item.html" th:action th:object="${loginForm}" method="post">
<div th:if="${#fields.hasGlobalErrors()}">
<p class="field-error" th:each="err : ${#fields.globalErrors()}"
th:text="${err}">전체 오류 메시지</p>
</div>
<div>
<label for="loginId">로그인 ID</label>
<input type="text" id="loginId" th:field="*{loginId}" class="form-control"
th:errorclass="field-error">
<div class="field-error" th:errors="*{loginId}" />
</div>
<div>
<label for="password">비밀번호</label>
<input type="password" id="password" th:field="*{password}"
class="form-control"
th:errorclass="field-error">
<div class="field-error" th:errors="*{password}" />
</div>
<hr class="my-4">
<div class="row">
<div class="col">
<button class="w-100 btn btn-primary btn-lg" type="submit">로그인
</button>
</div>
<div class="col">
<button class="w-100 btn btn-secondary btn-lg"
onclick="location.href='items.html'"
th:onclick="|location.href='@{/}'|" type="button">취소</button>
</div>
</div>
</form>
</div> <!-- /container -->
</body>
</html>
- 로그인 화면.
- 로그인 성공시 -> 전체 타임라인 화면으로 이동하고, 로그인 실패시 페이지는 이동하지 않고 에러항목을 띄워준다.
[로그인 실패시 화면]
5. html 수정
joinForm.html
<div class="row">
<div class="col">
<button class="w-100 btn btn-primary btn-lg" type="submit" th:text="#{button.member.save}">회원 가입</button> </div>
<div class="col">
<button class="w-100 btn btn-secondary btn-lg"
onclick="location.href='members.html'"
th:onclick="|location.href='@{/}'|" type="button"
th:text="#{button.cancel}">취소</button>
</div>
</div>
- 기존에는 취소를 누르면 회원 목록으로 갔었는데 지금은 home.html으로 가도록 수정.
728x90
320x100
'💻 뚝딱뚝딱 > (구) 북북클럽' 카테고리의 다른 글
[개발일지#027] 회원 컬럼 추가하기 / 회원가입 화면 수정 (유효성검사 등) (0) | 2024.03.19 |
---|---|
[개발일지#026] 파일첨부 기능 만들기 (회원 프로필 이미지) (+) 피드백 반영 (2) | 2024.03.15 |
[개발일지#023] Validation 설정하기 (서버검증) (0) | 2024.03.08 |
[개발일지#022] 스프링 메시지화, 국제화 적용하기 (2) | 2024.03.07 |
[개발일지#021] 타임리프 th:object, th:field 적용하기 (0) | 2024.03.06 |