0%

Web Security 網頁安全泛指針對網頁、網站上的資訊安全,涵蓋程式漏洞、邏輯漏洞、資訊洩漏等。

Cross Site Request Forgery(XSRF or CSRF)

指在受害者不知情狀況下,被其他網域借用身份來完成未經同意的 HTTP Request。
當網站使用 cookie-based authentication,會把 session id 和 session data 存在 cookie 中,剛使用者曾在其他網站登入而未登出,並且進入惡意網站操作時,攻擊者利用可以進行跨域 http 請求的 http tag(ex: img、iframe等)埋入惡意的 request 程式碼,觸發後因為使用者未登出,因此攻擊者獲得 cookie 資料,攻擊者得以冒用使用者身份通過驗證。得以任意改變使者者在資料庫的資料。

同源政策對於連結(links)、重新導向(redirect)、表單(form)等的跨來源存取都是寬鬆的,因此駭客利用此特性做 CSRF 攻擊。

如何防範:

  1. 使用 token-based authentication
  2. 使用 HttpOnly 來防止 Cookie 被 JavaScript 讀取
  3. 目前各大成熟的框架,都有針對傳遞資料做 CSRF 驗證,網站會產生的一組密碼,並在請求送出時,檢查那組密碼是否正確。

Cross Site Request Forgery attacks are not an issue if you are using JWT with local storage. On the other hand, if your use case requires you to store the JWT in a cookie, you will need to protect against XSRF. XSRF are not as easily understood as XSS attacks. Explaining how XSRF attacks work can be time-consuming, so instead, check out this really good guide that explains in-depth how XSRF attacks work. Luckily, preventing XSRF attacks is a fairly simple matter. To over-simplify, protecting against an XSRF attack, your server, upon establishing a session with a client will generate a unique token (note this is not a JWT). Then, anytime data is submitted to your server, a hidden input field will contain this token and the server will check to make sure the tokens match. Again, as our recommendation is to store the JWT in local storage, you probably will not have to worry about XSRF attacks.

One of the best ways to protect your users and servers is to have a short expiration time for tokens. That way, even if a token is compromised, it will quickly become useless. Additionally, you may maintain a blacklist of compromised tokens and not allow those tokens access to the system. Finally, the nuclear approach would be to change the signing algorithm, which would invalidate all active tokens and require all of your users to log in again. This approach is not easily recommended, but is available in the event of a severe breach.

Cross Site Scripting(XSS)

「XSS攻擊通常指的是通過利用網頁開發時留下的漏洞,通過巧妙的方法注入惡意指令程式碼到網頁,使用戶載入並執行攻擊者惡意製造的網頁程式。 這些惡意網頁程式通常是JavaScript,但實際上也可以包括Java,VBScript,ActiveX,Flash或者甚至是普通的HTML。」–wikipedia

使用者以為安全進入網站後反而被導向到釣魚網站、甚至被竊取帳號密碼、個人資料。
目前 XSS 攻擊的種類大致可以分成以下幾種類型:

  • Stored XSS (儲存型):
    會被保存在伺服器資料庫中的 JavaScript 代碼引起的攻擊即為 Stored XSS,最常見的就是論壇文章、留言板等等,因為使用者可以輸入任意內容,若沒有確實檢查,那使用者輸入如 <script> 等關鍵字就會被當成正常的 HTML 執行,標籤的內容也會被正常的作為 JavaScript 代碼執行。

  • Reflected XSS (反射型):
    由網頁後端直接嵌入由前端使用者所傳送過來的內容造成的,最常見的就是以 GET 方法傳送資料給伺服器時,伺服器未檢查就將內容回應到網頁上所產生的漏洞。

  • DOM-Based XSS (基於 DOM 的類型):
    網頁上的 JavaScript 在執行過程中,沒有詳細檢查,輸入任意的內容都會被建立成有效的 DOM 物件,包含嵌入的代碼也會被執行,使得操作 DOM 的過程代入了惡意指令。但這樣的攻擊除非攻擊者親自到受害者電腦前輸入,否則不可能讓受害者輸入這種惡意代碼,通常需要搭配前兩個手法;讓內容保存在伺服器資料庫中、或是以反射型的方式製造出內容,再藉由JavaScript 動態產生有效的 DOM 物件來運行惡意代碼。

如何防範:

  1. 前兩種 Stored、Reflected 的類型都必須由後端進行防範,除了必要的 HTML 代碼,任何允許使用者輸入的內容都需要檢查,刪除所有「<script>」、「 onerror=」及其他任何可能執行代碼的字串。

  2. DOM-Based 則必須由前端來防範,但基本上還是跟前面的原則相同。
    另外不同的一點就是應該選擇正確的方法、屬性來操作 DOM,譬如document.getElementById('show_name').innerHTML = name; 中的innerHTML屬性代表插入的內容是合法的 HTML 字串,所以字串會解析成 DOM 物件,此處使用innerText會被保證作為純粹的文字,也就不可能被插入惡意代碼執行了。

Cross Site Scripting attacks occur when an outside entity is able to execute code within your website or app. The most common attack vector here is if your website allows inputs that are not properly sanitized. If an attacker can execute code on your domain, your JWT tokens are vulnerable. Many frameworks, including Angular, automatically sanitize inputs and prevent arbitrary code execution. If you are not using a framework that sanitizes input/output out-of-the-box, you can look at plugins like caja developed by Google to assist. Sanitizing inputs is a solved issue in many frameworks and languages and I would recommend using a framework or plugin vs building your own.

