2024/07/01

WebAuthn

WebAuthn 是 Web 與 Authentication 結合的縮寫,也就是 Web Authentication,用在網頁身份認證。這個規格是由 W3C 與 FIDO (Fast IDentity Online) 提出,該正式API 規格,透過瀏覽器的支援,可透過 Public Key Cryptography 機制註冊並認證使用者,取代傳統密碼形式的認證方式。

最傳統的身份認證機制是用密碼,但因為密碼太多了,又因為資安政策需要常常修改密碼,正常人應該已經沒辦法記得很清楚,哪個網站是用哪一個密碼。

WebAuthn 是  FIDO2 framework 的部分,透過 server, browser, authenticator 的搭配,提供一種 passwordless 無密碼的認證方式。

Demo

webauthn.io 是一個單純的網頁 demo。demo 時分兩個步驟,Register 與 Authenticate。

Register 就是註冊帳號,使用者向 browser 要求要註冊一個新帳號,browser 透過 authenticator 的搭配輔助,產生一組 public key,然後完成註冊的步驟。 authenticator 可能是本機的指紋 scanner,也可以是 USB key,或是目前最容易取得的每一個人的手機,並搭配手機的 FaceID 或是指紋 scanner 輔助做身份認證。

註冊完成後,會取得註冊的 id,然後在透過認證 API,進行使用者身份認證。

Javascript demo

註冊

// should generate from server
const challenge = new Uint8Array(32);
window.crypto.getRandomValues(challenge);

const userID = '5d157c55-ea8b-45ca-870a-da877a9c9916';

const utf8EncodeText = new TextEncoder();
const id = utf8EncodeText.encode(userID);

const publicKeyCredentialCreationOptions = {
    challenge,
    rp: {
        name: "Maxkit RP",
        // 這邊只是 demo,沒有放到某個 https 網站測試,如果是網站,就要改成該網站的 domain name
        //id: "maxkit.com.tw",
    },
    user: {
        id,
        name: "test",
        displayName: "Test",
    },
    pubKeyCredParams: [{alg: -7, type: "public-key"}],
    authenticatorSelection: {
        authenticatorAttachment: "cross-platform",
    },
    timeout: 60000,
    attestation: "direct"
};

const credential = await navigator.credentials.create({
    publicKey: publicKeyCredentialCreationOptions
});

把上面這一部分的 js code 直接貼到 chrome console 執行。主要就是透過 navigator.credentials.create 註冊產生 public key

challenge 欄位是由 server 產生,最終會由 authenticator 以私鑰簽章後,在 credential response 裡面傳回來,讓 server 能夠檢查確認 authenticator

rp.id 應該要填網域 domain name,這邊只是本機測試,不填寫

authenticatorAttachment: "cross-platform" 的意思是,可搭配使用外部認證工具,例如在 NB 瀏覽認證網頁時,可使用手機作為認證工具進行認證。

在 NB 提示認證時,會看到這個畫面

這時候,就用自己的手機相機,拍 QR code,手機上會出現以下畫面

確認後,會在手機的密碼部分,儲存一個專屬的密碼。iOS 手機選單在 "設定 -> 密碼"

在 console 列印 credential 的內容

// print credential
console.log(credential)
PublicKeyCredential {rawId: ArrayBuffer(20), response: AuthenticatorAttestationResponse, authenticatorAttachment: 'cross-platform', id: 'ku22CGGrZdYlkr9cCXL4IWtyYLc', type: 'public-key'}

正常狀況應該把 credential 傳送到 server,檢查並 decode 得到使用者的 id, public key。這邊僅是測試 demo,不做處理。

response.attestationObject 裡面有 public key 跟其他 metadata,是用 CBOR Concise Binary Object Representation 編碼後的 binary data。

認證

以下是認證的 demo code,從剛剛的 credential.id 欄位取得 id,通常應該是儲存在 server,從 server 傳到網頁得到這個資料

const credentialIdStr = credential.id;
const credentialId = Uint8Array.from(window.atob(credentialIdStr), c=>c.charCodeAt(0));

const challenge2 = new Uint8Array(32);
window.crypto.getRandomValues(challenge2);

