본문 바로가기
카테고리 없음

[인증/인가] 인증/인가, 쿠키/세션, passport 모듈

by hbIncoding 2024. 9. 5.

1. 인증

  • 시스템에 접근하려눈 사용자가 누구인지 확인하는 과정
  • 사용자가 제공한 정보가 시스템의 기록과 일치하는지 확인하는 절차
  • 예시
    • 로그인 시 사용자의 아이디와 비밀번호를 입력하는 과정. 시스템은 아이디와 비밀번호가 DB에 저장된 정보와 일치하는지 확인
    • 두 단계 인증(2FA)도 인증의 한 예시이다.
  • 주요 요소
    • 무엇을 알고 있는가? : 비밀번호, PIN
    • 무엇을 가지고 있는가? : 스마트폰, 보안 토큰 등
    • 누구인가? : 지문, 얼굴 인식

2. 인가

  • 인증에 성공한 후에, 해당 사용자가 무엇을 할 수 있는지 결정 하는 과정
  • 사용자가 시스템에 접근할 수 있는 자원이나 기능에 대한 권한을 부여하는 것
  • 예시
    • 일반 사용자와 관리자가 접근 할 수 있는 기능은 차이가 있다.
    • 누군가는 읽기만 할 수 있고, 누군가는 읽고 수정까지 할 수 있다.
  • 주요 요소
    • 역할 기반 접근 제어(RBAC) : 사용자에게 역할을 부여하고, 그 역할에 따라 권한을 할당
    • 자원 기반 접근 제어 : 특정 자원에 대한 접근 권한을 개별적으로 설정

3. JWT(JSON Web Token)

  • 웹 애플리케이션에서 사용자 인증 및 권한 부여를 위해 널리 사용되는 토큰 기반 인증 방식
  • 클라이언트와 서버 간의 안전하고 효율적인 데이터 교환을 가능하게 하는 JSON 객체로, 정보의 무결성과 인증을 보장할 수 있다.
  • 구조
    • 헤더 : JWT의 유형과 해싱 알고리즘을 정의하는 정보를 포함
    • 페이로드 : 토큰에 담길 실제 정보를 포함. 주로 사용자 관련 데이터와 토큰의 수명 등을 나타내는 클레임(Claims)으로 구성
      • 등록된 클레임 (Registered Claims): 표준화된 클레임으로, iss (발행자), exp (만료 시간), sub (주체), aud (대상자) 등이 포함됩니다.
      • 공개 클레임 (Public Claims): 충돌을 방지하기 위해 IANA JSON 웹 토큰 클레임 등록에서 정의된 클레임.
      • 비공개 클레임 (Private Claims): 발행자와 소비자 간에 정의된 클레임으로, 특정 애플리케이션 요구사항에 맞게 설정됩니다.
    • 서명 : 헤더와 페이로드를 결합한 후, 비밀 키를 사용해서 지정된 해싱 알고리즘으로 생성된다.
  • 작동 원리
    1. 로그인 요청 : 사용자가 로그인할 때, 서버는 사용자의 자격 증명을 확인하고, 유효한 경우 JWT를 생성하여 클라이언트에게 반환합니다.
    2. 클라이언트 저장 : 클라이언트는 이 JWT를 로컬 스토리지나 쿠키에 저장합니다.
    3. 요청 시 토큰 포함 : 클라이언트는 이후 서버에 요청을 보낼 때, 이 JWT를 HTTP 헤더(일반적으로 Authorization: Bearer <token>)에 포함하여 전송합니다.
    4. 서버 검증 : 서버는 전달된 JWT의 서명을 검증하여 토큰의 무결성을 확인하고, 페이로드의 클레임을 기반으로 요청의 유효성을 판단합니다.
    5. 요청 처리 : 토큰이 유효하면 서버는 요청을 처리하고, 그렇지 않으면 적절한 오류를 반환합니다.
  • 장점
    • 자급자족 : JWT는 페이로드에 필요한 모든 정보를 포함하기 때문에, 서버는 별도의 상태를 저장할 필요가 없다.
    • 확장성 : 분산 시스템에서 인증 정보를 쉽게 전파 가능
    • 보안 : 서명된 JWT는 클라이언트에서 변경되지 않도록 보호
  • 단점
    • 크기 문제 : JWT는 필요한 모든 정보를 포함하여 길이가 길어질 수 있고, 네트워크 비용을 증가 시킬 수 있다.
    • 서명 검증 : JWT가 서명되었다고 해서 암호화된 것은 아니므로, 민감한 정보는 페이로드에 담지 않아야한다.
    • 무효화 어려움 : 서버에서 발급된 JWT는 만료될 때까지 유효하기 때문에, 무효화 하기 어렵다.

