基于OIDC實(shí)現(xiàn)istio來源身份驗(yàn)證

本文介紹如何生成可以經(jīng)過istio來源身份驗(yàn)證的jwt token。istio的來源身份驗(yàn)證是通過OpenID connect規(guī)范實(shí)現(xiàn)的擅笔,這里只需要遵循OIDC的小部分規(guī)范便可以實(shí)現(xiàn)可以通過驗(yàn)證的token。

首先來看一下istio官方文檔對來源身份驗(yàn)證的說明:


https://istio.io/zh/docs/concepts/security/#%e6%9d%a5%e6%ba%90%e8%ba%ab%e4%bb%bd%e8%ae%a4%e8%af%81

ISTIO的來源身份驗(yàn)證通過ENVOY完成,看一下envoy官方文檔對JWT的說明:


https://www.envoyproxy.io/docs/envoy/latest/configuration/http_filters/jwt_authn_filter

可以知道彤路,istio會對token的signature蜻拨、audiencesissuer三個(gè)屬性進(jìn)行校驗(yàn)躯保,也會對有效期進(jìn)行檢查,而且只支持ES256和RS256兩種算法澎语,因此我們需要保證我們的token生成中這三項(xiàng)屬性的規(guī)范性途事。

一验懊、前置知識

JWT

https://jwt.io/

JSON Web Token(縮寫 JWT)是目前最流行的跨域認(rèn)證解決方案,簡單來說尸变,一個(gè)JWT的TOKEN由三部分組成:

  • Headers: 頭部信息鲁森,經(jīng)過base64編碼的JSON字符串
  • Payload: 負(fù)載信息,經(jīng)過base64編碼的JSON字符串
  • Sinature: 簽名

最終的結(jié)構(gòu)如下:

  headers.payload.sinature

如下:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

OpenID connect

https://openid.net/connect/

OpenID Connect 是一套基于 OAuth 2.0 協(xié)議的輕量認(rèn)證級規(guī)范振惰,提供通過 API 進(jìn)行身份交互的框架歌溉。較 OAuth 而言, OpenID Connect 方式除了認(rèn)證請求之外骑晶,還標(biāo)明請求的用戶身份痛垛。

簡單來說,我們需要提供一個(gè)符合OIDC規(guī)范的認(rèn)證服務(wù)端桶蛔,它需要提供token生成能力和token校驗(yàn)所使用的公鑰匙头。認(rèn)證服務(wù)端保管好一組簽名所用的私鑰,生成token時(shí)選擇一個(gè)私鑰對token進(jìn)行簽名并在token中注入相關(guān)信息(比如對應(yīng)的公鑰ID仔雷、算法蹂析、issuer、audiences碟婆、有效期等)电抚。然后認(rèn)證服務(wù)端需要提供一個(gè)接口來開放所有的公鑰,這樣ISTIO才能拿到公鑰對token進(jìn)行校驗(yàn)竖共。

上述都是OIDC規(guī)范的一部分蝙叛,這里并不嚴(yán)格實(shí)現(xiàn)OIDC規(guī)范,僅僅為了實(shí)現(xiàn)istio的來源身份驗(yàn)證公给。

二借帘、JAVA實(shí)現(xiàn)

依賴

maven

<dependency>
    <groupId>org.bitbucket.b_c</groupId>
    <artifactId>jose4j</artifactId>
    <version>0.6.5</version>
</dependency>
<dependency>
    <groupId>com.auth0</groupId>
    <artifactId>java-jwt</artifactId>
    <version>3.8.1</version>
</dependency>

base64格式化工具

用于對公私鑰二進(jìn)制內(nèi)容的加解密,可以自行選擇其他方式淌铐。

private static String decodeBase64(String src) {
    return new String(Base64.getDecoder().decode(src));
}

private static byte[] decodeBase64ToBytes(String src) {
    return Base64.getDecoder().decode(src);
}

private static String encodeBase64(byte[] bytes) {
    return Base64.getEncoder().encodeToString(bytes);
}

生成簽名密鑰對

這里使用RS256算法肺然,注意管理好keyId,這里我把公私鑰都使用base64格式化腿准,方便后續(xù)操作际起。

jwk.toJson()返回的內(nèi)容是一個(gè)JSON,我們需要記錄一下公鑰的JSON用于開放給istio释涛。

