登録/ログイン/ログアウト/権限確認機能の実装


サーバの作成、dbへの接続後、クライアントが要求を受信し、会員登録、ログイン、権限確認を行う機能を実現しました.
クライアントに単独で処理させ,まず受信要求と処理のバックエンド部分のみを処理する.
会員登録 /api/users/registerでpostを介してサブスクリプション情報を渡します。 ユーザモデルを使用して受信したreq。新しいモデルインスタンスユーザーとしてbodyを作成します。 その後saveメソッドでuserをdbに保存します。(保存する前にpasswordはhashing暗号化を使用します。) 暗号化プロセス:bcryptのgenSaltメソッドによってsaltが生成され、ユーザーが指定したsaltRoundsによってhashメソッドでユーザーのpasswordがハッシュされます。したがって、mongodbにはハッシュパスワードが含まれています。

ログオンLogin&ログオフLogout (ログイン) /api/user/loginでpostを介してログイン情報を渡します。 ユーザーはfindOneメソッドで同じ電子メールを持つオブジェクトをインポートします(対応するオブジェクトがない場合はfalse)。 要求された電子メールがdbにある場合は、パスワードが正しいかどうかを確認します。(パスワード確認プロセス)bcryptの比較方法により/loginで発行されたパスワードと電子メールで見つけたユーザdbに存在するハッシュパスワードを比較する. パスワードが正しい場合、jwtを使用してユーザにトークンを生成し、クッキーに格納します。 トークン作成プロセス:ログイン情報に一致するdbのuserオブジェクトのidとsecretkeyを使用してtokenを生成します。以降のuserオブジェクトのtokenプロパティで、生成したtokenを挿入して保存します。 (ログアウト) dbでそのプレイヤーのidを見つけた後、プレイヤーのトークンをdbから削除し、認証を行わない。

権限認証 getは、/api/users/auth(認証を行う場所)で実行されます。 ミドルウェアauthによりクライアントのCookieからトークンを取得する. トークンを復号した後、dbでユーザを検索します。 プレイヤーが存在する場合、reqにユーザとtokenをreqに入れる。 復号プロセス:Cookie内のトークンをjwtのverifyメソッドで復号します。この場合、トークン生成時に使用するsecret keyを用いて復号することができる。復号化によりユーザのidを得ることができる。その後、復号により、求めたidがdbに存在する対応するuserのidと一致するかどうかを検証する。(トークンも検証する)

server/models/User.js(シナリオ、モデル、メソッドの作成)

const mongoose = require('mongoose');
const bcrypt = require('bcrypt');
const saltRounds = 10;
const jwt = require("jsonwebtoken")//토큰 발행 
//스키마 생성
const userSchema = mongoose.Schema({
  /*데이터를 mongodb에 저장하려면 먼저 구조(스키마)가 있어야 한다.
  스키마는 해당 컬렉션의 문서에 어떤 종류의 값이 들어가는지를 정의한다.
  mongoose 모듈을 불러와 mongoose.Schema 를 통해 스키마 객체를 생성한다.*/
  
  name: {
    type: String,
    maxlength: 50
  },
  lastname: {
    type: String,
    maxlength: 50
  },
  email: {
    type: String,
    trim: true,//email에 공백이 있을 때 없애주는 역할
    unique: 1//똑같은 이메일 못쓰게
  },
  password: {
    type: String,
    minlength: 5
  },
  role: {
    type: Number,
    default: 0
  },
  image: String,
  token: {//유효성관련
    type: String
  },
  tokenExp: {//token의 유효기간
    type: Number
  }
})
//pre는 mongoose 에서 가져온 메서드, save는 저장하기 전에 function을 실행
userSchema.pre('save', function(next) {//next는 바로 이 과정을 pass 함
  //현재 스키마에 들어있는 post된 password를 가져온다
  var user = this;
  //field에서 password가 변환될때만 password를 암호화 해준다.
  if (user.isModified('password')) {
    //bcrypt 패키지의 salt를 이용해서 비밀번호를 암호화 시킨다.
    //genSalt는 salt를 생성한다
    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//password를 암호화된 hash 로 바꿔준다
        next()//완료 후 돌아감
      })
    })
  } else {//그냥 나갈 곳을 만들어준다.
    next()
  }
});

