semtax의 개발 일지

스프링부트로 게시판 만들기 6 : 로그인 기능 추가 본문

개발/Java

스프링부트로 게시판 만들기 6 : 로그인 기능 추가

semtax 2020. 5. 1. 17:19
반응형

개요

이번 포스팅에서는 지난 포스팅에 구현 한 회원가입 기능에 이어서, 로그인 기능을 구현하도록 하겠다.

추가적으로, 로그인 기능을 구현하면서 인증(Authentication)의 개념과 세션, 그리고 인터셉터에 대해서도 알아보도록 하겠다.

인증(Authentication) 이란?

먼저, 인증이란게 어떠한 개념인지 알아보도록 하자.

위키피디아에 인증에 대한 개념을 검색하면 아래와 같은 결과가 나온다

Authentication is the act of proving an assertion, such as the identity of a computer system user. In contrast with identification, the act of indicating a person or thing's identity, authentication is the process of verifying that identity. It might involve validating personal identity documents, verifying the authenticity of a website with a digital certificate,[1] determining the age of an artifact by carbon dating, or ensuring that a product or document is not counterfeit.

즉, 요약하면 우리 서비스에 접근하는 사람이 우리가 허가한 사용자 목록에 있는지, 그리고 우리가 허가한 사용자가 맞는지를 검증하는 과정이라고 보면 된다.

(한마디로, 뷔페집에서 뷔페 입장표 검사해주는 입구하고 직원을 만든다고 생각하면 된다.)

대충 아래와 같은 알고리즘을 따르면 된다.

  1. 사용자에게 아이디, 비밀번호를 전달 받는다.
  2. 저번 시간에 만든 회원 가입 기능을 이용해서 가입된 정보를 가져온다.
  3. 가입된 정보와 넘겨받은 정보를 비교한다.
    1. 이때, 패스워드를 해싱해서 비교해야 한다.
  4. 두개 다 일치하는 경우 로그인 성공, 그렇지 않은 경우 로그인 실패로 처리한다.
  5. 로그인에 성공한 경우 입장표 에 해당하는것을 사용자에게 넘겨준다

그렇다면, 서버에서는 이러한 입장표 에 해당하는 걸 어떠한 방식으로 구현 할까?

이제 그 방법에 대해서 알아보도록 하자.

세션(Session)?

다시 원론으로 돌아가서, 결국 우리가 만드는 웹 서버도 소프트웨어이고, 소프트웨어는 사실상 데이터(혹은 객체)의 집합체이기 때문에 저러한 입장표도 앞 뒤 다 자르고 이야기 하면 결국 특수한 데이터이다. 즉, 저러한 입장표에 해당하는 객체와, 저러한 입장표를 저장/관리해주는 객체를 만들어주면 된다.

바로 저 입장표와, 저 입장표를 저장/관리 해주는 객체들을 각각 세션(Session), 세션 저장소(Session Storage) 라고 한다.

다행히도, 스프링 MVC(정확히는 서블릿)에서는 저러한 세션과 관련된 기능이 있기 때문에 가져다 쓰면 된다.

보통 아래와 같은 방식으로 사용한다.

@Component
public class LoginInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        HttpSession httpSession = request.getSession();
        String sessionItem = (String)httpSession.getAttribute(Sessions.SESSION_ID);

        if(sessionItem == null){
            response.getOutputStream().println("LOGIN REQUIRED!");
            return false;
        }

        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {

    }
}

그런데, 컨트롤러가 1~2개도 아니고 저런 중복되는 로직을 일일히 손으로 각 컨트롤러마다 전부 적어주면 뭔가 불편하다.

그리고 해당 예제와 같은 경우 프로그램의 규모가 작아서 상관이 없지만, 나중에 프로그램 규모가 커지고, 저 검증로직을 바꿔야 하는 경우가 생길수도 있다.

이때, 일일히 모든 컨트롤러들을 뒤져가면서 전부 고쳐줘야 한다. 정말로 불편하지 않을수가 없다. 뭔가 다른 방법이 필요하다.