public void generateKey() throws Exception {
    String keyId = "def_test";
    RsaJsonWebKey jwk = RsaJwkGenerator.generateJwk(2048);
    jwk.setKeyId(keyId);
    jwk.setAlgorithm(AlgorithmIdentifiers.RSA_USING_SHA256);
    System.out.println(encodeBase64(jwk.getRsaPublicKey().getEncoded()));
    System.out.println(encodeBase64(jwk.getRsaPrivateKey().getEncoded()));
    String publicKey = jwk.toJson(RsaJsonWebKey.OutputControlLevel.PUBLIC_ONLY);
    String privateKey = jwk.toJson(RsaJsonWebKey.OutputControlLevel.INCLUDE_PRIVATE);
    System.out.println("publicKey: " + publicKey);
    System.out.println("privateKey: " + privateKey);
}

公鑰JSON示例:

{
    "kty": "RSA",
    "kid": "def_key_id_oidc",
    "alg": "RS256",
    "n": "itEiXnQl10vhzYKMc5YXkzOovq2Z_jqSkVWbzqKKJx9Cfxg2VHk8h7eA8PD5xVXCydV_nCu1thDidnh_iWyPQAOHmUrs26txLfVpoyYV2tzYd988eCugnEZAGXx4tXljvpeLOdDsAbtrm-HIyeJ5UE7egx7vmI1EJacqlM1JAZu4jEx99lW7P4ePfqcuytYnAWV1qL3FYKBtDs3Y3Whl4_gFsLErcqhRTIs8mrvhoOCrBYDyJ8nX-59oliaOGIKmPbyYPfQ5beJ-zwjAcn5Z6plZqJ3GtbpNyD6s5GO3WcwqttuCIGpwFdMyfuJl_QYH8sFlufsdyeSKHs_ncbcmOw",
    "e": "AQAB"
}

這里的公鑰是經(jīng)過OIDC規(guī)范特殊格式化的加叁,不是base64倦沧。

生成token

注意issuer唇撬、keyId、clientId展融、subject等值的統(tǒng)一窖认,要與下一步的開放接口一致,注意有效期可以自己定義,也可以向token中注入自定義的信息扑浸。

public static String createToken(String issuer,
                                 String keyId,
                                 String clientId,
                                 String subject,
                                 String secret,
                                 Map<String, Object> headers,
                                 Map<String, String> payload) {
    JWTCreator.Builder builder = JWT.create();
    builder.withIssuer(issuer);
    builder.withKeyId(keyId);
    builder.withSubject(subject);
    builder.withAudience(clientId);

    Calendar calendar = Calendar.getInstance();
    builder.withIssuedAt(calendar.getTime());
    //有效期 自行配置
    calendar.add(Calendar.HOUR, 24);
    builder.withExpiresAt(calendar.getTime());

    if (null != headers && headers.size() > 0) {
        builder.withHeader(headers);
    }
    if (null != payload && payload.size() > 0) {
        for (Map.Entry<String, String> entry : payload.entrySet()) {
            builder.withClaim(entry.getKey(), entry.getValue());
        }
    }
    Algorithm algorithm = Algorithm.HMAC256(decodeBase64ToBytes(secret));
    return builder.sign(algorithm);
}

開放接口 jwksUri

使用rest開放公鑰(OIDC規(guī)范)

最終該接口的返回值如下所示烧给,可以自行設(shè)計(jì)接口,該接口的訪問地址用于配置在istio中喝噪。

{
    "keys": [{
        "kty": "RSA",
        "kid": "def_key_id_oidc",
        "alg": "RS256",
        "n": "itEiXnQl10vhzYKMc5YXkzOovq2Z_jqSkVWbzqKKJx9Cfxg2VHk8h7eA8PD5xVXCydV_nCu1thDidnh_iWyPQAOHmUrs26txLfVpoyYV2tzYd988eCugnEZAGXx4tXljvpeLOdDsAbtrm-HIyeJ5UE7egx7vmI1EJacqlM1JAZu4jEx99lW7P4ePfqcuytYnAWV1qL3FYKBtDs3Y3Whl4_gFsLErcqhRTIs8mrvhoOCrBYDyJ8nX-59oliaOGIKmPbyYPfQ5beJ-zwjAcn5Z6plZqJ3GtbpNyD6s5GO3WcwqttuCIGpwFdMyfuJl_QYH8sFlufsdyeSKHs_ncbcmOw",
        "e": "AQAB"
    }]
}

這里提供一個(gè)最簡單的實(shí)現(xiàn)础嫡,推薦自行根據(jù)需求開發(fā)。

maven依賴

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-webflux</artifactId>
    <version>2.1.7.RELEASE</version>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.59</version>
</dependency>

實(shí)體類

