[엘리스 sw 엔지니어 트랙] 34일차hash, Passport.js, aggregation

오경찬·2022년 5월 31일
0

수업 34일차

수업을 듣고 문제를 풀어보는데 난의도가 너무 올라간게 느껴진다... 이번주는 수업을 듣고 있지만 무슨내용인지 잘 안들어오지만 다음에 봤을때 아 이거였지 하는 날이 빨리오길 기대하며 :)

이론

  • hash: 문자열을 되돌리 수 없는 방식으로 암호화 하는 방법
    Passport.js: Express.js 어플리케이션에 간단하게 사용자 인증 기능을 구현
    session: 클라이언트 정보를 서버에 저장하고 클라이언트 요청시 Session ID를 사용해 정보를 다시 확인하는 기술
    CSR 구현: 필요한 리소스를 클라이언트에 선언, 데이터를 비동기 호출, 데이터를 가공, 리소스를 사용하여 화면에 표시
    aggregation: Document 들을 가공하고, 연산하는 기능

hash

Hash란 내부적으로 배열을 사용하여 데이터를 저장하기 때문에 빠른 검색속도를 갖는다.( ArrayList의 장점 )
그리고 데이터를 추가/삭제시 기존 데이터를 밀어내거나 당기는 작업이 없도록 (연결리스트의 장점) 특별한 알고리즘을 이용하여 데이터와 연관된 고유한 숫자를 만들어 낸 뒤 이를 인덱스로 사용한다

Hash에서 사용하는 내부적인 배열을 Hash Table이라고 하며 크기에 따라 성능차이가 있다.

hash Table

Hash Table은 key-value에서 key를 테이블에 저장할 때 key값을 Hash Method를 이용해 계산을 수행한 후 그 결과값을 배열의 인덱스로 사용하여 저장하는 방식이다.

hash Method

위에서 설명했듯 Hash는 특별한 알고리즘을 이용한다고 하였는데 이 알고리즘을 구현한 메소드를 Hash Method라고 하며 Hash Method에 의해 반환된 데이터의 고유 숫자값을 Hash Code라고 한다.

Hash Method의 특성으로는
1. 동일한 입력값에 대해 동일한 출력값을 보장한다.
2. 출력 값은 가능한 고른 범위에 균일하게 분포된다.

자바에서는 Object클래스의 hashCode() 메소드를 이용하여 모든 객체의 HashCode를 쉽게 구할 수 있다.

하지만 입력값이 달라져도 드물게 동일한 출력값이 나오는 경우가 있는데 이런 경우를 해시충돌이라고 한다.

hash 충돌

해시함수에서 출력된 해시코드값이 다른입력값과 동일할때 발생되는것을 해시충돌이라 하고 이러한 해시충돌은 특정 키의 버켓에 데이터가 집중된다는 것을 의미합니다. 그래서 너무 많은 해시 충돌은 해시테이블의 성능을 떨어뜨립니다.

해시충돌을 최소화 하기위해선 해시함수를 잘 정의하여 사용해야 한다. 하지만 해시함수의 입력값은 무한이지만 출력값은 유한하므로 해시충돌은 반드시 발생합니다.

이러한 해시충돌을 해결하기 위해 여러 방법이있다.

hash 충돌의 해결방법

체이닝(Chaning)

버켓 내의 연결리스트를 할당하여, 버켓에 데이터를 삽입하다가 해시 충돌이 발생하면 연결리스트로 데이터들을 연결하는 방식이다.

Java8이상에서는 더 향상된 방법으로 충돌을 회피하는데, 충돌된 데이터가 8개가 모이면 연결리스트를 트리로 변형하고 해당 값이 6개에 이르면 다시 연결리스트로 변경시킨다. 8개에서 7개가 아닌 이유는 잦은 연결리스트와 트리로의 변경으로 인한 성능저하를 막기 위함이라 한다.

개방 주소법(Open Addressing)

