어드민 사이트에 로그인 기능을 구현해봤다.
로그인 기능을 구현하는 방법은 여러가지가 있는데 크게 세션, JWT, Oauth 방식들이 있다.
이번 강의에선 가장 기초적이고 구현하기 쉬운 세션 방식으로 로그인을 구현하는 방법을 다뤘다.
세션 방식이란?
로그인 시 서버에서 Map과 같은 형식으로 클라이언트에 세션id 값과 로그인 유저 정보 값을 저장한 후
세션 id 값만 http header 쿠키에 담아 클라이언트에 전달하는 방식.
클라이언트는 받은 쿠키를 요청이 있을때마다 지속적으로 전달하고,
서버는 쿠키에서 전달된 id 값을 세션에서 확인해 로그인을 유지하는 방식이다.
세션이 수행하는 일은 크게 3가지이다.
1. 세션 생성 (로그인 정보를 저장하고 id 값을 쿠키에 담아 반환)
2. 세션 조회 (받아온 쿠기의 id값이 세션에 있는지 확인)
3. 세션 삭제 (로그아웃시 세션에서 해당 정보 삭제)
1. Session 직접 구현하기
@Component
public class SessionManager{
public static final String SESSION_COOKIE_NAME = "mySessionId";
private Map<String, Object> sessionStore = new ConcurrentHashMap<>();
public void createSession(Object value, HttpServletResponse response){
//세션 id 생성하고 저장
String sessionId = UUID.randomUUID().toString();
sessionStore.put(sessionId, value);
Cookie cookie = new Cookie(SESSION_COOKIE_NAME, sessionId);
cookie.setPath("/");
response.addCookie(cookie);
}
//세션 조회
public Object getSession(HttpServletRequest request){
Cookie sessionCookie = findCookie(request, SESSION_COOKIE_NAME);
if (sessionCookie == null){
return null;
}
return sessionStore.get(sessionCookie.getValue());
}
private Cookie findCookie(HttpServletRequest request, String cookieName){
Cookie[] cookies = request.getCookies();
if (cookies == null){
return null;
}
return Arrays.stream(cookies)
.filter(cookie -> cookie.getName().equals(cookieName))
.findAny()
.orElse(null);
}
//세션 만료
public void expire(HttpServletRequest request){
Cookie cookie = findCookie(request, SESSION_COOKIE_NAME);
if (cookie != null){
sessionStore.remove(cookie.getValue());
}
}
}
UUID와 ConcurrentHashMap을 이용해서 세션을 직접 구현했다.
세션에서 주의할 점은
- 세션에서 쿠키로 전달되는 값은 클라이언트가 임의로 변경할 수 있다.
- 클라이언트에 저장되는 쿠키는 다른 사용자에게 탈취당할 수 있다.
그러므로 쿠키 내용으로 들어가는 값(클라이언트에게 전달하는 값)은 항상
비연속적이고, 예측 불가능하며, 주요 정보가 들어가선 안된다.
또한 세션의 유효기간을 짧게 설정하여(보통 30분)
쿠키가 탈취되더라도 다른 곳에서 사용되는 것을 최소화하고 메모리 과부화를 막아야한다.
직접 구현할때는 UUID를 사용했다.
그러나 실제로 이렇게 구현하는 것은 귀찮고 번거로우니까 HttpSession기능을 사용하자.
2. HttpSession 사용
@Controller
@RequiredArgsConstructor
public class AdminController{
// private final SessionManager sessionManager;
private final UserService userService;
@GetMapping("/") //홈 화면
public String newHome(@SessionAttribute(name=SessionConst.LOGIN_USER, required=false) User findUser,
Model model){
if (findUser == null){
return "admin/home";
}
model.addAttribute("userName", findUser.getName());
return "admin/menu";
}
@GetMapping("/admin/login") //로그인 폼
public String loginForm(Model model) {
model.addAttribute("signInRequest", new SignInRequest());
return "admin/login";
}
@PostMapping("/admin/login") //로그인 로직
public String login(@Valid @ModelAttribute SignInRequest signInRequest,
BindingResult bindingResult,
HttpServletRequest request){
if(bindingResult.hasErrors()){ // 검증 실패
return "admin/login";
}
else { //검증 성공
try{
User loginUser = userService.signIn(signInRequest.getAccountId(), signInRequest.getPassword());
//HttpSession 사용 로그인 정보 저장
HttpSession session = request.getSession();
session.setAttribute(SessionConst.LOGIN_USER, loginUser);
return "redirect:/";
}
catch(NoSuchUserException e){ //아이디나 비밀번호 틀림
bindingResult.reject("loginFail", "아이디 또는 비밀번호가 맞지 않습니다.");
return "admin/login";
}
}
}
@PostMapping("/admin/logout")
public String logout(HttpServletRequest request){
//세션에서 삭제
HttpSession session = request.getSession(false);
if (session != null){
session.invalidate();
}
return "redirect:/";
}
}
HttpSession은 서블릿이 제공하는 session 기능이다.
기본 쿠키 이름은 JSESSIONID이다.
HttpServletRequest.getSession(create=true)
- HttpSession 객체를 가져온다.
- create 옵션을 지정할 수 있는데 default는 true이다.
- create 옵션이 true이면 세션이 존재하지 않을시 생성하며, false이면 null을 반환한다.
HttpSession.setSessionAttribute(name, value)
- 세션에 데이터를 저장한다.
- name값은 나중에 세션에서 정보를 꺼내올 때 사용된다.
@SessionAttribute(name=..., required=true) Object value
- 쿠키 값을 이용해 세션에서 데이터를 조회해온다.
- HttpServletRequest.getSession().getSessionAttribute()와 예외처리 로직을 편하게 수행하는 어노테이션이다.
기본 저장기간은 마지막 사용으로부터 30분이며
application.yml 파일을 수정해 세션의 저장기간을 바꿀 수 있다.
'Spring > Spring MVC' 카테고리의 다른 글
사지방에서 Spring 공부하기 Web MVC #11 - 예외처리 (0) | 2023.08.31 |
---|---|
사지방에서 Spring 공부하기 Web MVC #10 - 필터와 인터셉터 (0) | 2023.08.27 |
사지방에서 Spring 공부하기 Web MVC #8 - Bean Validation (0) | 2023.08.26 |
사지방에서 Spring 공부하기 Web MVC #7 - Validation(검증) (0) | 2023.08.20 |
사지방에서 Spring 공부하기 Web MVC #6 - 메세지 국제화 (5) | 2023.08.19 |