public class VerificationKeys {
    public VerificationKeys() {
    }
    public VerificationKeys(List<VerificationKey> keys) {  this.keys = keys;  }
    private List<VerificationKey> keys;
    public List<VerificationKey> getKeys() {  return keys;  }
    public void setKeys(List<VerificationKey> keys) {  this.keys = keys;  }
}

public class VerificationKey {
    /**
     * 公鑰ID
     */
    private String kid;
    /**
     * 公鑰算法類型
     */
    private String kty;
    /**
     * 公鑰算法
     */
    private String alg;
    /**
     * 公鑰用途:  sig 簽名;enc 加密
     */
    private String use;
    /**
     * 公鑰
     */
    private String n;
    /**
     * AQAB
     */
    private String e;
    public String getKid() {  return kid;  }
    public void setKid(String kid) {  this.kid = kid;  }
    public String getAlg() {  return alg;  }
    public void setAlg(String alg) {  this.alg = alg;  }
    public String getKty() {  return kty;  }
    public void setKty(String kty) {  this.kty = kty;  }
    public String getUse() {  return use;  }
    public void setUse(String use) {  this.use = use;  }
    public String getN() {  return n;  }
    public void setN(String n) {  this.n = n;  }
    public String getE() {  return e;  }
    public void setE(String e) {  this.e = e;  }
}

接口

@RestController
@RequestMapping("/auth")
public class AuthController {
    @RequestMapping(value = "/token_keys", method = {RequestMethod.GET, RequestMethod.POST})
    public VerificationKeys tokenKeys() {
                    VerificationKeys keys = new VerificationKeys();
                    String pubKeyJson = "生成公私鑰對時(shí)的公鑰JSON";
                    List<VerificationKey> keyList = new ArrayList<VerificationKey>();
                    VerificationKey key = JSON.parseObject(pubKeyJson, VerificationKey.class);
                    keyList.add(key);
                    keys.setKeys(keyList);
        return keys;
    }
}

openid-configuration接口(可選酝惧,非強(qiáng)制)

嚴(yán)格的OIDC規(guī)范榴鼎,還需要開放一個(gè)接口用于聲明所有的接口和相關(guān)的約束,包括jwksUri晚唇。該接口的訪問路徑是issuer/.well-known/openid-configuration巫财,這里不再詳細(xì)介紹,有興趣的可以自行研究哩陕,或者等待后續(xù)的文章平项。

可以參考一下谷歌的OIDC,比如谷歌的issuer是https://accounts.google.com悍及,則對應(yīng)的接口是https://accounts.google.com/.well-known/openid-configuration,當(dāng)然里面有許多屬性可能是谷歌特有的闽瓢,詳細(xì)可以參考OIDC的官方文檔。

{
    issuer: "https://accounts.google.com",
    authorization_endpoint: "https://accounts.google.com/o/oauth2/v2/auth",
    token_endpoint: "https://oauth2.googleapis.com/token",
    userinfo_endpoint: "https://openidconnect.googleapis.com/v1/userinfo",
    revocation_endpoint: "https://oauth2.googleapis.com/revoke",
    jwks_uri: "https://www.googleapis.com/oauth2/v3/certs",
    response_types_supported: [
        "code",
        "token",
        "id_token",
        "code token",
        "code id_token",
        "token id_token",
        "code token id_token",
        "none"
    ],
    subject_types_supported: [
        "public"
    ],
    id_token_signing_alg_values_supported: [
        "RS256"
    ],
    scopes_supported: [
        "openid",
        "email",
        "profile"
    ],
    token_endpoint_auth_methods_supported: [
        "client_secret_post",
        "client_secret_basic"
    ],
    claims_supported: [
        "aud",
        "email",
        "email_verified",
        "exp",
        "family_name",
        "given_name",
        "iat",
        "iss",
        "locale",
        "name",
        "picture",
        "sub"
    ],
    code_challenge_methods_supported: [
        "plain",
        "S256"
    ]
}

三心赶、istio配置

在istio中為想要執(zhí)行來源身份驗(yàn)證服務(wù)配置一個(gè)policy鸳粉,issuer與生成token時(shí)一致,jwksUri就是用于開放公鑰的接口地址园担。如果不提供jwksUri届谈,那么就會使用issuer/.well-known/openid-configuration來訪問OIDC的聲明接口,來找到j(luò)wks_uri并拿到公鑰弯汰。

---
apiVersion: "authentication.istio.io/v1alpha1"
kind: Policy
metadata:
name: policy-test
namespace: default
spec:
targets:
- name: service-test
origins:
- jwt:
  issuer: "http://127.0.0.1:8080/"
  jwksUri: "http://127.0.0.1:8080/auth/token_keys"
