Server_Side

userModel

UserModel은 Schema를 감싸 주는 역할을 한다

Schema란 무엇인가?

Schema는 데이터베이스를 구성하는 데이터 개체, 속성, 관계 및 데이터조작 시 데이터 값들이 갖는 제약 조건 등에 대해 전반적으로 정의한다.

const userSchema = mongoose.Schema({
    name: {
        type: String,
        maxlength: 50
    },
    email: {
        type: String,
        trim: true,
        unique: 1
    },
    password: {
        type: String,
        minglength: 5
    },
    lastname: {
        type: String,
        maxlength: 50
    },
    role: {
        type: Number,
        default: 0
    },
    image: String,
    token: {
        type: String,
    },
    tokenExp: {
        type: Number
    }
})

Schema의 특징

  1. Schema는 Data Dictionary에 저장된다
    • Data Dictionary : 시스템 전체에서 나타나는 데이터 항목들에 대한 정보를 지정한 중앙 저장소로, 이 정보에는 항목을 참조하는데 사용되는 식별자, 엔티티의 구성요소, 항목을 참조하는 곳 등이 포함
  2. 특정 데이터 모델을 이용해서 만들어진다.
  3. 시간에 따라 불변이다.
  4. 데이터의 구조적 특성을 의미한다.
  5. 인스턴스에 의해 규정된다.

Git이란?

git은 형상 관리 도구중 하나이다.

  • 형상 관리 도구 즉, 버전 관리 시스템 중의 하나로 기업의 소스코드를 효과적으로 관리할수 있게 해주는 무료, 공개 소프트웨어이다.

git은 분산형 관리 시스템이다.

  • 소스코드를 여러 개발PC와 저장소에 분산해서 저장하기 때문에 중앙서버에 장애가 발생해도 로컬저장소에 커밋할수 있으며, 복원도 가능하다.

여러명이 동시 작업이 가능하다.

  • 브랜치를 통해 개발한뒤에 합치는 방식으로 개발을 진행한다.
  • 팀이아닌 개인 프로젝트라도 Git을 통해 버전 관리를 하기 수월하다.(pull을 통해 업데이트, patch를 통해 파일 배포)