const publicKeyCredentialRequestOptions = {
    challenge: challenge2,
    //rpId: "maxkit.com.tw",
    allowCredentials: [{
        id: credentialId, // from registration
        type: 'public-key',
        transports: ['usb', 'ble', 'nfc', 'hybrid'],
    }],
    timeout: 60000,
}

const assertion = await navigator.credentials.get({
    publicKey: publicKeyCredentialRequestOptions
});

transports 是可以認證的方式

網頁同樣會出現 QR code,等待 authenticator,這邊我們用剛剛註冊的手機掃描

手機掃描時,會出現一個提示

點"使用通行密鑰登入"

繼續透過 FaceID 認證,認證完成,同樣可取得 response

console.log(assertion)

CTAP

Client-to-Authenticator Protocol,這是在瀏覽器打開註冊/認證網頁後,authnticator 跟瀏覽器之間傳輸的協定,也就是 WebAuthn API 跟外部硬體認證機制溝通的規格

References

WebAuthn guide

什么是WebAuthn:在Web上使用Touch ID和Windows Hello登录_webauthn 网站登录-CSDN博客

你可能不知道的WebAuthN(FIDO) | 又LAG隨性筆記

初探 WebAuthn UAF 的無密碼 Passwordless 登入技術 – 要改的地方太多了,那就改天吧

一起來了解 Web Authentication

CredentialsContainer - Web APIs | MDN

2024/06/17

gradio

gradio 是一組 python library,可快速建立網頁介面的 API,目的是提供給 Machine Learning Model demo 的使用者操作介面。大部分的 AI model 是以 python 開發,且使用程式或文字介面操作,gradio 可快速將使用介面轉換為網頁應用程式,不需要撰寫網頁 css, javascript 等程式碼。

Quickstart 是 gradio 的快速入門說明

安裝 gradio

pip3 install gradio

在執行時,遇到這樣的錯誤訊息

ImportError: urllib3 v2 only supports OpenSSL 1.1.1+, currently the 'ssl' module is compiled with 'OpenSSL 1.0.2k-fips  26 Jan 2017'. See: https://github.com/urllib3/urllib3/issues/2168

根據 python - ImportError: urllib3 v2.0 only supports OpenSSL 1.1.1+, currently the 'ssl' module is compiled with LibreSSL 2.8.3 - Stack Overflow 的說明,要 downgrade urllib3

pip3 uninstall urllib3
pip3 install 'urllib3<2.0'

test1.py,launch 時加上 server_name="0.0.0.0" 參數,可讓機器所有 network interfaces 在 TCP Port 7860 提供網頁服務

import gradio as gr

def greet(name, intensity):
    return "Hello " * intensity + name + "!"

demo = gr.Interface(
    fn=greet,
    inputs=["text", "slider"],
    outputs=["text"],
)

demo.launch(server_name="0.0.0.0")

在瀏覽器連接 http://localhost:7860

如果在 launch 加上參數 share=True

啟動時,會出現這樣的訊息

$ python3 test1.py
Running on local URL:  http://127.0.0.1:7860
Running on public URL: https://424222ce32aa52ca72.gradio.live