SQL injection

「Injection 是一種駭客常用的攻擊概念,透過注入惡意的程式碼的方式,破壞原本程式碼的語意,來達到攻擊的效果。SQL 是網站常用來取得資料的程式語法,SQL Injection 便是駭客透過修改 SQL 語句,改變他的語意,達成竊取資料/破壞資料的行為。」
一次看懂 SQL Injection 的攻擊原理

「在輸入的字串之中夾帶SQL指令,在設計不良的程式當中忽略了字元檢查,那麼這些夾帶進去的惡意指令就會被資料庫伺服器誤認為是正常的SQL指令而執行,因此遭到破壞或是入侵。」
–wikipedia

「在 SQL 語法中常會出現攻擊者可以透過更改語法邏輯或加入特殊指令的方式,在未設定過濾惡意程式碼的情況下,資料庫伺服器(DataBase)會直接接收使用者所輸入的 SQL 指令並執行攻擊代碼,使攻擊者能夠取得最高權限,得以擅自竊取、修改、挪動或刪除資料的可能。此類漏洞無疑的會對公司或商號造成相當大的損傷。」
攻擊行為-SQL 資料隱碼攻擊 SQL injection

常見攻擊手法:

  • Authorization Bypass(略過權限檢查)
  • Injecting SQL Sub-Statements into SQL Queries(注入 SQL 子語法)
  • Exploiting Stored Procedures(利用預存程序)