사실 위와 같은 문제는 프록시 패턴과 같은 디자인 패턴으로도 풀 수는 있다.

하지만, 스프링 MVC(서블릿) 에서 그것보다 더 좋은 방법을 제공하고 있다. 그 방법을 이제부터 알아보도록 하자.

인터셉터

스프링 MVC(정확히는 서블릿)에서는, HTTP Request 패킷이 실제 HTTP 패킷을 처리하는 로직(컨트롤러 또는 핸들러)에 접근하기 전에 먼저 앞에서 가로채서 전 처리를 할 수 있는 기능을 지원하고 있다.

해당 기능을, 패킷을 먼저 가로챈다고 해서 인터셉터라고 부른다. 대략적으로 아래와 같이 생겼다.

@Component
public class LoginInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        HttpSession httpSession = request.getSession();
        String sessionItem = (String)httpSession.getAttribute(Sessions.SESSION_ID);

        if(sessionItem == null){
            response.getOutputStream().println("LOGIN REQUIRED!");
            return false;
        }

        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {

    }
}

그리고, 위에 있는 인터셉터를 설정을 아래와 같이 Configure 클래스에서 등록하는 방식으로 사용한다.

@Configuration
public class WebserviceConfig implements WebMvcConfigurer {

    @Autowired
    LoginInterceptor loginInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loginInterceptor)
                .excludePathPatterns("/post/page")
                .addPathPatterns("/post/**")
                .addPathPatterns("/comment/**");
    }
}

이제 대략적인 내용들을 설명했으니 구현을 해보도록 하자.

구현

먼저 세션 관련된 정보를 편하게 관리하기 위한 래퍼 클래스를 선언해주자.

package com.semtax.application.util;

public class Sessions {

    public static final String SESSION_ID = "gallerySessionID";
}

위의, SESSION_ID 를 통해 세션에서 값을 가지고 오면 된다.

그런 뒤, 인터셉터를 아래와 같이 만들어 준다.

package com.semtax.application.interceptor;

import com.semtax.application.util.Sessions;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.PrintWriter;


@Component
public class LoginInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        HttpSession httpSession = request.getSession();
        String sessionItem = (String)httpSession.getAttribute(Sessions.SESSION_ID);

        if(sessionItem == null){
            response.getOutputStream().println("LOGIN REQUIRED!");
            return false;
        }

        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {

    }
}

그런 뒤 아래와 같이 인터셉터를 등록해준다.

package com.semtax.application.config;

import com.semtax.application.interceptor.CommentAuthInterceptor;
import com.semtax.application.interceptor.LoginInterceptor;
import com.semtax.application.interceptor.PostAuthInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebserviceConfig implements WebMvcConfigurer {

    @Autowired
    LoginInterceptor loginInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loginInterceptor)
                .addPathPatterns("/post/**")
                .addPathPatterns("/comment/**");
    }
}

테스트

Postman을 켜서 아래와 같이 회원가입을 하고 로그인을 해보자.

 

로그인 성공이 뜨는것을 알 수 있다.

 

 

 

이번에는 잘못된 패스워드 를 입력해보자

 

로그인 실패가 뜨는것을 알 수 있다.

결론

이번 시간에는, 로그인 기능, 인증(Authentication)의 개념과 세션, 그리고 인터셉터에 대해서 알아보았다.

여담으로, 저 세션 같은 경우 굳이 스프링에서 제공하는 세션 저장소 에서 관리하지 않고, 레디스나 RDB에 세션 저장소를 직접 만들고 관리 할 수 도 있다.

실제로, SSO(Single Sign On) 기능에서 보통 저런식으로 구현해서 쓰고 있기도 하다. 그리고 , spring session data에서도 해당 기능을 지원하고 있다.

다음 시간에는 데이터의 이력을 저장/관리 하는 Auditing에 대해서 알아보도록 하겠다.

반응형
Comments