본문 바로가기
더이상 하지 않는 Backend - NodeJS/Node-Express 개론(완)

[O'REILLY] Node & Express - 18장 : 보안 처리 -2(인증)

by VictorMeredith 2023. 4. 17.

1. 인증

- 인증은 크고 복잡한 주제이다.

- 웹앱에서 빼놓을 수 없는 중요한 주제이고, 제발 직접 만들려고 하지 말자.

- 앱의 보안시스템을 이해하는 것은 중요하지만, 직접 구현하는 시도는 '보안 전문가' 가 아니라면 접어두는 것이 좋다.

 

2. 인증과 권한 부여

- 인증은 사용자를 확인하는 작업이다. (Authentication)

- 권한부여는 사용자에게 접근/수정/볼 권한을 주는 것이다.(Authorization)

- 인증은 AuthN, 권한부여는 AuthZ라고 짧게 표기하는 사례가 많다.

 

3. 비밀번호 문제

- 비밀번호에서 문제는 사람이다. 사람이 만들기 때문에 가장 약한 고리이다.

- 2018년 보안 분석 결과 대부분의 비밀번호가 '123456' 이었고, 'password'가 두 번째로 많았다고 한다.

- 특수문자가 포함된다고 난리를 쳐도 'password1!' 같은걸 사용한다.

- 웹개발자가 할 수 있는 일은 별로 없다. 그나마 서드파티에 인증 위임하는 것 혹은 비밀번호 관리서비스와 연동하도록 하는 것이다.

 

4. 서드파티 인증(OAuth)

- 세가지 주요 장점이 있다.

- 인증 부담이 줄어들고, 서트파티와 협력하기만 하면 된다.

- 다양한 계정을 사용하면서 생기는 비밀번호 고갈을 피할 수 있다.

- 이미 가지고 있는 자격 증명으로 사이트를 빠르게 이용할 수 있다.

- 단점도 있다.

- 단점은 구글/페이스북/트위터/링크드인 등에 계정이 아예 없는 사람들이 있다. 이정도면 그냥 웹서비스를 이용하지 않는건지 뭔사람이야 도대체? 고연령층은 이해한다만, 어쨌든 그런 사람들은 못쓴다.

 

5. DB에 사용자 저장

- 서드파티 인증과 관계 없이 DB에 사용자 정보를 저장하는 것이 좋다.

 

실제로 사용하고 있는 사이드프로젝트의 User Schema 예제



사이트의 특성 상 특별히 대단한 정보를 저장하지 않는다.

- 서드파티에서 사용할 필드를 추가해서 사용해야 한다. 다중 인증을 사용한다면, 충돌방지를 위해 인증타입과 서드파티 ID의 조합으로 만든다. 예를들면 authId 는 facebook:525759301 뭐 이런식이다. 

 

6. 인증과 등록, UX

- 인증은 신뢰할 수 있는 서드파티, 또는 사용자에게 지급한 사용자 이름과 비밀번호 같은 자격 증명을 통해 사용자를 확인하는 것이다.

- 사용자가 처음 방문했을 때, 혹은 어떠한 서비스를 사용하고자 할 때 등록해야 한다는 것이 명확히 드러나야 한다.

- 사용자 자신이 사이트에 등록하고 있다는 것을 명확히 인지해야 하며, 탈퇴고 쉽게 가능해야 한다.

- 사용자의 이메일 주소를 저장해뒀다면, 이메일로 어떤 서비스를 통해 인증해썼는지 알리는 방법을 사용하면 좋다.

 

7. 패스포트

- 패스포트는 노드/익스프레스에서 널리 쓰이는 강력한 인증모듈이다.

- 패스포트는 전략패턴을 통해 한 가지 인증 메커니즘이 아니라 여러가지 다양한 인증 방법을 플러그인처럼 사용한다.

- 서드파티가 비밀번호를 처리하고, 이후 과정은 리디렉트에 의존한다.

- 페이지 -> 302/307 -> 서드파티 로그인 팝업 혹은 페이지 -> 302/307 -> 인증됨페이지

- 브라우저는 HTTP 요청을 만들고, 응답을 표시하고, 리디렉트를 수행하는 세 가지 역할을 한다.

 

 1) 로그인페이지 :

   - 로그인 방법을 정하는 곳

   - 인증이 필요한 페이지/서비스에 접속하고자 한다면 이 페이지로 리디렉트해야한다.,

 2) 인증 요청 생성 : 

   - 리디렉트를 통해 서드파티에 전송될 요청을 만든다.

   - 패스포트와 플러그인이 이 단계의 세부사항을 처리한다.

   - 인증요청에는 중간자 공격(MITM)을 포함해 공격 수단으로부터 보호한다.

   - 서드파티 인증 메커니즘에 대한 추가 정보를 요청한다.

 3) 인증 응답 확인 :

   - 서드파티에서 인증이 완료되면 유효한 인증 응답을 봬며, 이를 통해 사용자를 확인한다.

   - 인증 응답에는 해당 서드파티에서 고유한 ID가 포함되며, 2단계에서 요청한 추가 정보도 포함된다.

   - 4단계로 이동하기 위해서는 사용자가 인증되었음을 반드시 기억해야 한다. 쿠키나 세션을 사용하지만, 세션을 보통 권한다.

 4) 인증 확인

   - 사용자 ID가 있으면 DB에서 사용자가 어떠한 권한을 가졌는지에 관한 정보가 담긴 사용자 객체를 가져올 수 있다.

   - 이런 방식을 사용하면 모든 요청마다 서드파티 인증을 통할 필요가 없다.

 