principalBinding: USE_ORIGIN

還支持排除某些路徑艰山,或僅作用于某些路徑(摘自官方文檔)

---
apiVersion: authentication.istio.io/v1alpha1
kind: Policy
metadata:
  name: productpage-mTLS-with-JWT
  namespace: frod
spec:
  targets:
  - name: productpage
    ports:
    - number: 9000
  peers:
  - mtls:
  origins:
  - jwt:
      issuer: "https://securetoken.google.com"
      audiences:
      - "productpage"
      jwksUri: "https://www.googleapis.com/oauth2/v1/certs"
      jwt_headers:
      - "x-goog-iap-jwt-assertion"
      trigger_rules:
      - excluded_paths:
        - exact: /health_check
  principalBinding: USE_ORIGIN

---
issuer: https://example.com
jwks_uri: https://example.com/.well-known/jwks.json
trigger_rules:
- excluded_paths:
  - exact: /health_check
  - prefix: /status/

---
issuer: https://example.com
jwks_uri: https://example.com/.well-known/jwks.json
trigger_rules:
- included_paths:
  - prefix: /admin

---
issuer: https://example.com
jwks_uri: https://example.com/.well-known/jwks.json
trigger_rules:
- excluded_paths:
  - exact: /status/version
  included_paths:
  - prefix: /status/

詳情參考官方英文文檔 https://istio.io/docs/reference/config/istio.authentication.v1alpha1/

END

個(gè)人能力有限,如有錯(cuò)誤咏闪,歡迎指正曙搬!

有興趣的可以研究一下一些開源的實(shí)現(xiàn)OIDC規(guī)范的框架的源碼,可以有更加深入的了解鸽嫂。

  • openid-connect-spring-java
  • keycloak
  • cas
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末纵装,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子据某,更是在濱河造成了極大的恐慌橡娄,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,884評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件癣籽,死亡現(xiàn)場離奇詭異挽唉,居然都是意外死亡滤祖,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,347評論 3 385
  • 文/潘曉璐 我一進(jìn)店門瓶籽,熙熙樓的掌柜王于貴愁眉苦臉地迎上來匠童,“玉大人,你說我怎么就攤上這事塑顺√狼螅” “怎么了?”我有些...
    開封第一講書人閱讀 157,435評論 0 348
  • 文/不壞的土叔 我叫張陵严拒,是天一觀的道長首昔。 經(jīng)常有香客問我,道長糙俗,這世上最難降的妖魔是什么勒奇? 我笑而不...
    開封第一講書人閱讀 56,509評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮巧骚,結(jié)果婚禮上赊颠,老公的妹妹穿的比我還像新娘。我一直安慰自己劈彪,他們只是感情好竣蹦,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,611評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著沧奴,像睡著了一般痘括。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上滔吠,一...
    開封第一講書人閱讀 49,837評論 1 290
  • 那天纲菌,我揣著相機(jī)與錄音,去河邊找鬼疮绷。 笑死翰舌,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的冬骚。 我是一名探鬼主播椅贱,決...
    沈念sama閱讀 38,987評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼只冻!你這毒婦竟也來了庇麦?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,730評論 0 267
  • 序言:老撾萬榮一對情侶失蹤喜德,失蹤者是張志新(化名)和其女友劉穎山橄,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體住诸,經(jīng)...
    沈念sama閱讀 44,194評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡驾胆,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,525評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了贱呐。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片丧诺。...
    茶點(diǎn)故事閱讀 38,664評論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖奄薇,靈堂內(nèi)的尸體忽然破棺而出驳阎,到底是詐尸還是另有隱情,我是刑警寧澤馁蒂,帶...
    沈念sama閱讀 34,334評論 4 330
  • 正文 年R本政府宣布呵晚,位于F島的核電站,受9級特大地震影響沫屡,放射性物質(zhì)發(fā)生泄漏饵隙。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,944評論 3 313
  • 文/蒙蒙 一沮脖、第九天 我趴在偏房一處隱蔽的房頂上張望金矛。 院中可真熱鬧,春花似錦勺届、人聲如沸驶俊。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,764評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽饼酿。三九已至,卻和暖如春胚膊,著一層夾襖步出監(jiān)牢的瞬間故俐,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,997評論 1 266
  • 我被黑心中介騙來泰國打工紊婉, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留购披,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,389評論 2 360
  • 正文 我出身青樓肩榕,卻偏偏與公主長得像刚陡,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子株汉,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,554評論 2 349

推薦閱讀更多精彩內(nèi)容