iT邦幫忙

2023 iThome 鐵人賽

DAY 17
0

Hi,大家好,昨天我們完成了使用者登入的雛形,今天來將這個雛形轉變為符合 ISO27001資通安全規則之使用者登入機制,那麼,就讓我們開始吧。

什麼是ISO27001資通安全規則,有哪些呢?

這幾年若是有接觸到公家專案或是金融機構專案的人,應該對ISO27001不陌生,行政院在近幾年非常注重資訊安全,因此有訂定出普、中、高三個等級的防護基準,並且每年進行稽核。在防護基準中,針對使用者管理有幾個要求項目,簡單說起來有下項目:

  1. 使用者密碼需有一定之強度(通常是8碼以上、密碼本身需有英文大小寫、數字、特殊符號三項的其中2項組成
  2. 需定時更換密碼(普級6個月更換一次,中級以上3個月更換一次),且密碼不可以前3代相同
  3. 變更密碼時,需以另外之媒體(例如mail或簡訊)再進行double check
  4. 密碼不可以明文存於資料庫中
    上述是簡單的說明,以下是我常用的對應的處理方式
  5. 密碼強度的部份,在設定密碼時,於前端與後端加入 regular expression(正規表示法)進行文字驗證。
  6. 資料庫中的使用者資料中,紀錄最後一次的密碼變更時間與預計可變更密碼時間,並於登入時進行檢查,密碼到期時強迫使用者更換
  7. 資料庫中的密碼欄位,在存入前先以 sha 或 md5等非對稱式加密進行加密後再存入,驗證時則是將表單傳入之欄位進行加密後進行驗證,看到加密之密文相同時,即表示驗證通過
  8. 系統在規畫時,儘量使用AD或第3方的使用者驗證,如google、apple、facebook…等,若限定台灣公務人員使用之情形,可以考慮使用我的e政府的單一登入模組,反正就是不要在系統中自己管理帳號密碼

side project處理方案

以下是這次 side project 中,我所規畫之使用者帳號密碼表

CREATE TABLE SYSUSER (
    USERID VARCHAR(20),              -- 使用者帳號
    PWD    VARCHAR(512),             -- 密碼密文
    USERNAME VARCHAR(40),            -- 使用者姓名
    EMAIL  VARCHAR(50),              -- e-mail
    USERSDT DATE,                    -- 帳號起始日期
    USEREDT DATE,                    -- 帳號到期日
    USERED VARCHAR(1) DEFAULT 'N',   -- 是否可用
    PWDSLIST TEXT,                   -- 前3代密碼
    PWDCHDATE DATE,                  -- 密碼異動日期
    GUID VARCHAR(40) default UUID_GENERATE_V4(),  -- 資料唯一編碼
    CONSTRAINT SYSUSER_PK PRIMARY KEY (GUID)
);

說明:
PWDSLIST密碼會直接存在 json 陣列,元素只有 3個,每次變更密碼時進行檢核,小於3個的話直接附加進陣列中,大於3個的情形則刪除第0個元素後,將修改前的密碼附加進入陣列中

修改系統登入router

/**
 * 系統登入
 */
router.post("/login", async (req, res, next) => {
    try {
        let uid = req.body.uid
        let pwd = req.body.pwd
        let userinfo = await userdao.chkuser(uid, pwd)
        if(!userinfo) {
            res.render("loginform", {
                breads: createBreadcrumb("系統登入"),
                errmsg: "登入失敗"
            })
            return false;
        } else {
            //驗證通過
            req.session.user = userinfo
            res.redirect("/saf/mgr")
        }
    } catch(err) {
        if(err === "密碼已逾期") {
            //密碼逾期後,紀錄目前登入之id後,導向至變更密碼畫面
            req.session.chgpwduser = uid
            res.redirect("/saf/chgpwd")
        } else {
            //驗證不通過,送回登入頁,並將錯誤訊息一併送回
            res.render("loginform", {
                breads: createBreadcrumb("系統登入"),
                errmsg: err
            })
        }
    }
})

說明:
接收表單傳入之帳號密碼後,呼叫 userdao.chkuser 進行驗證,若有回傳使用者資訊,則認定驗證通過,紀錄 session後重新導向至登入人員之系統主頁,驗證時若接收到「密碼已逾期」之錯誤,則重導至變更密碼畫面,否則回到系統登入表單

修改使用者驗證之功能

'use strict';
const sysuser = require("./db/sysuser")
const crypto = require('crypto');

/**
 * 密碼以sha 512 進行加密
 * @param {string} src 密碼原文
 */
 function sha512Str(src) {
    let hash = crypto.createHash('sha512');
    let data = hash.update(src, 'utf-8');
    let gen_hash = data.digest('hex');
    return gen_hash
 }

 /**
  * 以現在時間進行日期檢測 
  * @param {string} dat 檢測日期字串,需為 yyyy-mm-dd格式
  * @returns 大於 0 - 比現在晚,小於 0 - 比現在早、 等於 0 - 和現在一樣
  */
 function chdate(dat) {
    let datval = new Date(dat).getTime()
    let nowval = new Date().getTime()
    return datval - nowval
 }

 /**
  * 檢查密碼更換紀錄
  * @param {*} obj 
  */
function chkPwdTime(obj) {
    let pwddat = obj.usersdt
    if(obj.pwdchdate) {
        pwddat = obj.pwdchdate
    }
    let dv = chdate(pwddat)
    if(dv / 24 / 60 / 60 / 1000 < -90){
        return true
    } else {
        return false
    }
}

module.exports = {

    /**
     * 使用者驗證
     * @param {string} uid 系統帳號
     * @param {string} pwd 系統密碼
     */
    chkuser: async(uid, pwd) =>{
        return new Promise((resolve, reject) => {
            let shapwd = sha512Str(pwd)
            sysuser.findOne({
                where: {
                    userid: uid,
                    pwd: shapwd
                }
            }).then((data) => {
                if(!data) {
                    reject("帳號密碼錯誤")
                    return false;
                }
                let obj = data.dataValues
                if(obj.usered === "N") {
                    reject("帳號已停用")
                    return false;
                }
                if(obj.usersdt) {
                    if(chdate(obj.usersdt) > 0) {
                        reject("帳號尚未啟用")
                        return false;
                    }
                }
                if(obj.useredt) {
                    if(chdate(obj.useredt) < 0) {
                        reject("帳號已逾期")
                        return false;
                    }
                }
                if(chkPwdTime(obj)) {
                    reject("密碼已逾期")
                    return false;
                }
                resolve(obj)
            }).catch((err) => {
                reject(err.message)
            })
        })
    }
}

說明:
接收到帳號密碼後,先將密碼進行 sha512 之加密,並送入資料庫比對,若沒找到符合之資料,回傳帳號密碼錯誤之訊息,接下來依序檢查 1. 帳號是否停用、2. 若有設定啟用日的話,是否已經超過啟用日期了、3. 若有設定停用日期的話,是否已超過停用日期了、4. 密碼是否逾期,全部通過才認定通過使用者驗證作業

結語

近幾年相關資安要求的力度很大,因此使用者驗證的作法需要比較複雜一些,今天的部份完成了使用者驗證的機制,但是還沒結束,明天會再完成密碼變更之機制,此時才是一個完成的使用者驗證含密碼管理的功能,那麼,我們明天再繼續吧


上一篇
使用者登入機制
下一篇
變更密碼機制
系列文
以vue.js + node.js 搭建一個客服填單系統30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言