- 인증을 위해 패스포트를 사용하는 것도 작업량이 만만치는 않다.

- 인증솔루션을 개인화하고자 한다면 패스포트가 좋은 출발점이 된다.

 

8. 패스포트 설정

- 페이스북을 예시로 한다.

- 페이스북 인증을 위해서는 페이스북 앱이 필요하다.

- 적당한 페이스북 앱이 있다면 그걸 쓰면 되고, 없다면 인증 전용으로 하나 만들어도 된다.

- 기관의 공식 페이스북 계정으로 앱을 만든다.

- 개인 페이스북 계정을 해당 앱의 관리자로 추가할 수도 있다.

- 개발/테스트 목적에 사용하려면 개발/테스트용 도메인 이름을 앱과 연결해야 한다.

- 페이스북에서는 localhost와 포트 번호를 허용하므로 테스트 목적으로 알맞다.

- 앱을 테스트하기 위해 브라우저에 입력하는 URL이 페이스북 앱과 연결된다.

- 페이스북 개발자 문서를 확인하여 앱 설정을 한다.

- 이후에는 앱ID와 앱 시크릿이 필요하다. 페이스북 앱 관리 페이지에서 찾을 수 있다.

- 콜백 URL의 호스트 이름과 포트가 앱에서 설정한 것과 일치하는지 확인하려면 브라우저의 URL에서 인코드된 URL의 쿼리스트링에서 단서를 찾을 수 있다.

 

- 이제 페이스북 인증 플러그인을 설치한다.

npm i passport passport-facebook

- lib/auth.js 모듈같이 파일을 만들어 조각조각내어 나눈다.

- 패스포트에 필요한 serializeUser와 deserializeUser 메서드를 먼저 만든다.

패스포트는 전략패턴

- 패스포트는 serializeUser와 deserializeUser를 사용해 요청을 인증된 사용제에 연결하므로 어떤 스토리지든 호환된다.

- 우리는 DB ID(_id)를 세션에 저장하기만 한다.

- 이 작업이 끝나면 필요할 때 이 ID를 DB에서 찾아 사용자 객체를 가져온다.

- 이 메서드를 만들면, 활성화된 세션이 있고 인증이 잘 되었다면 req.session.passport.user는 db에서 가져온 사용자객체가 된다.

- 다음은 내보낼 것을 선택할 차례이다.

- 우선 패스포트를 초기화해야하고, 인증과 함께 서드파티 인증 서비스에서 리디렉트한 콜백을 처리할 라우트를 등록해야 한다.

- 이 모듈에서 이 작업을 하는 함수를 내보내기보다 필요한 메서드를 가진 객체를 반환하는 함수를 내보낸다.

- 설정값을 넣어줘야 하기 때문이다.

auth.js 모듈 내보내기

- 객체를 반환하는 함수를 반환하는 이유는 아래와 같다

- 그리고나서 credentials.development.json 에 authProviders 프로퍼티를 추가해야 한다.

"authProviders":{
	"facebook":{
    	"appId":"your_app_id",
        "appSecret":"your_app_secret"
    }
}

 

- 이제 TODO 영역에 패스포트 템플릿을 통해 인증되고 난 후 FacebookStrategy 함수를 호출하면 된다.

거의 템플릿이다.

- 그려면 profile 매개변수에 페이스북 사용자에 대한 정보가 담긴다. 여기 페이스북 ID가 들어가게 되고, 이 ID로 계정을 우리의 사용자 객체에 연결하면 된다.

- TODO의 registerRoutes 메서드도 만들어준다.

 

- 이제 /auth/facebook 에 방문하면 자동으로 페이스북 인증화면으로 리디렉트한다.

- 쿼리스트링 매개변수 redirect가 있는지 확인하고, 있다면 세션에 저장한다.

- 인증하면 /auth/facebook/callback 경로로 리디렉트한다.

- passport.use() 추가를 통해 다양한 인증 제공자를 추가할 수 있다.

 

어유 복잡해.

댓글