MicroProfile JWT Authentication・加點安全吧!

在微服務架構(Microservice Architecture)的概念中,雖每個人的理解與做法不同,但原則上就是將相關的服務合併拆解,重組為可獨立運作的服務。但由於服務拆解的緣故,應用服務和用戶管理服務(含認證與授權)會被拆開,因此,直覺的作法是當應用服務收到新請求時,若需要身份驗證就呼叫用戶管理服務。試想幾種可能性:

  1. 每次應用服務收到請求,就詢問用戶管理服務 => 網路 IO 的延遲與用戶管理服務的負載將成為問題。
  2. 應用服務收到不同用戶的請求,才詢問用戶管理服務 => 利用 Session 進行管理,若應用服務運行於多個節點,Session 同步又成了問題。
  3. 那把應用和用戶管理合併 => 這不是又回到了微服務架構前了嗎?整個砍掉重練 Orz...

唔...... 怎麼感覺在鬼打牆!難道就沒有好方法嗎?(啊嗚~

當然有!JWT(JSON Web Token)就是用來解決這類問題的主流方法!
JWT 機制提供了一定程度的安全性,而 Token 中除了包含規格中定義的資訊外,開發者還可自訂所需的資訊,所以能減少對用戶管理服務的依賴。網路上有很多大神解釋什麼是 JWT,還請大家能自己花點時間查詢研究。

因此,以微服務架構為目標的 MicroProfile 提出了 MicroProfile JWT Authentication(後簡稱 MPJWTAuthentication),用以協助開發者快速採用 JWT 來提升 API 的安全性。
特別要提醒大家,MPJWTAuthentication 目的是幫助開發者處理含有 JWT 的請求,並不包含任何安全產生 JWT 的機制。


在開始之前

就像前面提到的,使用 JWT 之前,得先有個能提供 JWT 的服務。那麼,服務在哪呢?
可以選擇自己開發,當然也可以介接別人已經做好的服務,但,為了讓解釋起來更方便,本文採用了 真・大神 AdamBien 的開源工具 jwtenizr 來模擬取得 JWT 後的操作,使用法請參考該專案內的說明。以下重點將放在 MPJWTAuthentication 怎麼使用。

在使用工具執行設定完之後,請先確定 MPConfig 的設定檔有放在 src/main/resources/META-INF 之中,要使用 MPJWTAuthentication 應包含以下兩個屬性:

  1. mp.jwt.verify.publickey
  2. mp.jwt.verify.issuer

屬性的內容是由工具生成的,也可以選擇複製貼上。

權限保護很簡單

保護權限很簡單,一樣是使用 @annotation 的形式,首先要啟動 JWT 驗證機制。

@ApplicationPath("api")
@LoginConfig(authMethod = "MP-JWT")
public class RestConfig extends Application {
}

只要加上了 @LoginConfig(authMethod = "MP-JWT"), JWT 驗證機制就設定完成了。

接下來在 RESTful API 的方法上加上些保護:

@Path("hello")
@RequestScoped
public class HelloJWTAuthentication {

    @Inject
    @Claim("groups")
    private Set<String> groups;

    @Inject
    @Claim("upn")
    private String username;

    @Inject
    @Claim("level")
    private Long level;

    @GET
    public String sayHello1() {
        return "Hello! " + username + "[" + level + "] of " + groups;
    }

    @Path("auth")
    @GET
    @RolesAllowed("dev")
    public String sayHello2() {
        return "Hello! " + username + "[" + level + "] of " + groups;
    }
}

保護 API 的方法是透過 @RolesAllowed 這個 @annotation,框架會自動解析請求 JWT 內容中的 groups 是否包含所定義的角色,也就是若非 dev 的角色呼叫 sayHello2,將取回 401 Unauthorized 的結果。

MPJWTAuthentication 最方便的地方,就是直接可以注入 JWT 之中的資訊,如:@Claim("level") 就是取得 JWT 中的 level 來使用。框架另外有提供數種資料格式可供注入,還有囊括全部 JWT 資訊的 JsonWebToken,詳情請參考規格書。

我所設定的 token 內容如下:

// jwt-token.json
{
    "iss": "QStudio",
    "jti": "42",
    "sub": "Arren Ping",
    "upn": "Arren Ping",
    "groups": ["everyone", "dev"],
    "level": 1
}

請記得,除了 token 有使用時間限制外,在修改工具產生的 jwt-token.json 後,一定要重新執行一次工具,重新產生 JWT,再依照工具的建議執行 curl 來測試。

實際測試就留給大家,幫 RESTful API 加上 JWT 保護就這麼簡單。
特別提醒 sayHello1 因為不在保護範圍內,無論是否有傳送 JWT 都不會驅動安全機制,所以無法拿到其內容。


文末,做個簡單的結語,

MPJWTAuthentication 提供了直接使用 JWT 的機制,讓開發者在微服務架構下,更容易開發具有安全性的 API。若系統架構中已採用 JWT,請務必考慮使用!!!

by Arren Ping