4. 쿠키 (Cookie)

  • 사용자의 웹 브라우저에 자장되는 작은 데이터 조각
  • 웹 서버는 클라이언트(브라우저)에게 쿠키를 생성하여 보내며, 이후 클라이언트는 해당 쿠키를 요청 시마다 서버에 자동으로 전송
  • 사용 목적
    • 사용자의 로그인 상태, 사이트 설정, 장바구니 정보 등과 같은 상태를 유지하는 데 사용
    • 사용자가 다시 방문할 때, 이전의 상태를 기억하기 위해 사용
  • 특징
    • 저장 위치 : 쿠키는 클라이언트(사용자 브라우저)에 저장된다.
    • 수명 : 쿠키는 만료 시간을 지정할 수 있다. 설정하지 않으면 브라우저가 종료될 때 삭제되는 세션 쿠키가 된다.
    • 보안 : 쿠키는 클라이언트 측에서 저장되기 때문에, 민감한 정보는 저장하지 않는 것이 좋다. 아래 속성을 통해 보안을 강화 할 수 있다.
      • Secure : HTTPS로만 쿠키 전송
      • HttpOnly : 자바스크립트에서 쿠키에 접근하지 못하도록 설정
      • SameSite : 크로스 사이트 요청 위조(CSRF) 공격을 방지하기 위해 쿠키 전송을 제한

5. 세션 (Session)

  • 서버에서 생성된 사용자의 상태 정보를 유지하는 방식
  • 사용자가 웹 사이트에 접솔할 때, 서버는 고유한 세션 ID를 생성하고, 이 ID를 통해 사용자와 관련된 데이터를 서버측에 저장
  • 세션 ID는 클라이언트의 쿠키에 저장되며, 이후 클라이언트의 요청마다 이 세션 ID가 서버에 전송되어 사용자의 상태를 추적
  • 사용 목적
    • 로그인 상태, 사용자 환경 설정, 권한 정보 등의 상태를 유지하는 데 사용
  • 특징
    • 저장 위치 : 세션 데이터는 서버 측에 저장되며, 클라이언트에는 세션 ID만 쿠키로 저장
    • 수명 : 세션은 서버 측에서 관리되며, 일정 시간 동안 활동이 없으면 세션이 만료될 수 있습니다. 일반적으로 브라우저가 종료되면 세션도 종료
    • 보안 : 세션 데이터는 서버에 저장되기 때문에, 민감한 정보를 비교적 안전하게 관리할 수 있다. 세션 ID를 도난당하면 보안 문제가 발생할 수 있으므로, HTTPS를 사용해 세션 ID를 보호해야 한다

6. passport

  • Node.js 애플리케이션에서 인증을 처리하기 위해 널리 사용되는 미들 웨어
  • 다양한 인증 전략을 쉽게 통합할 수 있도록 도와주며, 로컬 로그인, OAuth, SAML 등 다양한 방식의 인증을 지원
  • 주요 개념
    • 전략(Strategy)
      • 각 전략은 특정 인증 방법을 처리하기 위해 구현된 모듈이다.
        • 예를들어 로컬 로그인은 passport-local 전략을 사용하고, Facebook 로그인은 passport-facebook 전략을 사용한다.
      • 원하는 인증 방법에 따라 전략을 선택하고 이를 passport에 등록하여 사용할 수 있다.
    • 시리얼라이즈(serialize) / 디시리얼라이즈(deserialize)
      • 시리얼라이즈는 사용자가 인증에 성공한 후, 사용자 정보를 세션에 저장하는 방식
      • 디시리얼라이즈는 세션에 저장된 정보를 기반으로, 사용자를 다시 인증된 상태로 복원하는 역할
      • 이 과정에서 주로 사용자 ID와 같은 최소한의 정보를 세션에 저장하고, 나중에 이 정보를 사용해 데이터베이스에서 사용자 전체 정보를 가져온다.

7. passport의 주요 메서드와 기능

  • passport의 흐름
    1. 사용자 요청이 들어오면 passport에 설정된 전략을 사용해 인증 시도
    2. 인증이 성공하면 serializeUser를 통해 사용자 ID를 세션에 저장
    3. 이후 모든 요청에서는 세션에 저장된 ID를 사용해 deserializeUser를 호출하고, 사용자 정보를 복원하여 인증된 사용자로서 요청을 처리
    4. 사용자가 로그아웃하면 세션이 파기되고, 사용자 인증 상태가 종료

 

1) passport.use()

  • 인증 전략을 등록할 때 사용
  • 예를 들어 로컬 전략을 사용하려면 아래와 같이 사용하면 된다.
passport.use(new LocalStrategy(
    function(username, password, done) {
        // 인증 로직
    }
));

