2024.01.07 - [Spring Boot] - [Spring Boot] 로그인 기능 구현 (0) - 공통 기능, 코드 구현
1. 세션 (Session) 이란 ?
- 웹사이트를 이용할 때 쓰는 프로토콜인 HTTP는 stateless '무상태' 임
- stateless : 서버로 전달되는 모든 request는 이전 request와는 독립적으로 다뤄짐 (관계없음)
- 따라서 request가 끝나면 서버는 클라이언트가 누구인지 모르기 때문에 모든 request에 클라이언트의 정보를 넘겨줘야 함
- 이를 수행하는 방법 중 하나가 세션 (Session) 임
- 즉, 세션이란 서버에서 클라이언트의 상태를 유지하기 위한 메커니즘
- 회원가입을 하면 서버는 세션 DB에 해당 계정의 정보를 저장함 (세션은 고유한 세션 ID가 존재)
- 세션 ID는 쿠키와 같은 매개채를 통해서 브라우저, 클라이언트에 전달, 저장됨
- 브라우저는 해당 페이지에서 다른 request를 할 때마다 세션 ID가 포함된 쿠키를 매번 서버에 전달함
- 서버에서는 매번 세션 ID를 통해서 세션 DB에 데이터가 있는지 확인함
- 이로 인한 단점은
- request가 발생할 때마다 DB에서 데이터를 검색해야 함 => 데이터가 많을 수록 비효율적
- 사용자가 많아지면 그에 따른 세션 ID를 저장해야할 세션 DB의 용량이 커지게 됨
2. 세션 생성, 추가, 읽기
- HttpSession 인터페이스를 사용
2-1. 세션 생성
- 자동으로 세션 생성
- 웹 애플리케이션에서 세션은 사용자의 첫 요청이 들어올 때 자동으로 생성됨
- 명시적으로 세션 생성
- HttpServletRequest 객체를 통해 세션 생성
@RequestMapping("")
public String session(HttpServletRequest request){
// request 에 대한 세션을 가져옴
// true : 세션이 없다면 새로운 세션 생성해서 리턴
// false : 세션이 없다면 null 리턴
HttpSession session = request.getSession(true);
...
}
2-2. 세션 속성 설정
- setAttribute 메서드는 "키", "값" 쌍의 형태로 세션에 저장함
- 저장된 속성은 세션의 유효 기간 동안 유지, 같은 사용자의 다른 요청에서도 사용 가능
// 키, 값 쌍으로 세션에 속성 추가
session.setAttribute("키", "값");
// 세션의 유효 기간 설정 => 1시간
session.setMaxInactiveInterval(60 * 60);
2-3. 세션 정보 읽기
- getAttribute
@RequestMapping("")
public String getSessionAttribute(HttpSession session){
// 세션에 담겨있는 속성 중 "키"라는 이름의 "값"을 가져옴
String value = session.getAttribute("key");
...
}
- @SessionAttribute
@RequestMapping("")
public String getSessionAttribute(@SessionAttribute(name = "키", required = false) String value){
// @SessionAttribute 를 통해서 value 변수 안에 세션의 속성 중 "키" 이름을 가진 값이 저장됨
...
}
2-4. 세션 삭제
- 세션 삭제 방법은 크게 두 가지
- 세션의 모든 속성을 제거하는 방법
- 세션을 무효화 하는 방법
2-4-1) 세션 속성 제거
// 세션에서 "키"라는 이름의 세션 속성 제거
session.removeAttribute("키");
2-4-2) 세션 무효화
// 세션 무효화 : 세션에 저장된 모든 데이터 삭제, 세션 종료
session.invalidate();
3. 로그인 프로젝트에 적용
- 로그인을 제외한 나머지 기능, 라이브러리 등은 아래 게시글 참고
2024.01.07 - [Spring Boot] - [Spring Boot] 로그인 기능 구현 (0) - 공통 기능, 코드 구현
3-1. SessionLoginController
- home
@GetMapping(value = {"", "/"})
public String home(Model model, @SessionAttribute(name = "memberId", required = false) Long memberId) {
// 화면에 출력하기 위해 model에 속성 추가
model.addAttribute("loginType", "session-login");
model.addAttribute("pageName", "세션로그인");
Member loginMember = memberService.getLoginMemberById(memberId);
// 로그인 되어있다면 model에 이름 속성 추가
if (loginMember != null) {
model.addAttribute("name", loginMember.getName());
}
return "home";
}
- @SessionAttribute를 통해서 세션의 값 읽어옴
- 세션의 name에 해당하는 값이 존재한다면 로그인 한 것 => model에 속성 추가
- 존재하지 않으면 로그인 하지 않은 것 => model에 name이 null로 들어있음
- join
@GetMapping("/join")
public String joinPage(Model model) {
model.addAttribute("loginType", "session-login");
model.addAttribute("pageName", "세션로그인");
// 회원가입을 위해서 model 통해서 joinRequest 전달
model.addAttribute("joinRequest", new JoinRequest());
return "join";
}
@PostMapping("/join")
public String join(@Valid @ModelAttribute JoinRequest joinRequest,
BindingResult bindingResult, Model model) {
model.addAttribute("loginType", "session-login");
model.addAttribute("pageName", "세션로그인");
// ID 중복 여부 확인
if (memberService.checkLoginIdDuplicate(joinRequest.getLoginId())) {
bindingResult.addError(new FieldError("joinRequest", "loginId", "ID가 존재합니다."));
}
// 비밀번호 = 비밀번호 체크 여부 확인
if (!joinRequest.getPassword().equals(joinRequest.getPasswordCheck())) {
bindingResult.addError(new FieldError("joinRequest", "passwordCheck", "비밀번호가 일치하지 않습니다."));
}
// 에러가 존재할 시 다시 join.html로 전송
if (bindingResult.hasErrors()) {
return "join";
}
// 에러가 존재하지 않을 시 joinRequest 통해서 회원가입 완료
memberService.join(joinRequest);
// 회원가입 시 홈 화면으로 이동
return "redirect:/session-login";
}
- 쿠키 사용한 로그인과 동일 ( https://blogan99.tistory.com/84 )
- login
@GetMapping("/login")
public String loginPage(Model model) {
model.addAttribute("loginType", "session-login");
model.addAttribute("pageName", "세션로그인");
model.addAttribute("loginRequest", new LoginRequest());
return "login";
}
@PostMapping("/login")
public String login(@ModelAttribute LoginRequest loginRequest, BindingResult bindingResult,
HttpServletRequest request, Model model) {
model.addAttribute("loginType", "session-login");
model.addAttribute("pageName", "세션로그인");
Member member = memberService.login(loginRequest);
// ID나 비밀번호가 틀린 경우 global error return
if (member == null) {
bindingResult.reject("loginFail", "로그인 아이디 또는 비밀번호가 틀렸습니다.");
}
if (bindingResult.hasErrors()) {
return "login";
}
// ===== 로그인 성공 => 세션 생성 및 속성 설정 =====
// 기존의 세션을 무효화
request.getSession().invalidate();
// 세션 생성 => request에 연관된 세션이 없을 시 새로운 세션 생성 후 반환
HttpSession session = request.getSession(true);
// 세션에 {"memberId", memberId} 속성 추가
session.setAttribute("memberId", member.getId());
// 세션의 유효기간을 1시간으로 설정
session.setMaxInactiveInterval(60 * 60);
// =========================================
return "redirect:/session-login";
}
- @ModelAttribute로 받아온 데이터로 로그인 진행
- 쿠키 로그인과 동일 ( https://blogan99.tistory.com/84 )
- 로그인 성공 시 세션 생성, 속성 설정
- 세션에 Id값 저장, 유효기간 1시간으로 설정
- logout
@GetMapping("/logout")
public String logout(HttpServletRequest request, Model model) {
model.addAttribute("loginType", "session-login");
model.addAttribute("pageName", "세션로그인");
// request와 연관된 세션 불러옴 (없으면 null 반환)
HttpSession session = request.getSession(false);
// 세션이 존재 ( : 로그인 되어있다는 뜻)
if (session != null) {
// 로그인 된 세션 무효화
session.invalidate();
}
return "redirect:/session-login";
}
- session.invalidate()를 통해서 로그아웃 구현
- info
@GetMapping("/info")
public String memberInfo(@SessionAttribute(name = "memberId", required = false) Long memberId, Model model) {
model.addAttribute("loginType", "session-login");
model.addAttribute("pageName", "세션로그인");
Member loginMember = memberService.getLoginMemberById(memberId);
if (loginMember == null) {
return "redirect:/session-login/login";
}
model.addAttribute("member", loginMember);
return "info";
}
- admin
@GetMapping("/admin")
public String adminPage(@SessionAttribute(name = "memberId", required = false) Long memberId, Model model) {
model.addAttribute("loginType", "session-login");
model.addAttribute("pageName", "세션 로그인");
Member loginMember = memberService.getLoginMemberById(memberId);
if(loginMember == null) {
return "redirect:/session-login/login";
}
if(!loginMember.getRole().equals(MemberRole.ADMIN)) {
return "redirect:/session-login";
}
return "admin";
}
}
4. 실행결과
- 홈 화면
- 회원가입 화면
- 회원가입 화면 (오류)
- 로그인 화면
- 로그인 화면 (오류)
- 로그인 후 홈 화면
- 마이 페이지
- 관리자 계정으로 로그인 후 관리자 페이지
- 쿠키의 값이 변경됨
- 로그인 함으로써 세션ID가 쿠키를 통해서 전달됨을 확인
'Spring Boot' 카테고리의 다른 글
[Spring Boot] 스프링으로 엑셀 파일 읽기 (1) | 2024.01.08 |
---|---|
[Spring Boot] 로그인 기능 구현 (3) - 스프링 시큐리티 로그인 (4) | 2024.01.07 |
[Spring Boot] 로그인 기능 구현 (1) - 쿠키 로그인 (0) | 2024.01.07 |
[Spring Boot] 로그인 기능 구현 (0) - 공통 기능, 코드 구현 (2) | 2024.01.07 |
[Spring Boot] 페이징 기능 구현 ( + 페이징, 정렬, 검색, 에러 메시지 포함 예제) (1) | 2024.01.03 |