체이닝의 경우 버켓이 꽉 차더라도 연결리스트로 계속 늘려가기에, 데이터의 주소값은 바뀌지 않는다. 하지만 개방 주소법의 경우에는 다르다. 해시 충돌이 일어나면 다른 버켓에 데이터를 삽입하는 방식을 개방주소법이라고 하며, 개방주소법은 대표적으로 3가지가 있다.

  • 선형탐색 : 해시 충돌시 다음 버켓, 혹은 몇개를 건너뛰어 데이터를 삽입한다.
    제곱 탐색 : 해시 충돌시 제곱만큼 건너뛴 버켓에 데이터를 삽입
    이중 해시 : 해시 충돌시 다른 해시함수를 한 번 더 적용한 결과를 이용함.

passport.js

Node.js 용 인증 미들웨어로, Express 기반 웹 애플리케이션에 간단하게 설치할 수 있다.
Passport는 로그인 방식, 인증 방법을 strategy라고 부르는데, 구글 로그인, 페이스북 로그인 등 여러 strategy를 지원한다.

설치 패키지

  • express : 웹 서버 구현을 도와주는 프레임워크
  • express-session : express 프레임워크에서 session을 사용하기 위한 라이브러리
  • passport : passport 코어 라이브러리
  • passport-facebook : facebook 로그인에 필요한 라이브러리
  • passport-google-oauth20 : google 로그인에 필요한 라이브러리
  • passport-kakao : kakao 로그인에 필요한 라이브러리
  • passport-naver : naver 로그인에 필요한 라이브러리

예제

express로 서버 띄우기

// app.js

const express = require('express')
const http = require('http')

const app = express()
const server = http.createServer(app)

const PORT = process.env.PORT || 5000

server.listen(PORT, () => console.log(`Server is runngin on ${PORT}`))

dotenv 설정

소셜 로그인에 필요한 ID, Secret, Redirect URL을 .env 파일에서 관리 할 것이다. 아래 명령어로 dotenv 라이브러리를 설치 해준다.

$ npm i dotenv
// app.js

const dotenv = require('dotenv')
dotenv.config()

위와 같이 설정 해주면, 코드에서 process.env['key'] 로 .env 에 있는 값을 접근 할 수 있다.

session 설정

// app.js

/**
 * 세션 세팅
 */
const configureSession = require('./config/session')
configureSession(app)

// config/session.js
const session = require('express-session')

module.exports = (app) => {
  app.use(
    session({
      secret: process.env['SESSION_SECRET'],
      cookie: { maxAge: 60 * 60 * 1000 },
      resave: false,
      saveUninitialized: true,
    })
  )
}

세션 만료 기간은 1시간으로 설정 했으며, secret 은 .env 에서 가져온다.

resave 는 세션을 언제나 저장할 지 정하는 값입니다. express-session doc 에서는 이 값을 false 로 하는 것을 권장하고 필요에 따라 true로 설정합니다.

saveUninitialized 는 세션이 저장되기 전에 uninitialized 상태로 미리 만들어서 저장합니다.

passport 설정

// app.js

/**
 * passport 세팅
 */
const configurePassport = require('./config/passport')
configurePassport(app)

// config/passport.js
const passport = require('passport')

module.exports = (app) => {
  app.use(passport.initialize())
  app.use(passport.session())

  /**
   * Serialize
   */
  passport.serializeUser((user, done) => {
    done(null, user)
  })

  /**
   * Deserialize
   */
  passport.deserializeUser((user, done) => {
    done(null, user)
  })
}

serializeUser 는 로그인에 성공했을 때 호출된다. user 의 값은, 뒤에서 추가 할 각 전략의 결과 값을 done 으로 보낸 값이다. 보통은 소셜 로그인에 성공한 사용자의 프로필 정보다. serializeUser 에서 done 을 호출하게 되면, session에 사용자 정보다 저장되고, 두 번째 인자로 전달한 user가 deserializeUser로 전달된다.

deserializeUser 는 서버에 요청이 있을 때마다 호출된다. done 의 두 번째 인자로 user를 전달하게 되면 req.user로 user의 값을 접근할 수 있게 된다.

전략 세우기

passport는 LocalStrategy, GoogleStrategy ... 등 다양한 로그인 전략을 제공한다. 여기서는 GoogleStrategy 만 확인하고, 전체 코드는 git 에서 확인할 수 있다.

// config/passport.js
const passport = require('passport')
const GoogleStrategy = require('passport-google-oauth20').Strategy

