- 요구사항
홈 화면 요구사항
- 홈 화면은 로그인 전과 후로 구분되어야 한다.
- 로그인 전
- 로그인 후
보안 요구사항
- 로그인 사용자만 상품관리에 접근하고, 관리할 수 있어야 한다.
- 로그인 하지 않은 사용자가 상품관리에 접근하면 로그인 화면으로 이동해야 한다.
- 메인 홈 화면
- 로그인 하지 않았을 때 기본적으로 보여줄 홈 화면과 로그인 시 보여줄 홈 화면, 2개의 뷰를 따로 개발한다.
- 회원가입
- 검증기능을 포함한 Member 도메인 클래스를 만든다.
- MemberRepository에 회원 저장, 조회기능을 개발한다. 이전에 상품 도메인을 개발한 것과 매우 유사한 형태이다.
- 이제 MemberController를 개발한다.
- @GetMapping 요청이 들어오면 회원가입 폼을 반환해준다.
- @PostMapping 요청이 들어왔을 때, BindingResult 객체에 오류가 없으면 회원을 저장하고 홈 화면으로 리다이렉트 한다.
- 로그인
- 먼저 핵심 비지니스 로직인 로그인 기능을 LoginService에 개발할 것이다.
- id, password를 파라미터로 받았을 때 저장소에 해당 id를 가진 회원 객체를 찾고 비밀번호까지 같은지 확인하는 메소드를 LoginService에 작성한다.
- 이제 LoginController를 개발한다.
- @GetMapping 요청이 들어오면 로그인 폼을 반환해준다.
- @PostMapping 요청이 들어오면 @ModelAttribute로 LoginForm 타입으로 입력 폼 데이터를 받는다.
- 검증 로직을 거친다.
- 통과가 되면, 로그인 할 수 있는지 LoginService를 호출해 확인한다. --> Member 객체 반환
- 객체가 null이 아니고 정상적으로 뽑혔다면 로그인 성공이다.
- 이후 기존 홈 화면으로 리다이렉트
- 로그인 로직을 통과했지만 로그인 정보를 기억하고 있는 상태는 아니다.
- 로그인 상태 유지 (쿠키 사용)
- 로그인에 성공하면, 로그인 정보를 기억하고 새로운 홈 화면으로 이동하고 싶다.
- 새로운 홈 화면에서는 "안녕하세요, OOO님" 이라는 문구가 추가되고, 상품 관리 페이지로 이동하는 버튼과 로그아웃할 수 있는 버튼으로 구성된다.
- 먼저 쿠키를 사용해 로그인 정보를 기억해보자.
- LoginController에서 로그인에 성공하면 HttpServletResponse에 쿠키를 담고 HomeController로 리다이렉트 할 것이다.
- 쿠키에는 회원ID값을 담는다.
- 이제 HomeController에서는 쿠키값이 null이면 기존 홈 화면으로 렌더링하고 쿠키값이 존재한다면 로그인 처리가 된 새로운 홈 화면으로 렌더링 해준다.
- 이후 브라우저를 완전히 종료하거나 로그아웃을 하기 전까지는 로그인 처리가 된 홈 화면에 접속하게 된다.
- 쿠키를 사용한 로그아웃 방법은 요청이 들어올 때 해당 쿠키의 Maxage를 0으로 설정해 다시 응답으로 보내주면 된다.
- 쿠키와 보안 문제
- 쿠키 값은 개발자 도구에 들어가면 임의로 변경할 수 있다.
- 만약 쿠키값으로 회원ID를 넣어줬다면, 임의로 변경하면 다른 사용자로 로그인처리가 될 수 있는 것이다.
- 심각한 문제이다!
- 혹은 쿠키에 중요한 개인정보를 보관한다면 해커에 의해 털릴 수 있다.
- 따라서 쿠키에 중요한 값을 노출하지 않고, 예측할 수 없는 임의의 토큰을 노출하고 서버와 클라이언트는 이 임의의 토큰값으로 연결되어야 한다.
- 세션을 사용하면 해결된다.
- 로그인 상태 유지 (세션 사용)
- 앞서 이야기한 것처럼 쿠키에 중요한 정보를 보관하는 방법은 여러가지 보안적 이슈가 있다.
- 이 문제를 해결하기 위해서는 실제 중요한 정보는 모두 서버에 저장해야한다.
- 그리고 클라이언트와 서버는 추정 불가능한 임의의 식별자 값으로 연결해야 한다.
- 이렇게 서버에 중요한 정보를 보관하고 연결을 유지하는 방법을 세션이라고 한다.
- 이렇게 세션ID를 랜덤한 식별자 값으로 설정하고 해당 값을 쿠키에 저장해 클라이언트-서버간 연결을 유지하면 된다.
- 실제로 HttpSession을 사용하는 방법을 알아보자.
- request.getSession() : HttpSession 객체를 생성한다.
- 기본적으로 세션이 있으면 기존 세션을 반환하고, 세션이 없으면 새로운 세션을 생성해서 반환한다.
- create 옵션을 넣을 수 있는데 기본값은 true이다.
- true이면 위에서 이야기한 것처럼 세션이 없으면 새로운 세션을 생성해서 반환한다.
- false이면 새로운 새션을 생성하지 않는다. null을 반환한다.
- session.setAttribute() : 세션에 key-value 형태로 데이터를 저장한다. 하나의 세션의 여러개의 값을 저장할 수 있다.
- session.invalidate() : 세션을 제거한다.
- @SessionAttribute(name = "", required = ?) : 세션에 있는 값을 찾을 때 쓰면 간편한 애노테이션이다. @ModelAttribute처럼 사용할 수 있다. 참고로 이 기능은 세션을 생성하지 않는다. 세션에 있는 값을 찾을 때 쓸 수 있는 방법이다.
- 세션 정보와 타임아웃 설정
세션에 대한 여러가지 정보를 확인해보자.
@Slf4j
@RestController
public class SessionController {
@GetMapping("/session-info")
public String sessionInfo(HttpServletRequest request) {
HttpSession session = request.getSession(false);
if (session == null) {
return "세션이 없습니다.";
}
session.getAttributeNames().asIterator()
.forEachRemaining(name -> log.info("session name={}, value={}", name, session.getAttribute(name)));
log.info("sessionId={}", session.getId());
log.info("getMaxInactiveInterval={}", session.getMaxInactiveInterval());
log.info("creationTime={}", new Date(session.getCreationTime()));
log.info("lastAccessedTime={}", new Date(session.getLastAccessedTime()));
log.info("isNew={}", session.isNew());
return "세션 출력 완료";
}
}
세션이 있으면 해당 세션에 대한 정보들에 대한 로그를 남겨달라는 코드이다.
출력 결과를 확인해보자.
- session.getId() : 식별자 역할을 하는 세션ID를 얻을 수 있다.
- session.getMaxInactiveInterval() : 세션의 유효시간을 나타낸다. 기본으로 1800초이며, 클라이언트에서 서버로 sessionId를 요청한다면(쿠키를 가지고 HTTP 요청을 한다면) 해당 시간이 갱신된다.
- session.getCreateTime() : 세션이 생성된 시간을 표시해준다. 기본적으로 Long 타입으로 나오기 때문에 Date로 감싸주면 좋다.
- session.getLastAccessedTime() : 세션과 연결된 사용자가 가장 최근에 서버에 접근한 시간을 알려준다. 마찬가지로 Date로 감싸주면 좋다.
- session.isNew() : 새로 생성된 세션인지, 이미 존재하던 세션인지 알려준다.
세션의 타임아웃의 경우 생각해봐야 할 것들이 있다.
- 세션은 사용자가 직접 로그아웃 버튼을 눌러 session.invalidate()가 호출되는 경우에 삭제할 수 있다.
- 하지만 대부분의 사용자는 로그아웃을 누르지 않고 그냥 웹 브라우저를 종료한다.
- 하지만 서버 입장에서는 HTTP가 비연결성이기 때문에 "어 클라이언트가 웹 브라우저를 종료했구나! 세션을 종료시켜야지" 라고 인식 자체가 불가능하다.
- 또 중요한 점은 세션은 실제 메모리를 사용하기 때문에 불필요한 세션이 열려있다면 메모리 낭비가 심하다.
- 따라서 적절하게 타임아웃을 설정해주어 세션이 만료되도록 설정해야한다.
세션 타임아웃은 2가지 방식으로 할 수 있다.
- 글로벌 설정 : application.properties에 직접 설정할 수 있다.
server.servlet.session.timeout=60 와 같이 직접 시간을 입력해줄 수 있다. 주의할 점은, 글로벌 설정은 분 단위로 설정해야 하므로 60, 120, 180등 분 단위로 딱 끊기게 설정해줘야 한다. 기본값은 1800초, 30분이다. - 특정 세션에 타임아웃 설정 : session.getMaxInactiveInterval()을 통해 세션의 유효시간을 얻어왔던 것처럼,
반대로 session.setMaxInactiveInterval(1500) 처럼 직접 특정 세션의 타임아웃을 설정해줄 수 있다.
출처 : 인프런, 김영한의 스프링 MVC 2편 - 백엔드 웹 개발 핵심 기술
'Spring' 카테고리의 다른 글
[Spring] 예외 처리와 오류 페이지 (0) | 2023.07.28 |
---|---|
[Spring] 로그인 처리 - 서블릿 필터, 스프링 인터셉터 (0) | 2023.07.27 |
[Spring] 애노테이션 기반의 Bean Validation (0) | 2023.07.25 |
[Spring] 유효성 검증 - Validation (0) | 2023.07.21 |
[Spring] 메시지, 국제화 기능 (0) | 2023.07.19 |