// 간단한 예시
passport.use(new LocalStrategy(async (username, password, done) => {
    try {
        const [rows] = await connection.execute('SELECT id, userId, password FROM member WHERE userId = ? AND password = ?', [username, password]);

        if (rows.length > 0) {
            const user = rows[0];  // 로그인 성공한 사용자
            return done(null, user);
        } else {
            return done(null, false, { message: 'Invalid credentials.' });
        }
    } catch (err) {
        return done(err);
    }
}));
  • done함수의 의미와 파라미터들
    • done(error, user, info) 가 시그니처이다.
    • error 
      • 인증 과정에서 발생한 오류를 전달하며, 오류가 없을 경우 null을 전달한다.
    • user
      • 인증이 성공했을 때 사용자 정보를 전달한다. 만약 인증이 실패하거나 사용자를 찾지 못한 경우에는 이 자리에 false를 전달한다.
    • info 
      • 선택정인 정보 객체이다. 인증 실패 이유와 같은 추가적인 정보를 전달할 때 사용된다.
  • 꼭 username, password만 써야하는 건 아니지만 다른 걸로 필드명을 바꾸기 위해서는 아래와 같이 코드가 난잡해 질 수 있기 때문에 비추천한다.
passport.use(new LocalStrategy(
    { usernameField: 'userId', passwordField: 'secretword' },  // 필드명 변경
    async (userId, secretword, done) => {
        try {
            const [rows] = await connection.execute('SELECT id, userId, password FROM member WHERE userId = ? AND password = ?', [userId, secretword]);

            if (rows.length > 0) {
                const user = rows[0];  // 로그인 성공한 사용자
                return done(null, user);
            } else {
                return done(null, false, { message: 'Invalid credentials.' });
            }
        } catch (err) {
            return done(err);
        }
    }
));

 

2) passport.serializeUser()

  • 사용자 정보를 세션에 저장할 때 사용
  • 인증이 성공한 후, 사용자 객체를 어떻게 세션에 저장할 지 결정
  • 보통은 id만 세션에 저장하고, 나중에 이 id를 사용해 전체 사용자 정보를 불러온다.
passport.serializeUser(function(user, done) {
    done(null, user.id);
});

passport.serializeUser((user, done) => {
    done(null, user.id);
});

 

3) passport.deserializeUser()

  • 세션에 저장된 사용자 정보를 기반으로, 실제 사용자 객체를 복원할 때 호출
  • 세션에 저장된 사용자 ID를 사용해 데이터베이스에서 사용자 정보를 가져온다
passport.deserializeUser(function(id, done) {
    User.findById(id, function(err, user) {
        done(err, user);
    });
});

passport.deserializeUser(async (id, done) => {
    try {
        const [rows] = await connection.execute('SELECT id, userId, password FROM member WHERE id = ?', [id]);
        if (rows.length > 0) {
            const user = rows[0];
            done(null, user);
        } else {
            done(new Error('User not found'));
        }
    } catch (err) {
        done(err);
    }
});

 

 


4) passport.authenticate()

  • 특정 전략을 사용하여 인증을 수행할 때 사용
  • 이 메서드는 미들웨어로 사용되며, 요청 객체(req), 응답 객체(res), 다음 미들웨어(next)를 인수로 받는다.
  • 성공, 실패, 리다이렉트 등의 동작을 설정할 수 있다.
  • 다양한 옵션들 : 아래 예시에 적힌 것 외에도 다양한 옵션이 있다. 
    • successFlash, assignProperty, session 등이 있다. 공식 문서를 참고하자
app.post('/login',
    passport.authenticate('local', {
        successRedirect: '/',		//인증 성공 시 메인 페이지("/")로 이동
        failureRedirect: '/login',	//인증 실패 시 다시 로그인 페이지로 이동
        failureFlash: true			//인증 실패 시 사용자에게 보여줄 메시지 설정, true면 기본 메시지 사용
    })
);

 

5) passport.isAuthenticated()

  • 현재 사용자가 인증되어 있는지 확인하는 메서드
  • 인증된 사용자는 true, 아니면 false를 반환한다.
app.get('/profile', (req, res) => {
    if (req.isAuthenticated()) {
        res.render('profile');
    } else {
        res.redirect('/login');
    }
});

 

6) passport.login()

  • 사용자를 로그인 상태로 만드는 메서드
  • 직접 사용자가 인증되었을 때, 인증 상태를 유지하려고 할 때 사용된다.
req.login(user, function(err) {
    if (err) {
        return next(err);
    }
    return res.redirect('/profile');
});

 

7) passport.logout()

  • 현재 사용자 세션을 종료하는 메서드
  • 사용자가 로그아웃을 할 때 호출된다.
app.get('/logout', (req, res) => {
    req.logout(function(err) {
        if (err) {
            return next(err);
        }
        res.redirect('/');
    });
});