module.exports = (app) => {
  
	...

	/**
   * Google Strategy
   */
  passport.use(
    new GoogleStrategy(
      {
        clientID: process.env['GOOGLE_CLIENT_ID'],
        clientSecret: process.env['GOOGLE_CLIENT_SECRET'],
        callbackURL: process.env['GOOGLE_CALLBACK'],
      },
      function (accessToken, refreshToken, profile, done) {
        done(null, profile)
      }
    )
  )
}

다른 소셜 로그인 모두 구글 로그인과 패턴이 비슷하다. 구글 로그인만 한 번 해보면 다른 것들은 힘들지 않게 할 수 있을 것이다. 여기서 done 을 호출하게 되면 두 번째 인자 profile이 serializeUser 로 전달된다.

라우팅

// app.js

...

/**
 * Routing
 */
const authRouter = require('./routes/auth')
app.use('/auth', authRouter)

app.get('/', (req, res) => {
  /**
   * req.user가 있는 경우는 소셜 로그인에 성공한 경우
   * passport에 의해 user가 주입됨 (deserialize 확인)
   */
  if (req.user) {
    res.send(`
        <h3>Login Success</h3>
        <a href="/auth/logout">Logout</a>
        <p>
            ${JSON.stringify(req.user, null, 2)}
        </p>
      `)
  } else {
    res.send(`
        <h3>Node Passport Social Login</h3>
        <a href="/auth/login/google">Login with Google+</a>
        <a href="/auth/login/facebook">Login with Facebook</a>
        <a href="/auth/login/naver">Login with Naver</a>
        <a href="/auth/login/kakao">Login with Kakao</a>
    `)
  }
})

// routes/auth.js
const express = require('express')
const passport = require('passport')
const router = express.Router()

router.get('/login/google', passport.authenticate('google', { scope: ['profile'] }))
router.get(
  '/login/google/callback',
  passport.authenticate('google', { failureRedirect: '/auth/login' }),
  (req, res) => {
    res.redirect('/')
  }
)

module.exports = router

/ 로 접근 했을 때 로그인 및 로그아웃을 테스트 할 수 있는 간단한 HTML 을 전달하고 있다. 여기서 req.user 의 값을 통해 로그인 여부를 확인 할 수 있다.

/login/google 으로 접근하면, passport ㅡ를 거쳐 google login api 를 호출 한다.

/login/google/callback 은 Google Developer 에서 등록한 callback URL 로 성공, 실패 여부에 대한 callback router다.

로그아웃

// routes/auth.js

...

router.get('/logout', (req, res) => {
    req.logout()
    res.redirect('/') 
})

passport로 부터 설정된 logout 메소드를 호출 해주고, 그 이후에는 서비스에 알맞게 처리 해주면 된다. 여기서 세션이 제대로 지워지지 않는다는 문제가 종종 발생한다고 하는데, 그럴 땐 아래 코드를 적용하면, 세션이 정상적으로 만료되는 것을 확인 할 수 있을 것이다.

// routes/auth.js

...

router.get('/logout', (req, res) => {
  req.session.destroy((err) => {
    req.logout()
    res.redirect('/')
  })
})

카카오, 네이버 등 다양한 소셜 로그인이 있는데, 전반적인 컨셉은 모두 동일하여 따로 다루지는 않았다. 각 Developer t사이트에서 Client ID 와 Client Secrety Key 만 잘 받아 오면 크게 문제 없이 구현 할 수 있을 것이다.

aggregation

MongoDB의 Aggregation은 데이터 처리 파이프라인의 개념을 모델로 한다.
파이프라인(pipeline)이란 이전 단계의 연산 결과를 다음 단계 연산에 이용하는 것을 말한다.

장점

원하는 데이터만 필터링하기 때문에 결과 JSON 길이가 줄어든다.
네트워크 대역폭이 높아진다.

기본 사용법

Studio 3T for MongoDB에서도 Aggregate를 제공한다.
https://jaehun2841.github.io/2019/02/24/2019-02-24-mongodb-2/#sort

pipeline 순서
collection > $project > $match > $group > $sort > $skip > $limit > $unwind > $out

profile
코린이 입니당 :)

0개의 댓글