userSchema.methods.comparePassword = function(plainPassword, callback) {
  //클라이언트가 입력한 비밀번호와 db의 비밀번호를 비교한다.
  bcrypt.compare(plainPassword, this.password, function(err, isMatch) {
    if (err) return callback(err)//같을때
    callback(null, isMatch)//다를때
  })
}

userSchema.methods.generateToken = function(callback) {
  var user = this;
  //jsonwebtoken을 이용해서 token 생성
  var token = jwt.sign(user._id.toHexString(), 'secretToken');//임의로 두번째(secret key) 지정
  //user._id + 'secretToken' = token (incode)
  //->
  //'secretToken' -> user._id (decode)

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

userSchema.statics.findByToken = function(token, callback) {
  var user = this;

  //토큰을 복호화(decode) 한다
  jwt.verify(token, 'secretToken', function(err, decoded) {
    user.findOne({ "_id": decoded, "token": token }, function(err, user) {

      if (err) return callback(err);
      callback(null, user);
    })
  })
}
const User = mongoose.model('User', userSchema);
module.exports = { User };
//모델 다른 곳에서도 쓸 수 있게 export 해준다.

server/middleware/auth.js(ライセンスミドルウェア)

const { User } = require('../models/User');
//decode 복호화(암호화해제) incode 암호화
let auth = (req, res, next) => {
  //인증 처리를 하는 곳
  //1. 클라이언트 쿠키에서 토큰을 가져온다. cookie parser 이용
  let token = req.cookies.x_auth;//암호화(incode)되있는 상태
  
  //2. 토큰을 복호화 한 후, 해당 유저를 찾는다.
  User.findByToken(token, (err, user) => {
    if(err) throw err;
    if(!user) return res.json({isAuth: false, error: true})

  //req에서 user와 token 을 사용할 수 있게 해준다.
  req.token = token;
  req.user = user;
  next();//미들웨어를 벗어나 계속 갈 수 있게
  })
}

module.exports = { auth };

server/index.js(サーバを作成しdbに接続し、リクエストを処理)

const express = require("express");
const app = express()
//client 에서 보내는 정보를 분석해서 서버에서 받을 수 있게 해준다.
//bodyParser를 사용하지 않으면 req.body가 undefinded를 default로 받는다.
const bodyParser = require("body-parser")
//모델을 가져온다.
const cookieParser = require("cookie-parser");
const config = require('./config/key');
const { auth } = require('./middleware/auth');
const { User } = require("./models/User");
//application/x-www-form-urlencoded
app.use(bodyParser.urlencoded({ extended: true }));
//application/json
app.use(bodyParser.json());
/*bodyparser는 client 에서 오는 정보를 "분석해서" 가져올 수 있게 한다.
x-www-form-urlencoded 이렇게 된 데이터와
json 형식의 데이터를 분석할 수 있게 하기 위해 윗 문장을 적어준다.*/
app.use(cookieParser());

const mongoose = require('mongoose');
//스키마를 만들고, 해당 스키마에 맞는 모델을 만들어 공통된 조건에 맞게 조회 및 저장이 가능하다.
mongoose.connect(config.mongoURI)//서버와 데이터베이스(mongoDB)를 연결
  .then(() => console.log('MongoDB Connected...'))
  .catch(err => console.log("MongoDB error: ", err));

app.get('/', (req, res) => res.send('hello world'))

app.get('/api/hello', (req,res)=> {
  res.send("Hello World~ ")
})

app.post('/api/users/register', (req, res) => {
  //받은 정보로 모델 생성, json 형식으로 req가 들어있다.
  const user = new User(req.body)
  
  user.save((err, doc) => {
    if (err) return res.json({ success: false, err })
    return res.status(200).json({ success: true })
  })
  //결과적으로 http post 메소드로 백엔드 서버로 유저 정보를 날려주고
  //백엔드 서버에서 save 메소드로 DB에 저장을 해준다
})

app.post('/api/users/login', (req, res) => {
  //요청된 이메일을 데이터베이스에서 찾는다. mongoDB 메서드 이용
  User.findOne({ email: req.body.email }, (err, user) => {
    //요청한 email이 db정보 안에 있을 때 해당 db정보를 담은 객체 user 가 생성된다.
    if (!user) {
      return res.json({
        loginSuccess: false,
        message: "제공된 이메일에 해당하는 유저가 없습니다."
      })
    }
    //요청된 이메일이 데이터 베이스에 있다면 비밀번호가 맞는 비밀번호 인지 확인 
    user.comparePassword(req.body.password, (err, isMatch) => {
      if (!isMatch) //비밀번호가 틀림
        return res.json({ loginSuccess: false, message: "비밀번호가 틀렸습니다" })
      //비밀번호가 맞다면 그 유저를 위한 토큰 생성
      user.generateToken((err, user) => {//token이 들어있는 user 객체
        if (err) return res.status(400).send(err);

        //토큰을 저장한다. 어디에? 쿠키, localStorage 등.. 지금은 쿠키에
        res.cookie("x_auth", user.token)
          .status(200)//성공했다는 표시
          .json({ loginSuccess: true, userId: user._id })
      })
    })
  })
})

//현재의 role => role 0 -> 일반유저, role 0 아니면 관리자
app.get('/api/users/auth', auth/*미들웨어*/, (req, res) => {
  //여기 까지 미들웨어를 통과해 왔다는 얘기는 authentication 이 true 라는 말
  res.status(200).json({
    _id: req.user._id,//auth에서 user 를 req 에 넣었기 때문에 사용가능
    isAdmin: req.user.role === 0 ? false : true,//변경가능
    isAuth: true,
    email: req.user.email,
    name: req.user.name,
    lastname: req.user.lastname,
    role: req.user.role,
    image: req.user.image
  })
})

app.get('/api/users/logout', auth, (req, res)=> {
  //첫번째 인자에 찾을 조건, 두번재 인자에 변경할 것
  User.findOneAndUpdate({_id: req.user._id/*auth에서 req에 넣어줌*/}, 
  {token: ""},
  (err, user)=> {
    if(err) return res.json({success: false, err});
    return res.status(200).send({success:true})
  })
})


const port = 5000
//5000번 포트에서 연결을 청취하고, 연결됬을 시 콜백함수를 실행한다.
app.listen(port, () => console.log(`Example app listening on port ${port}!`))

開発環境


開発は2つの環境で行うことができる.1つはローカルでローカル開発モードで行うことができ、もう1つはherokuまたは他のクラウドサービスを使用して導入(導入)し、本番モードで開発することができます.
二つのことは別々に考えなければならない.

server/config/key.js(環境内のmongodbに接続する方法)

if(process.env.NODE_ENV/*환경변수*/ === 'development')
//process.env.NODE_ENV 는 현재 위치한 모드를 가리킨다.
{//development 모드
  module.exports = require('./prod');
} else {//production 모드(deploy, 배포한 후)))
  module.exports = require('./dev');
}

server/config/dev.js(開発モード)


アカウントとパスワードを省略しました.
//development 모드에서 사용할 것

module.exports = {
  mongoURI: 'mongodb+srv://--id--:[email protected]/myFirstDatabase?retryWrites=true&w=majority'
}

server/config/prod.js(本番モード)

//production 모드에서 사용할 것
module.exports = {
  mongoURI: process.env.MONGO_URL
}
github - NodeReact