로그인 서비스 흐름도
로그인 인증)
- 로그인 인증의 경우, Filter와 Interceptor를 이용하여 구현할 수 있다
- Filter의 경우, spring의 Distpatcher에 들어가기 전에 필터링이 가능하므로 주로 Filter에서 로그인 인증이 일어난다
- 현재 프로젝트 적용
- 1차 로그인 인증 (Filter)
- Session값의 유무를 통해, Session을 가지고 있으면 Filter 통과
- 2차 로그인 인증 (Interceptor)
- Session 값 유무 판단 + URL 변경을 통한 사용자 인증을 막고자 정규표현식을 이용한 필터링 진행
- ex) http://localhost/members/test1 -> http://localhost/members/admin (이동 방지)
로그인 인증 방법
1) JWT
2) Session
- JWT
- JWT의 경우, 멤버 객체의 특정 값들을 조합하여 토큰을 발행 후, 클라이언트에게 전달하여 인증하는 방법
- 장점 : 서버의 저장 리소스를 줄일 수 있음 (클라이언트가 저장하고 있기에) -> Session 방식과 차이점
- 단점 : 유효기간이 지나기 전까지는 클라이언트가 탈취를 당하더라도 해결하는 방법이 적절치 못함
- Session
- Session의 경우, 인스턴스당 session값을 매칭하여 session store에 서버가 저장한 후 인증 하는 방법
- 장점 : 세션 관리가 용이함
- 단점 : 서버의 리소스를 소모하므로, 너무 많은 인원의 경우 저장하는데 많은 리소스를 소모할 수 있음
- 현재 프로젝트 적용
- Http Session을 사용하여, member 객체의 정보를 이용해 session 생성 후 인증진행
주요 구현 부분
- Filter 인증 부분
- getSession(false)를 통해, 세션이 있는지 확인 후 없으면 로그인화면으로 리다이렉트 진행
try{
log.info("인증체크 필터 시작 {}", requestURI);
if(loginCheckPath(requestURI)){
log.info("인증체크 로직 실행 {} ", requestURI);
HttpSession session = httpRequest.getSession(false);
if (session == null || session.getAttribute(SessionConst.LOGIN_MEMBER) == null){
log.info("미인증 사용자 요청 {}", requestURI);
httpResponse.sendRedirect("/members/login?redirectURL=" + requestURI);
return;
}
}
chain.doFilter(request,response);
}catch (Exception e){
throw e;
}finally{
log.info("인증 체크 필터 종료 {} ", requestURI);
}
- Interceptor 인증 부분
- session값에 저장된 Member와 URL에 나와있는 memberId와 다를 경우, 로그인화면으로 리다이렉트
if(matcher.find()){
String account = matcher.group(1);
Member authAccount = (Member) session.getAttribute(SessionConst.LOGIN_MEMBER);
if (authAccount.getLoginId() == null || !authAccount.getLoginId().equals(account)){
response.sendRedirect("/members/login?redirectURI="+requestURI);
log.info("Session loginId: {}, URL account: {}", authAccount.getLoginId(), account);
return false;
}
@Bean
public FilterRegistrationBean loginCheckFilter(){
FilterRegistrationBean<Filter> filterFilterRegistrationBean = new FilterRegistrationBean<>();
filterFilterRegistrationBean.setFilter(new LoginCheckFilter());
filterFilterRegistrationBean.setOrder(2);
filterFilterRegistrationBean.addUrlPatterns("/*");
filterFilterRegistrationBean.setDispatcherTypes(DispatcherType.REQUEST);
return filterFilterRegistrationBean;
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LogInterceptor())
.order(1)
.addPathPatterns("/**")
.excludePathPatterns("/css/**", "/*.ico", "/error", "/errors/custom/**");
registry.addInterceptor(new LoginCheckInterceptor())
.order(2)
.addPathPatterns("/**")
.excludePathPatterns("/members/*/boards/*","/members/login", "/members/form", "/members/logout", "/css/**", "/*.ico", "/error","/errors/custom/**");
}
- 로그인 인증 서비스 부분
- 로그인이 성공적으로 될경우, Session 발급
if (loginService.loginCheck(loginForm.getLoginId(), loginForm.getPasswd())){
HttpSession session = request.getSession(true);
Member loginMember= memberRepository.findByLoginId(loginForm.getLoginId());
session.setAttribute(SessionConst.LOGIN_MEMBER, loginMember);
return "redirect:/members/" + loginForm.getLoginId();
}
아쉬운점
1. HttpSession이 아닌, session store를 운용하지 못한점을 보완하고 싶다 (ex_ redis)
- redis 사용이유 : in-memory기능으로서 굳이 DB에 쌓는 것이아닌 세션 값이기에 빠른 조회 및 탐색을 위해 in-memory 방식을 택하고자 함
2. URL를 통한 계정 이동을 방지하고자, Interceptor에 추가하였지만 Spring security를 이용하여 안정적으로 서비스를 제공하고 싶다