(사진 출처: http://pismute.github.io/whygitisbetter/images/local-remote.png)

  • Repository : 저장소를 의미하며, 저장소는 히스토리, 태그, 소스의 가지치기 혹은 branch에 따라 버전을 저장한다. 저장소를 통해 작업자가 변경한 모든 히스토리를 확인 할 수 있다.

  • Working Tree : 작업자의 현재 시점.

  • Staging Area : 저장소에 커밋하기 전에 커밋을 준비하는 위치.

  • Commit : 현재 변경된 작업 상태를 점검을 마치면 확정하고 저장소에 저장하는 작업.

  • Head : 현재 작업중인 Branch

  • Branch : 가지 또는 분기점을 의미하며, 작업을 할때에 현재 상태를 복사하여 Branch에서 작업을 한 후에 완전하다 싶을때 Merge를 하여 작업을 한다.

  • Merge : 다른 Branch의 내용을 현재 Branch로 가져와 합치는 작업을 의미한다.

Bcrypt

단방향 암호화를 위해 만들어진 Hash 함수
기존 해쉬 함수들은 rainbow table attack(미리 Hash값들으 계산해놓은 테이블로 원래 정보를 찾아내는 해킹방법)에 취약함

Salting과 키 스트레칭

Salting

  • 실제 정보 이외에 추가적으로 무작위 데이터를 더해서 Hash값을 계산하는 방법.
  • salt로 인해 Hash값이 달라지기 때문에 rainbow attack 같이 미리 Hash값을 계산하는 공격을 무효화 시킨다.
  • salt 자체는 비밀이 아니고 Hash값을 바꾸는데 목적이 있다. 따라서 같은 비밀번호라도 Hash값이 달라지게 된다.

키 스트레칭

  • 단방향 Hash값을 계산한후 그 Hash값을 다시 Hash하고, 이를 반복한다. Hash의 반복횟수를 추가하여 계쏙 보완할 수 있다.

Bcrypt는 이러한 Salting과 키 스트레칭을 구현한 대표적인 함수다.

  • Alg : Algorithm 알고리즘 식별자 2a2a는 bcrypt를 뜻한다.
  • Cost : Cost factor 키 스트레칭 한 횟수. 2^n으로 1010 은 2^10 즉 1024 이다.
  • Salt : 128비트 Salt, 22자 base 64로 인코딩
  • Hash : Salting과 키 스트레칭 후 Hash 값

How to use Bcrypt?

https://www.npmjs.com/package/bcrypt
NPM 공식 사이트에서 Install 및 usage 방법을 숙지, 프로젝트에 적용

userSchema.pre('save', function (next) {
    var user = this;
    if (user.isModified('password')) {
        bcrypt.genSalt(saltRounds, function (err, salt) {
            if (err) return next(err);

            bcrypt.hash(user.password, salt, function (err, hash) {
                if (err) return next(err);
                user.password = hash
                next()
            })
        })
    } else {
        next()
    }
});

user모델에서 user정보를 받아온뒤 password를 hash함수로 암호화

userSchema.methods.comparePassword = function (plainPassword, cb) {
    bcrypt.compare(plainPassword, this.password, function (err, isMatch) {
        if (err) return cb(err);
        cb(null, isMatch)
    })
}

로그인 시 비밀번호를 check한뒤 일치하면 null값을 리턴하도록 callback함수로 지정한다

JsonWebToken

전자 서명 된 URL-safe (URL로 이용할 수있는 문자 만 구성된)의 JSON으로
전자 서명은 JSON 의 변조를 체크 할 수 있게되어 있다

JWT는 서버와 클라이언트 간 정보를 주고 받을 때 Http 리퀘스트 헤더에 JSON 토큰을 넣은 후 서버는 별도의 인증 과정없이 헤더에 포함되어 있는 JWT 정보를 통해 인증

JWT장점

  • 인증에 필요한 모든 정보를 토큰이 포함하고 있기 때문에 별다른 인증 저장소가 필요없다.
  • 디버깅 및 관리가 용이하다
  • 트레픽에 대한 부담이 적다

How To Use JWT?

https://www.npmjs.com/package/jsonwebtoken
npm 공식 페이지에서 나온대로 사용한다. 본 프로젝트에서는

userSchema.methods.generateToken = function (cb) {
    var user = this;
    console.log('user', user)
    console.log('userSchema', userSchema)
    var token = jwt.sign(user._id.toHexString(), 'secret')
    var oneHour = moment().add(1, 'hour').valueOf();

    user.tokenExp = oneHour;
    user.token = token;
    user.save(function (err, user) {
        if (err) return cb(err)
        cb(null, user);
    })
}

로 토큰을 만든뒤에 user Schema에서 토큰 필드에 저장을 한다.

const userSchema = mongoose.Schema({
    name: {
        type: String,
        maxlength: 50
    },
    email: {
        type: String,
        trim: true,
        unique: 1
    },
    password: {
        type: String,
        minglength: 5
    },
    lastname: {
        type: String,
        maxlength: 50
    },
    role: {
        type: Number,
        default: 0
    },
    image: String,
    token: {
        type: String,
    },
    tokenExp: {
        type: Number
    }
})

저장된 토큰필드에서 토큰을 비교하여 쿠키에 저장된 토큰과 동일하면 loginSuccess:True를 반환

router.post("/login", (req, res) => {
    User.findOne({ email: req.body.email }, (err, user) => {
        if (!user)
            return res.json({
                loginSuccess: false,
                message: "Auth failed, email not found"
            });

        user.comparePassword(req.body.password, (err, isMatch) => {
            if (!isMatch)
                return res.json({ loginSuccess: false, message: "Wrong password" });

            user.generateToken((err, user) => {
                if (err) return res.status(400).send(err);
                res.cookie("w_authExp", user.tokenExp);
                res
                    .cookie("w_auth", user.token)
                    .status(200)
                    .json({
                        loginSuccess: true, userId: user._id
                    });
            });
        });
    });
});

좋은 웹페이지 즐겨찾기