防範手法:

  • 使用 SQL query builder for JavaScript - knex.js:
    Read carefully from knex documentation how to pass values to knex raw (http://knexjs.org/#Raw).
    If you are passing values as parameter binding to raw like:

    1
    knex.raw('select * from foo where id = ?', [1])

    In that case parameters and query string are passed separately to database driver protecting query from SQL injection.

    Other query builder methods always uses binding format internally so they are safe too.

    Biggest mistake that one can do with knex raw queries is to use javascript template string and interpolate variables directly to SQL string format like:

    1
    knex.raw(`select * from foo where id = ${id}`) // NEVER DO THIS

    One thing to note is that knex table/identifier names cannot be passed as bindings to driver, so with those one should be extra careful to not read table / column names from user and use them without properly validating them first.

    Refer from:Does Knex.js prevent sql injection?

  • 使用 ORM(object relational mapping):
    在資料庫和 model資料容器之間的框架,他可以幫助開發者更簡便安全的去資料庫讀取資料,透過 ruby, java 等程式語言,去操作資料庫語言。同時因為是操作程式語言,若 query 中的值不符合預期格式,框架會自動擋掉而不會讓 SQL injection 成功。目前大部分的網站都是使用框架來開發,而這些框架都是使用 ORM 來處理他們的資料庫,因此在 ORM 的保護下,不是我們預期的資料格式,而是 SQL 語法的話,具有基本的保護能力。

  • 參數化查詢(parameterized query 或 parameterized statement):
    是指在設計與資料庫連結並存取資料時,在需要填入數值或資料的地方,使用參數(parameter)來給值,這個方法目前已被視為最有效可預防SQL注入攻擊的攻擊手法的防禦方式。 – 參數化查詢

  • 使用 Regular expression 驗證過濾輸入值與參數中惡意代碼,將輸入值中的單引號置換為雙引號。

  • 限制輸入字元格式並檢查輸入長度。

  • 資料庫設定使用者帳號權限,限制某些管道使用者無法作資料庫存取。

Reference

CSRF
網頁安全】給網頁開發新人的 XSS 攻擊 介紹與防範
資安常見攻擊( Web ) 與學習方法

根據 google 開發者指南,選擇正確的網頁存儲機制對本地設備和基於雲端的服務器存儲都很重要,良好的存儲引擎可以確保以可靠的方式保存信息,同時減少佔用的網路頻寬並提升響應能力,正確的存儲緩存策略是實現離線移動網頁體驗的核心構建基礎。
Never Load the Same Resource Twice - 網頁存儲概覽

Session 是什麼?

session 可以被翻譯作「一系列動作、具有上下文意義的狀態」或被簡單的翻作「會話」、「工作階段」。
由於 http 協定是無狀態(stateless)的設計,server 和 client 不會一直保持連線狀態,也不會有雙方狀態的即時更新,每一次的請求和回應的都是獨立的,沒辦法記憶內容,而 session 就是讓 client 和 server 之間保持狀態的解決方案。
session 可以說是一種機制,可以讓伺服器記住訪問者的身份,避免短時間內重複載入,不同程式語言對於 session 機制有不同的實現方法。

Session 機制的實作

網址

將 request 內容附加在網址上,server 透過網址資訊判斷 client 狀態。

Cookies

cookie 是最常見的方式,可以把資訊記錄在本機端的文檔中,網站只能儲存使用者確實輸入過的內容而無法存取電腦內的資料。
cookie 的機制就是當 clinet 發送 request 時,server 收到 request 後透過 Set-Cookie 語法讓瀏覽器儲存一些內容(設置 cookie),而這些內容會在瀏覽器發送 request 時一併送達 server,server 透過 cookie 內容決定狀態。

  • cookie-based session: 把所有 session 狀態存在 cookie 裡面並且加密
  • 在 cookie 只儲存 session id,其餘狀態(session data)都存在 server,透過 session id 來向 server 請求訪問資料。

    但 cookie 的問題是認證不認人,一旦 cookie、session id 被偷走,別人就可以偽造身份來登入,這也就是為什麼有時候如果駭客取得一大串 session id 的時候,有用戶會發現帳戶被系統自動登出,讓該 session id 失效。
    由於 session 代表意義廣泛、多處有使用到此詞,因此有時候口語中講到 session 也會直接指存在 cookie 中的 session id 和 session data。

  • 要注意 HttpOnly 與 Secure 的設定值

cookie-based authentication 流程:

  1. User enters their login credentials.
  2. Server verifies the credentials are correct and creates a session which is then stored in a database.
  3. A cookie with the session ID is placed in the users browser.
  4. On subsequent requests, the session ID is verified against the database and if valid the request processed.
  5. Once a user logs out of the app, the session is destroyed both client-side and server-side.

Token

Token-based authentication 是無狀態的,server 不會保留有關哪些用戶已登入或已發出哪些 token 的紀錄,取而代之的是,對 server 的每個請求都帶有一個 token,server 使用該 token 來驗證請求的真實性。
The token is generally sent as an addition Authorization header in the form of Bearer {JWT}, but can additionally be sent in the body of a POST request or even as a query parameter.

一般 token 的流程如下:

  1. User enters their login credentials.
  2. Server verifies the credentials are correct and returns a signed token.
  3. This token is stored client-side, most commonly in local storage - but can be stored in session storage or a cookie as well.
  4. Subsequent requests to the server include this token as an additional Authorization header or through one of the other methods mentioned above.
  5. The server decodes the JWT and if the token is valid processes the request.
  6. Once a user logs out, the token is destroyed client-side, no interaction with the server is necessary.

Token 優於傳統 cookie-based 方法的原因為:

  • Stateless, Scalable, and Decoupled,
    使用 token-based appoarch,後端不需要保存 token 的紀錄,每個 token 包含驗證所需的資訊,server的工作只要給予成功登入的請求 signed token,並且驗證後續的 token 是否一致。 Third party services such as Auth0 can handle the issuing of tokens and then the server only needs to verify the validity of the token.
  • Cross Domain and CORS,不同於 cookies 只能在單網域或子網域中運作,token-based approach with CORS enabled 所以可以被不同 services 和 domains 訪問。

JWT (JSON Web Token)

A JSON Web Token is comprised of three parts: the header, payload, and signature.
Header:

1
2
3
4
{
'typ': 'JWT', # 聲明類型
'alg': 'HS256' # 加密的方法: HMAC、SHA256、RSA 進行 Base64 編碼
}

Payload: 用來放用來放必要的資料(通常是用來認證使用者的資料)或額外的資料(metadata),存放溝通訊息。
Signature: 由三個部分組成,加密後的 header、加密後的 payload以及 secret,secret 要保存在 server 端,JWT 的簽發驗證都必須使用這個 secret,當其他人得知這個 secret 就表示 client-side 可以自己簽發 JWT,因此任何場景都不應該外流。

Tokens Are Signed, Not Encrypted. Token could be signed by the HMACSHA256 algorithm, and the header and payload are Base64URL encoded, it is not encrypted. Base64URL can be decoded.
It should go without saying that sensitive data, such as passwords, should never be stored in the payload. If you must store sensitive data in the payload or your use case calls for the JWT to be obscured, you can use JSON Web Encryption (JWE). JWE allows you to encrypt the contents of a JWT so that it is not readable by anyone but the server.

Commonly, the JWT is placed in the browser’s local storage and this works well for most use cases.There are some issues with storing JWTs in local storage to be aware of. Unlike cookies, local storage is sandboxed to a specific domain and its data cannot be accessed by any other domain including sub-domains.

You can store the token in a cookie instead, but the max size of a cookie is only 4kb so that may be problematic if you have many claims attached to the token. Additionally, you can store the token in session storage which is similar to local storage but is cleared as soon as the user closes the browser.

Web Storage

HTML5 提供的 web storage 方法分成兩種: session storage 和 local storage。
這是一種可以讓網頁儲存資料於本地端的技術,作用與 cookie 相同,但與只有 4k 容量的 cookie 不同,基本上一般瀏覽器支援 5M 的儲存空間。而且 cookie 會在伺服器和用戶端之間旅行,而 web storage 則是儲存於用戶端,不會佔用網路頻寬、不影響網站效能,因此可以把安全性低而量較大的資料以 web storage 形式儲存。

web storage 儲存形式為 string,存取的程式指令寫在用戶端的 JavaScript 中,資料結構為 key/value pair。

  • local storage: 永久性儲存在瀏覽器中,關閉瀏覽器也不會消失,除非手動刪除。
  • session storage: 只存在單一分頁,開新分頁即是新的 session,資料無法跨分頁分享。

隔離:瀏覽器會把不同網站的 local storage 隔離開來,基於「同源政策」避免不同網域的訪問者進行惡意攻擊,某個網站的網頁所儲存於 local storage 的資料,其他網站是看不到的,只有來自相同網站的網頁才能共享資料。

  • 不同協定視為不同源: HTTP 和 HTTPS 不同源
  • 主機名稱: 相同 host name 底下的虛擬路徑屬於相同來源,不同主機名稱視為不同源

Session 安全設計原則

  • 需有一定期限的生命週期(expire機制)
  • 盡量利用原有架構 session 管理的機制
  • 登入成功後的 sessoin id 必須強制更換

Notes

Reference

延伸閱讀

position

  • static 預設,只有套用此預設的元素屬於「不會被特別定位」

  • relative 表現與 static 一樣,除非有設定 top, right, bottom, left 等其他屬性,會使元素相對的調整其原本該出現的所在位置,不管「相對定位」過的元素如何在頁面上移動位置或增加多少空間,都不會影響到原本其他元素所在的位置。

  • fixed 相對於瀏覽器視窗來定位,表示即便頁面捲動他還是會固定在相同位置,使用 top, right, bottom, left 來定位,固定元素不會保留它原本在頁面上應有的空間,不會跟其他元素的配置相干擾。

  • absolute 元素的定位是他所屬的上層容器的相對位置,若元素的上層沒有可以被定位的元素,那就會以相對於整個網頁,也就是 <body> 的相對位置。當畫面捲動時元素會隨頁面捲動。

box-sizing

  • border-box 元素的內距和邊框不會增加元素本身的寬度。
    因為部分瀏覽器版本還未支援,需要做瀏覽器兼容設定:
    1
    2
    3
    -webkit-box-sizing: border-box;
    -moz-box-sizing: border-box;
    box-sizing: border-box;

其他屬性值:

  • content-box 預設,長寬值只包含 content,不包括 border 和 padding。
  • inherit 從上層元素繼承

display

  • none The element is completely removed.
  • inline Displays an element as an inline element (like <span>). Any height and width properties will have no effect.
  • block Displays an element as a block element (like <p>). It starts on a new line, and takes up the whole width.
  • inline-block Displays an element as an inline-level block container. The element itself is formatted as an inline element, but you can apply height and width values.
  • flex Displays an element as a block-level flex container.
  • table Let the element behave like a element
  • table-cell Let the element behave like a element
  • table-row Let the element behave like a element

Reference

https://zh-tw.learnlayout.com/toc.html
https://www.w3schools.com/cssref/pr_class_display.asp
https://www.oxxostudio.tw/articles/201501/css-flexbox.html

Git 向遠端儲存庫傳輸資料有兩種方式:SSH 金鑰 或 HTTPS 通訊協定。

問題

這次發生 git push 失敗時,是打算透過 HTTPS 遠端傳輸檔案,將完成的檔案 git commit 到已經創建好的 Github Repository,但 iTerm 顯示下面資訊:

1
2
3
remote: You must verify your email address.
remote: See https://github.com/settings/emails.
fatal: unable to access ‘https://github.com/username/repo.git/’: The requested URL returned error: 403

P.S. 這裡 username 代表自己的使用者名稱;repo 代表 repository名稱。

參考 Github 官方的說明 ,先前使用 Github 時已 verify 過 email 無法重寄認證信,而且確認設定的遠端地址正確。

原因:Git credential storage

之前使用的 Github 帳戶因為 email 刪除已經不存在,
即便已經重設過帳密,但是因為這台電腦儲存了我之前的帳戶的帳密,每次要 git push 的時候就會因為和遠地帳戶帳密不符而失敗。

類似的問題像是更改 username 或 密碼等也會遇到相同錯誤。
在 Stackoverflow 也有查詢到解法:改用 SSH 金鑰傳輸就不用認證帳密,如果還是想用 HTTPS 傳輸的話,就要研究一下要怎麼修改電腦設定。

要探討這個問題,須先了解 Git 的認證機制:
通過 HTTPS 傳輸時,預設需要輸入帳號密碼才可以訪問,而 Git 內建 credential storage 提供了開發者將帳密儲存於自己電腦中的功能,就不用每次遠端傳輸資料時都要輸入 帳號密碼;儲存可分為 osxkeychain、store、chache 三種方式。

解決辦法

先用這個指令查詢電腦系統支援的 git credentials 形式:

1
git help -a | grep credential

在不同的作業系統平台,Git credential storage 有不同的預設值,如果是 mac OS X 用 Homebrew 安裝 Git ,會得到如下的資訊:

1
2
3
4
5
credential                remote-ext
credential-cache remote-fd
credential-cache--daemon remote-ftp
credential-osxkeychain remote-ftps
credential-osxkeychain remote-http

Homebrew 安裝過程預設 HTTPS 通訊協定情況使用 osxkeychain 輔助工具來儲存認證訊息,表示透過 HTTPS 連接 Repository 時,系統用 osxkeychain 儲存的帳密向其要求傳輸權限,所以如果把 HTTPS 的 credential 設定修改,就能重新輸入新的、正確的帳號密碼了。

修改方式如下:

  1. 用這行命令找出所有 credential.helper 在電腦中的位置:
    1
    git config --show-origin --get credential.helper

通常可能是在…/usr/share/git-core/gitconfig

  1. 找到 .gitconfig 文件後,將下面這段文字修改後儲存:
    1
    2
    3
    4
    原始預設:
    [credential] helper = osxkeychain
    刪除 osxkeychain 變成:
    [credential] helper =

此時沒有設置儲存方式,之後每次 push 都會詢問帳密。

  1. 輸入以下指令來檢查是否已經成功取消osxkeychain設置,如果還是出現 osxkeychain 表示沒有完成,可能一台電腦上有多個 .gitconfig 文件需要處理

    1
    git config credential.helper
  2. 檢查完成後,進行 git push , 此時 git 會詢問帳密,便可輸入新的、正確的帳號密碼了。

  3. 最後可以直接在 iTerm 命令列恢復 osxkeychain 設定:

    1
    git  config --global  credential.helper  osxkeychain

或開啟.gitconfig 文件修改 [credential] helper = osxkeychain ,由於使用 store 方式會產生一個明碼的檔案在電腦中,比較不建議,為了安全起見,還是使用 osxkeychain 方式來儲存帳密。而如果是用 cache 方式的話,會將帳密暫存在硬碟中,一定時間內可以快取,但時間過了就要再重新輸入。
這樣就完成了 git credentials 的重新設置,另外如果想要查看 git config 的相關設定,可以用這個指令 git config --list

Scrum 是什麼?

Scrum 是一個流程框架,從 1990 年代初期由 Ken Schwaber 和 Jeff Sutherland 共同發展出來的,他被用在研究和辨識市場、技術、產品性能的可能性,以及開發產品和加強功能,人們透過運用這個框架來處理錯綜複雜的調適性問題,並且講求團隊運用其生產力和創意來盡可能的交付高價值的產品。

“A framework within which people can address complex adaptive problems, while productively and creatively delivering products of the highest possible value.”
The Scrum Guide

Scrum 如何運作?

將所有工作事項切分成多個可量化執行時間、工作量的小任務,並依照優先順序分配小任務於每個開發週期中,這個週期稱為 sprint,也就是快速衝刺的意思,通常以 2-4 週為一個 sprint,團隊成員必須於時限內完成計畫目標,當一個 sprint 結束時,會將完成的任務整理成一個可檢視的成果,再繼續進行下一個週期,反覆循環直到專案完成。

在管理進程上,會此用 Scrum board 來視覺化表示進度,例如將 board 分為 story(使用情境)、todo(細節功能)、WIP(work-in-process)、to verify(QA、test) 和 done 這幾個欄位,並用一張便利貼表示一個任務,當任務有進度時,便將便利貼移動到所屬進度,依照狀態及時挪動,直到完成,這樣的作法可以讓團隊成員共享資訊並清楚所有工作流程和進度。

scrum 的運作依靠以下重要物件、活動、特質:

  1. 使用者故事
  2. task: 根據 user story 列出所有需要被完成的任務
  3. product backlog: 產品代辦清單
  4. sprint backlog: 衝刺代辦清單
  5. product increment: 可隨時發佈的可運作產品
  6. sprint meeting
  7. daily srcum
  8. sprint review meeting
  9. sprint retrospective meeting
  10. 團隊專注、勇氣、開放、承諾和尊重

專案角色

在 scrum 中需要有明確的角色分配,主要角色為:

  1. product owner 代表客戶立場,整合客戶意見、使用者回饋,確保開發中產品符合需求,負責規劃使用者故事,並與團隊討論
    決定開發優先順序,在開發過程協助團隊釐清產品需求。在每個 sprint 週期結束時,決定使否將此週期的成果發佈。

  2. scrum master
    確保專案按照 scrum 方式開發,並排除任何會影響 scrum 流程的障礙,是規則的執行者。

  3. develop team
    負責開發和交付產品,包括設計、工程等個領域的專業人士。

其他角色可能為:
stackholder 利益關係者,可能是客戶

kanban v.s. scrum

kanban 也是一種敏捷開發框架,由豐田汽車的創辦人 豐田喜一郎開發出來的一套管理方法,以「減少浪費」、「持續改進」為生產管理目標,也使用 kanban board 來視覺化作業流程,限制半成品(work-in-progress)的數量,監控與管理作業流程,建立明確的規劃,建立良好的訊息回饋路徑,團隊間互相合作,帶著實驗精神展開持續的改善。屬於長期的工作計畫,可以在任何時間點,透過團隊討論達成共識,改變專案執行方式和流程,沒有明確的專案角色,視專案和團隊需求而定,同時持續發布產品更新,由團隊成員共同決定是否發佈。

兩者的主要不同之處為:

  • scrum 限制每個任務執行時間,kanban 主要限制同時執行的任務數目
  • scrum 有明確的專案角色,kanban 沒有
  • scrum 有明確的週期時間,kanban 是長期持續進行
  • scrum 在一個 sprint 結束後發佈產品,kanban 因為沒有明確週期,所以可以隨時發佈產品

兩者的相同之處是:

  • 採用視覺化的流程管理,公開透明化讓團隊成員可以共同討論、檢視。
  • 有週期性檢閱成果,持續改善優化工作內容與流程
  • 將複雜龐大的工作項目切分成多項可執行小任務,提升工作效率。

React lifecycle methods & state updating

lifecycle methods 可以在 react component 中使用的調用方法,當 react app 被 render 時,會按照 lifecycle 的順序檢查每個 methods,如果開發者有寫到就會執行裡面的調用方法:

  • Mounting
    • constructor()
    • componentWillMount()
    • render()
    • componentDidMount()

      在 component 完成第一次 Mounting 的時候呼叫

  • Updating
    Everytime the state got changed, it runs updating.
    • componentWillReceviceProps()
    • shouldComponentUpdate()
    • componentWillUpdate()
    • render()
    • componentDidUpdate()

      當 component 完成 updating

  • Unmounting
    • componentWillUnmount()

      This method is called when a component is being removed from the DOM.

提升 React 效能:保持 virtual DOM 的一致

  • DOM (Document Object Model)是什麼?
    DOM 文件物件模型,是 Html, XML, SVG 等文件的程式介面,提供一種文件樹的結構化表示方法,可以讓程式存取並改變文件架構、風格(style)和內容,透過 DOM 結構,文件擁有屬性、函式節點,物件可以和事件處理程序連結,當事件被觸發,執行處理程序來存取或改變文件。

  • React 如何改變 DOM 以及如何提升效能?
    由於讀取更新 DOM 損耗 browser 的執行率,每一次改變便 render 整個頁面在 JavaScript 是很慢的,React 使用 Virtual DOM 虛擬 DOM 的一個強大的 Render 系統,只需更新 DOM 而不需要從 DOM 讀取,以減少運算。
    同時, React 的 diff 演算法,當 render function running 得時候,讓 virtual dom 和 real dom 比對只重繪有更新的部分,做出最低必要的更新。
    官方文件:
    When you use React, at a single point in time you can think of the render() function as creating a tree of React elements. On the next state or props update, that render() function will return a different tree of React elements. React then needs to figure out how to efficiently update the UI to match the most recent tree.

  • 開發者提升 React 應用程式的方法
    盡可能的減少讀取和更新 DOM
    We must minimize the amount of work that we do to the DOM.

當 state 的結構是 array of objects 的時候,更新 state 的方法需要考慮到 data structure 和 react update 機制。

Immutable Data Structures

由於 react 的 state update 機制是觸發 render function 後會讓 virtual dom 和 real dom 比對只重繪有更新的部分,做出最低必要的更新。
因此每次變動 state 的時候需要這樣寫:

1
2
3
4
5
6
7
8
9
10
this.setState({
obj: {
...this.state.obj,
id: 2
}
})

this.setState({
list: [...this.state.arr, 123]
})

而不能這樣寫:

1
2
3
4
5
6
7
8
9
10
11
12
13
#wrong
const newObject = this.state.obj
newObject.id = 2;
this.setState({
obj: newObject
})

#wrong
const arr = this.state.arr;
arr.push(123);
this.setState({
list: arr
})

也就是說每次改動 state 的時候,都是產生一個新的物件,而不是直接對現有的 object 進行變更,這就是 Immutable data 的概念。

如何正確的更新 state?

由於 JavaScript 的資料結構特性,我們需要用 Object Spread OperatorObject.assign({}, defaults, options) 等方法在 React 中更新 state。

  • Objects, Array 深、淺拷貝

由於在 JavaScript 中 Objects, Array 的資料型別在賦值的時候,是用 pass by reference 的方法,與原始型別(primitive type)的直接傳值(pass by value)是不一樣的。

所以我們在複製 Objects, Array 的時候會利用函式處理,而不會直接用等號賦值。
但賦值的時候因為傳值的差異,有 淺拷貝(Shallow copies)和深拷貝(Deep copies)的差異。

淺拷貝:
Shallow copies duplicate as little as possible. A shallow copy of a collection is a copy of the collection structure, not the elements. With a shallow copy, two collections now share the individual elements.

深拷貝:
Deep copies duplicate everything. A deep copy of a collection is two collections with all of the elements in the original collection duplicated.

對 Shallow copy 來說,只是複製 collection structure,而不是 element。所以在指派第二層物件時會出現問題。

而 Deep Copy 是整個複製,包含 element。所以當我們在使用多層物件時,要用 deep copy。

  • 正確的更新 state

當我的 state 如下:

1
2
3
4
5
6
7
this.state = {
journeyList: [
{id: 1, name: "Japan, Tokyo"},
{id: 2, name: "Japan, Kyoto"},
{id: 3, name: "Europe 12 DAYS Travel"}
]
}

Update function would look like this:

1
2
3
4
5
6
7
8
9
10
11
12
updateJourney = (journey) => {
const index = this.state.journeyList.findIndex((item)=> item.id === journey[0].id);
if (index !== -1) {
this.setState({
journeyList: [
...this.state.journeyList.slice(0, index),
Object.assign({}, this.state.journeyList[index], journey[0]),
...this.state.journeyList.slice(index + 1)
]
});
}
};

若 journey 為:

1
[{id: 4, name: 'Singapore 7 days trip'}]

則我的 state 就會變成:

1
2
3
4
5
6
7
8
this.state = {
journeyList: [
{id: 1, name: "Japan, Tokyo"},
{id: 2, name: "Japan, Kyoto"},
{id: 3, name: "Europe 12 DAYS Travel"},
{id: 4, name: 'Singapore 7 days trip'}
]
}

Use Redux to update state

上述寫法當開發規模拓展的時候,每一次操作都需要深拷貝一次 state 並 assign 內容, code 維護上會較困難,因此可以考慮使用 Redux 的 state update pattern。

Redux uses a concept of state reducers which each work on a specific slice of the state of your application. That way you don’t have to manually dig through your entire state each time you want to affect a deep change.

相關文章可以參考:React State Management & Redux

Reference

StackOverflow

React 性能優化大挑戰

關於JAVASCRIPT中的SHALLOW COPY(淺拷貝)及DEEP COPY(深拷貝)

延伸閱讀

JS-Shallow Copy & Deep Copy
JS-pass by value or reference
React State Management & Redux

UI/UX 與互動設計

  • 靜態的網頁介面與具有互動性的網站不同之處在於,互動能夠有效地引導使用者進行期待的操作。

  • 互動設計與 UX 設計關係緊密,UX 關注網頁所規劃的操作流程是否足夠引導使用者表現出預期行為?使用者完成任務的效率是否良好?操作過程是否有挫折感?設計的操作提示,是否足夠引導使用者良好的方式吸收訊息?

  • 為網頁設計具有互動性 UI 的可以幫助打造更友善的使用者介面,營造良好的使用者體驗。

在為應用程式做介面設計和互動設計的時候,將為使用者考慮的細節包含進去思考,會發現每一個操作的動作,有它可以設計得更貼心的細微處。
觀察日常生活中經常使用的應用程式,也可以發現在細節處講究的互動設計,這些微互動設計可以稱為 微互動。

根據 微互動 一書的作者 Dan Saffer,他說:
「Microinteractions are contained product moments that revolve around a single use case — they have one main task. Every time you change a setting, sync your data or devices, set an alarm, pick a password, log in, set a status message, or favorite or “like” something, you are engaging with a microinteraction.」

  • 微互動是應用程式、設備等與使用者之間互動過程中,所有細節上的互動。
  • 微互動可以幫助使用者更流暢的完成主要功能操作,也能讓使用者感到愉悅,影響使用者會不會繼續使用產品。

UI 基本上由 輸入 -> 內容改變 -> 輸出 這個架構構成,而微互動則是
觸發 Trigger -> 規則 Ruls -> 回饋 Feedback -> 迴圈和模式 Loops & Modes。

「 A Trigger initiates a microinteraction. The Rules determine what happens, while Feedback lets people know what’s happening. Loops and Modes determine the meta-rules of the microinteraction. 」– Reference: MICROINTERACTIONS

為什麼需要回饋(feedbacks)

使用者操作時有預期心理,即使底層資料已改變,如果視覺上沒有給予足夠的回饋,會覺得操作行為尚未完成,適當的回饋可以傳遞更完整的訊息給使用者。

「 回饋:呼應微互動的規則。
使用數位裝置時,我們看到或聽到的一切都是一種抽象概念。只有少數使用者清楚軟體或裝置運作背後的原理。比如,我們不是真的把檔案放進資料夾內,電子郵件也不是真的送進收件夾裡,這些都是方便使用者理解互動如何進行的隱喻。任何可視、可聽、可感覺,可以幫助使用者了解系統運作規則,就是回饋。」 – Reference:[筆記] 微互動 microinteractions

如何產生適當的回饋(feedbacks)

「 回饋可以採用多種形式:視覺、聽覺、觸覺(震動),甚至是動畫(有許多維度可以變化:時間點、速度、方向等)。重點是讓回饋契合執行中的動作,儘可能以最適當的方式傳達明確的訊息。
回饋可以展現產品的個性。
回饋也可以具備自己的運作規則,比方出現的時機?如何改變顏色?當使用者旋轉平板,畫面如何旋轉?這些規則可以變成回饋本身的微互動,開放使用者手動設定。」 – Reference:[筆記] 微互動 microinteractions

CSS Animation & React Render 機制

  • 問題
    React 在 Render 時會比較 Virtual DOM 與真實 DOM 的差異,沒有改變的地方便不會重新 Render,但是 CSS Animation 的機制是每次節點生成時產生動畫效果,而如果 React Component 以 props 的方式傳遞資料,父元件的 state 更新使子元件的 props 改變,由 props 傳遞的內容也隨之改變,然而節點本身沒有消失再生成,所以並不會產生動畫效果。

  • 解決方式
    給予 Html element 特定的 Key -> 每次 props 更新時,連動 Key 更新,使 react render 機制判斷為新節點,因而重新生成觸動 animation的 element。

for example:

1
2
3
4
5
render() {
return (
<div>
<p key={this.props.name} className='title'>{this.props.name}</p>
</div>

Reference

[筆記] 微互動 microinteractions
MICROINTERACTIONS

在 hexo (theme/next)架構下使用 mermaid 繪製流程圖

How-to

安裝 package

1
npm install hexo-filter-mermaid-diagrams

在 hexo blog 的根目錄修改 _config.yml:

1
external_link: false

themes/next/_config.yml 檔案內有一處標記 mermaid 為 enable: false 需要改成 true:

1
2
3
4
5
6
# Mermaid tag
mermaid: ## mermaid url https://github.com/knsv/mermaid
enable: true
version: "7.1.2" # default v7.1.2
# Available themes: default | dark | forest | neutral
theme: forest

如果用 sublime text 編輯,可以用 command + F(mac) 直接搜尋 mermaid 比較快

同檔案內,往下拉有一區塊專門設置 CDN address,將 mermaid 的部分反註解:

1
2
3
4
5
6
7
8
9
10
11
# Script Vendors. Set a CDN address for the vendor you want to customize.
.
.
.
# Mermaid
# Example:
mermaid: //cdn.jsdelivr.net/npm/mermaid@8/dist/mermaid.min.js
# mermaid: //cdnjs.cloudflare.com/ajax/libs/mermaid/8.0.0/mermaid.min.js
.
.
.

最後在 themes/next/layout/_partials/footer.swig 檔案中最後面加上:

1
2
3
4
5
6
7
8
{%- if theme.mermaid.enable %}
<script src='https://unpkg.com/mermaid@{{ theme.mermaid.version }}/dist/mermaid.min.js'></script>
<script>
if (window.mermaid) {
mermaid.initialize({{ JSON.stringify(theme.mermaid.options) }});
}
</script>
{%- endif %}

以上就設置完成,可以使用 mermaid 語法了,流程圖示例如下:

graph TD;
  A(hexo) -- npm install hexo-filter-mermaid-diagrams --> B(set root/_config.yml)
  B --> C(set themes/next/_config.yml)
  C --> D(set themes/next/layout/_partials/footer.swig)

流程圖示例代碼如下:

1
2
3
4
<pre class="mermaid">graph TD;
A(hexo) -- npm install hexo-filter-mermaid-diagrams --> B(set root/_config.yml)
B --> C(set themes/next/_config.yml)
C --> D(set themes/next/layout/_partials/footer.swig)</pre>

若使用其他 theme 設置方法不太相同需要查看官方文件

Reference

mermaid
hexo-filter-mermaid-diagrams

How React manage State

React 使用單向數據流,父元件會把自身的 state 作為 props 傳遞給子元件,以此來共享資料。

根據官方文件描述:「 React Componenet 除了接收資料外( 透過 this.props 存取 ),也可以保存自身的 state (透過 this.state 存取)。當一個 component 的 state 改變的時候,產生的標記語法將會透過自動重新呼叫 render() 更新。」

React Componenet 變更 state 的唯一方法是用 this.setState(),而且 state 只能在自身的 Componenet 內變更,所以當應用程式的規模越來越大、元件越來越多,需要共通使用的 state 也會越來越多。

Lifting State Up

隨著應用程式增加更多功能,同一個 state 可能需要被多個同層級或非父子關係的元件使用,以反應相同的資料變化。此時這個 state 必須被提升到最靠近他們的共同上層元件(ancestor),再以 props 的形式傳遞給其他元件使用,這個過程稱為狀態提升(Lifting State Up)。

根據官方文件:「 在 React 應用程式中,對於資料的變化只能有一個唯一的「真相來源」。通常來說,state 會優先被加入到需要 render 的 component。接著,如果其他的 component 也需要的話,你可以提升 state 到共同最靠近的 ancestor。你應該依賴上至下的資料流,而不是嘗試在不同 component 之間同步 state。」

但不斷提升 state 的結果可能是許多 state 被以 props 形式不斷傳遞到子元件,而需要改變 state 時也必須將 function 以 props 傳遞到子元件,在子元件被觸發後,在上層元件內呼叫 this.setState()。如此一來,使用者操作應用程式改變資料等不同的 functions 被分散在各個元件,不僅不好維護,也容易因為 state 更新先後的問題引起錯誤。

Why Redux

Redux 由 Dan Abramov 開發:「 Redux provides a solid, stable and mature solution to managing state in your React application. Through a handful of small, useful patterns, Redux can transform your application from a total mess of confusing and scattered state, into a delightfully organized, easy to understand modern JavaScript powerhouse.」

Redux 受到幾個 Flux Pattern 重要特質的影響,過去的 MVC(Model-View-Controller) Pattern 是這樣的:

graph LR;
A(Action) --> B(Controller);
B --> C(Model);
C --> D(View);
C --> G(View);
B --> E(Model);
E --> G(View);
B --> F(Model);
F --> G(View);
E --> H(View);

而 Flux Pattern 是這樣的:

graph LR;
A(Action) --> B(Dispatcher);
B --> C(Store);
C --> D(View);

隨著 App 成長, state management 越來越複雜,所以我們會發現使用 Flux Pattern 這樣的邏輯是有效率的。

Redux Pattern

graph LR;
A(Action) -- Middleware --> B(Reducer);
B --> C(Store);
C --> D(Make changes);

Redux 的優點

  • Good for managing large state.
  • Useful for sharing data between containers.
  • Predictable state management using the 3 principles:
    1. Single source of truth - have one big object that describes the entire state
    2. State is read only - can only be modified by dispatching actions.
    3. Changes using pure function - receives an input and always return s an output that is predictable.

To dispatch state mutations you have to write a function that takes the previous state of the app and the action being dispatched, then return the next state of the app. This function is called the Reducer.

Redux help developer thinking in a way of “how can we build an app that is able to scale with thousands of user interactions” and “how can we make it that the information flows from one to another into a predictable view, a system that is predictable.”

Conclusion

整理為什麼使用 Redux + React 的方式管理 state 的理由:

  1. 更有效率的管理 state:
    Redux 採集中管理 state,再分配給需要該 state 的元件,因此不需要一直傳遞 props。

  2. 更有邏輯的管理 state:
    在 Redux 管理下,一律透過 action 描述更新動作,經過 Reducer 來變更 state。

  3. 更好維護:
    使用上結合兩者,將 UI 的 state 交給 React,將 Data 的 state 交給 Redux,當需要修改時,可以更快找到需要修改的地方。

但並不是所有 React 應用程式都需要 Redux,需要判斷 state tree 是否很複雜:

  1. 應用程式需要經常變化 UI
  2. 許多非父子關係的元件共用相同資料或以相同方式更新 state
  3. 同一個 state 受到多種不同方式更新

如果有這些狀況可以使用 Redux 來管理 state。

Reference

React Docs
Redux Docs