우테코 5기

[하루스터디] 어드민 페이지 개발하기 - 로그인 구현

teo_99 2023. 10. 1. 02:00

메인 어드민 기능을 개발하기에 앞서 로그인을 구현해보고자 합니다. 어드민 페이지에 접근할 수 있는 사용자는 하루스터디 개발자들로 제한되어야 하기 때문에 로그인 기능은 필수적이라 할 수 있겠습니다.

 

기존 인증 시스템 재사용하기?

하루스터디는 Oauth2 기반의 인증 시스템을 사용합니다. 사용자가 소셜 로그인을 진행하면 해당 정보를 기반으로 인가용 access token을 발급하는 구조입니다.

 

이런 기존 인증 시스템을 사용할 순 없을지 고민이 되었습니다. 투자하는 리소스를 최소화 시킬 수 있으면서 구현 방식또한 간단하리라 예측했기 때문입니다.

 

member 테이블

하루스터디 서비스에 소셜 로그인을 하게 되면 사용자는 ID를 갖게 됩니다. 그리고 이는 Member 테이블의 Primay Key입니다. 예를 들어 구글 로그인을 진행하는 경우에는 위 Member 테이블에 해당 사용자에 대한 행이 하나 생기게 됩니다. 

 

그렇다면 이런 Member 테이블을 활용해 어드민 기능을 구현할 수는 없을지 고민이 들었습니다. 컬럼을 하나 추가해(isAdmin과 같은) 어드민인지 식별할 수 있게 하거나 추가적인 매핑 테이블을 생성해 아래와 같이 어드민 여부를 판단할 수 있게 할 수 있을 것 같습니다.

 

다만 기존 인증 시스템을 재사용하기에는 아래와 같은 문제점들이 존재합니다.

  • JWS 토큰 기반 인증이기에 어드민의 member Id가 그대로 유출됩니다. (어떤 멤버가 어드민인지 알아낼 수 있다는 의미입니다)
  • access token이 탈취되는 경우 무력화시킬 수 없습니다.

특히 탈취되었을 때 무력화시킬 수 없다는 점이 가장 큰 문제라고 생각했습니다. 무력화시킬 수 없을 뿐만 아니라 로깅 내역을 실시간으로 확인하지 않으면 탈취 여부조차 알아내기가 어렵습니다. 따라서 다른 인증 프로세스를 고민하게 되었습니다.

 

세션 기반의 인증 도입하기

앞서 말한 문제점들을 해결하기 위한 방법은 바로 세션 기반의 인증 프로세스를 구축하는 것입니다. 세션 방식의 인증 / 인가를 사용하면 누가 어드민에 접근했는지도 쉽게 파악할 수 있으며 필요한 경우 무력화시키는 것도 가능합니다. 

 

이제 서버 측에서 로그인 유저를 관리할지 결정했으므로, 어떻게 아이디와 비밀번호를 전송할 것이냐가 관심사가 됩니다. Basic, Digest 등 여러 후보군이 있었지만 구현하기에 간단하고 자주 사용되는 폼 데이터를 사용하기로 결정했습니다.

 

물론 스프링 시큐리티와 같은 프레임워크를 도입할 수 있는 리소스까지는 없기 때문에 보안 상의 허점은 존재하겠지만 데이터 삽입 / 수정 / 삭제와 같은 크리티컬한 기능은 어드민에서 제공하지 않을 생각이기에 지금 당장은 큰 문제가 없을 것이라 판단했습니다. 

 

DB 스키마 & 코드 작성

세션 기반의 인증을 구현하기 위해 스키마는 위와 같이 구성했습니다. Admin 테이블에는 실제 어드민 정보가 들어가며 AdminSession 테이블에는 로그인 시 생성되는 세션이 저장됩니다. PK가 유출되지 않도록 uuid 값을 통해 세션을 식별하도록 구성했습니다.

 

@Entity
public class AdminSession extends BaseTimeEntity {

    @Id
    @GeneratedValue
    private Long id;

    private UUID uuid;

    private LocalDateTime expiredDateTime;

    public AdminSession(UUID value) {
        this.uuid = value;
        this.expiredDateTime = LocalDateTime.now()
                .plus(1L, ChronoUnit.HOURS);
    }
    
    // ...
}

추가로 expiredDateTime를 1시간 뒤로 짧게 설정해두었습니다. 발급 후 1시간이 지난 AdminSession은 사용할 수 없게 됩니다.

@PostMapping(value = "/login", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
public ResponseEntity<Void> login(AdminLoginRequest request,
                                  HttpServletRequest httpServletRequest) {
    UUID sessionId = adminService.login(request);
    HttpSession session = httpServletRequest.getSession();
    session.setAttribute("SESSION", sessionId);
    session.setMaxInactiveInterval(3600);

    return ResponseEntity.ok().build();
}

또한 브라우저에서 세션을 관리하기 위한 방법으로는 톰캣 서블릿 컨테이너에서 기본적으로 제공하는 HttpSession을 사용했습니다. 또한 Tomcat 세션의 경우 기본적으로 마지막으로 접근한 시간으로부터 30분동안 접근이 없다면 세션이 삭제되기 때문에 해당 주기를 1시간으로 늘려주었습니다.

 

이렇게 세션 기반의 어드민 인증 프로세스를 간략하게 구성해보았습니다. 사실 조금 더 보안에 신경썼다면 어땠을까 아쉬움은 남지만, 현재 하루스터디 사용자 유치가 활발하게 일어나고 있는 상황이고 어드민 기능을 빠르게 구현하는 것이 우선이라고 판단했습니다.

 

다음으로는 페이징 기반의 데이터 조회를 구현해보도록 하겠습니다.