This share link expires in 72 hours. For free permanent hosting and GPU upgrades, run `gradio deploy` from Terminal to deploy to Spaces (https://huggingface.co/spaces)

這表示可透過一個 public redirect 的網址,連接到這個 service

References

gradio

Creating A Chatbot Fast

2024/06/03

log4j2

java 開發環境,JDK 預設有提供 logging 機制,但 logging 機制的發展,也是一段精彩的歷史。就以往使用的經驗,從一開始的 log4j,後來換成了 logback + slf4j,到現在這個階段,似乎又要換成 log4j2,因為 log4j2 的效能評估,結果已經是又超過了 logback。

logging framwork 的發展可以參考這一篇文章: Java Logging 的歷史與戰爭

但這種情況也說明了,開放且多元的 java 開發環境,一個必要且熱門的 framework,會吸引大家注意並不斷提出更好的框架,但使用者就得跟著時代不斷的進步。

使用 log4j 用 maven 引用 library,在 pom.xml 的 dependencies 裡面加上 log4j2 及 slf4j。

slf4j 是 logging 的 wrapper,可用一致 API 介面進行程式開發,在不修改程式的狀況下,可替換內部的實作為 logback 或 log4j 或 jdk logging。

    <dependencies>
        <!-- log4j2 + slf4j -->
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-api</artifactId>
            <version>2.22.1</version>
        </dependency>
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-core</artifactId>
            <version>2.22.1</version>
        </dependency>
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-slf4j-impl</artifactId>
            <version>2.22.1</version>
        </dependency>
    </dependencies>

在 classpath 裡面加上設定檔 log4j2.xml

以下是調整後的設定

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="warn">
    <Appenders>
        <Console name="Console" target="SYSTEM_OUT">
            <PatternLayout>
                <Pattern>%d{HH:mm:ss.SSS} [%thread] %-5p %c %L - %m%n</Pattern>
            </PatternLayout>
        </Console>

        <!--
        <File name="FILE" fileName="testweb.log">
            <PatternLayout>
                <Pattern>%d [%thread] %-5p %c %L - %m%n</Pattern>
            </PatternLayout>
        </File>
        -->
        <RollingFile name="RollingFile" fileName="logs/testweb.log" filePattern="logs/testweb-%d{yyyy-MM-dd}-%i.log.gz">
            <PatternLayout>
                <Pattern>%d [%thread] %-5p %c %L - %m%n</Pattern>
            </PatternLayout>
            <Policies>
                <OnStartupTriggeringPolicy />
                <TimeBasedTriggeringPolicy />
                <SizeBasedTriggeringPolicy size="250 MB"/>
            </Policies>
            <DefaultRolloverStrategy>
                <Delete basePath="${baseDir}" maxDepth="2">
                    <IfFileName glob="*/test-*.log.gz" />
                    <IfLastModified age="30d" />
                </Delete>
            </DefaultRolloverStrategy>
        </RollingFile>
    </Appenders>
    <Loggers>
        <!-- hibernate -->
        <logger name="org.hibernate" level="INFO" />
        <Logger name="org.hibernate.SQL" level="DEBUG" />
        <logger name="org.hibernate.cfg" level="ERROR" />

        <Root level="info">
            <AppenderRef ref="Console"/>
            <AppenderRef ref="RollingFile"/>
        </Root>
    </Loggers>
</Configuration>

RollingFile 的部分可以參考官方說明Log4j 2 Appenders

Policies 的部分,決定什麼時候要 trigger rolling 的機制。OnStartupTriggeringPolicy 代表程式啟動要做一次。TimeBasedTriggeringPolicy 則是跟著 Pattern 裡面日期的最小精度的部分 trigger,目前日期是設定為日,所以這個範例,每天會處理一次。SizeBasedTriggeringPolicy 則是會判斷 log file 的檔案大小 trigger,pattern 部分會因為 %i 這個設定,每次會將檔案改為 -1, -2 這樣的檔名。

DefaultRolloverStrategy 可將超過 30 天,符合glob 設定的 pattern 的檔案刪除。

使用 logging 是透過 sl4j 介面

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class HelloWorld {
    Logger log = LoggerFactory.getLogger(this.getClass().getName());
    public void sayHello(){
        log.debug("This will be printed on debug");
        log.info("This will be printed on info");
        log.warn("This will be printed on warn");
        log.error("This will be printed on error");

        log.info("Appending string: {}.", "Hello, World");
    }
}

log4j2 對效能最大的改進點在非同步的 file appender Log4j 2 Appenders

調整 log4j2.xml 設定,基於上面的 RollingFile 增加 Appenders。

bufferSize 預設為 1024,代表可暫存 1024 則 log,必須要設定為 2 的指數。

        <Async name="AsyncRollingFile" bufferSize="262144">
            <AppenderRef ref="RollingFile"/>
        </Async>

然後修改下面的 AppenderRef,就可以換成 AsyncRollingFile

        <Root level="info">
            <AppenderRef ref="Console"/>
            <AppenderRef ref="AsyncRollingFile"/>
        </Root>

References

Log4j2中RollingFile的文件滚动更新机制 - Ye_yang - 博客园

Java Logging Frameworks: log4j vs logback vs log4j2

Log4j; Performance