Spring Security Oauth2(二)
認識JWT令牌
在介紹JWT之前先看一下傳統校驗令牌的方法,如下圖:
問題:
傳統授權方法的問題是用戶每次訪問資源服務,資源服務都須攜帶令牌去認證服務中心請求驗證令牌合法性,並根據令牌獲取用戶相關信息,性能低下。
這個時候的令牌只是作爲唯一標識,並沒有其他用途。
解決:
使用JWT的思路,用戶認證通過會得到一個JWT令牌,JWT令牌中已經包含了用戶相關信息,客戶端只需要攜帶JWT訪問服務資源,資源服務根據事先約定的算法自行完成令牌效驗,這個就不用每次請求認證服務完成授權。
什麼是jwt?
JSON Web Token(JWT)是一個開放的行業標(RFC 7519),它定義了一種簡介的、自包含的協議格式,用於在通信雙方傳遞json對象,傳遞的信息經過數字簽名可以被驗證和信任。JWT可以使用HMAC算法或使用RSA的公鑰/私鑰對來簽名,防止被篡改。
官網:https://jwt.io/
標準:https://tools.ietf.org/html/rfc7519
jwt令牌優點:
- jwt令牌採用json格式,便於解析
- 可以在令牌中自定義豐富的內容,易於擴展
- 通過非對稱加密算法和數字簽名技術,jwt防止篡改,安全性高
- 資源服務使用jwt可不用依賴認證服務即可完成授權。
缺點:
- jwt令牌較長,佔存儲空間
JWT令牌結構
JWT令牌由三部分組成,每部分中間使用點(.)分隔,比如:xxxxx.yyyyy.zzzzz
- Header
頭部包括令牌的類型(即JWT)及使用的哈希算法(如HMAC SHA256或RSA)一個例子如下:
下邊是Header部分的內容
{
"alg": "HS256",
"typ": "JWT"
}
將上邊的內容使用Base64Url編碼,得到一個字符串就是JWT令牌的第一部分。
- Payload
第二部分是負載,內容也是一個json對象,它是存放有效信息的地方,它可以存放jwt提供的現成字段,比
如:iss(簽發者),exp(過期時間戳), sub(面向的用戶)等,也可自定義字段。
此部分不建議存放敏感信息,因爲此部分可以解碼還原原始內容。
最後將第二部分負載使用Base64Url編碼,得到一個字符串就是JWT令牌的第二部分。
案例:
{
"sub": "1234567890",
"name": "456",
"admin": true
}
- Signature
第三部分是簽名,此部分用於防止jwt內容被篡改
這個部分使用base64url將前兩部分進行編碼,編碼後使用點(.)連接組成字符串,最後使用header中聲明
簽名算法進行簽名。
一個例子:
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)
base64UrlEncode(header):jwt令牌的第一部分。
base64UrlEncode(payload):jwt令牌的第二部分。
secret:簽名所使用的密鑰。
生成私鑰和公鑰
JWT令牌生成採用非對稱加密算法
1、生成密鑰證書
下邊命令生成密鑰證書,採用RSA 算法每個證書包含公鑰和私鑰。
keytool -genkeypair -alias xckey -keyalg RSA -keypass xuecheng -keystore xc.keystore -storepass xuechengkeystore
Keytool 是一個java提供的證書管理工具
-alias:密鑰的別名
-keyalg:使用的hash算法
-keypass:密鑰的訪問密碼
-keystore:密鑰庫文件名,xc.keystore保存了生成的證書
-storepass:密鑰庫的訪問密碼
查詢證書信息:
keytool -list -keystore xc.keystore
刪除別名
keytool -delete -alias xckey -keystore xc.keystore
2.導出公鑰
openssl是一個加解密工具包,這裏使用openssl來導出公鑰信息。
安裝 openssl:
https://pan.baidu.com/s/133OZPVr4jlTu9vIkPuDN4g
安裝資料目錄下的Win64OpenSSL-1_1_0g.exe
配置openssl的path環境變量,本教程配置在D:\OpenSSL-Win64\bin
cmd進入xc.keystore文件所在目錄執行如下命令:
keytool -list -rfc --keystore xc.keystore | openssl x509 -inform pem -pubkey
輸入密鑰庫密碼:
xuechengkeystore
下邊這一段就是公鑰內容:
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAijyxMdq4S6L1Af1rtB8SjCZHNgsQG8JTfGy55eYvzG0B
/E4AudR2prSRBvF7NYPL47scRCNPgLnvbQczBHbBug6uOr78qnWsYxHlW6Aa5dI5NsmOD4DLtSw8eX0hFyK5F
j6ScYOSFBz9cd1nNTvx2+oIv0lJDcpQdQhsfgsEr1ntvWterZt/8r7xNN83gHYuZ6TM5MYvjQNBc5qC7Krs9wM7U
oQuL+s0X6RlOib7/mcLn/lFLsLDdYQAZkSDx/6+t+1oHdMarChIPYT1sx9Dwj2j2mvFNDTKKKKAq0cv14Vrhz67Vj
mz2yMJePDqUi0JYS2r0iIo7n8vN7s83v5uOQIDAQAB
-----END PUBLIC KEY-----
將上邊的公鑰拷貝到文本文件中。
生成jwt令牌
創建測試類,測試jwt令牌的生成與驗證。
//創建jwt令牌
@Test
public void testCreateJwt(){
//密鑰庫文件,之前生成的私鑰文件
String keystore = "xc.keystore";
//密鑰庫的密碼
String keystore_password = "xuechengkeystore";
//密鑰庫文件路徑
ClassPathResource classPathResource = new ClassPathResource(keystore);
//密鑰別名
String alias = "xckey";
//密鑰的訪問密碼
String key_password = "xuecheng";
//密鑰工廠
KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(classPathResource,keystore_password.toCharArray());
//密鑰對(公鑰和私鑰)
KeyPair keyPair = keyStoreKeyFactory.getKeyPair(alias, key_password.toCharArray());
//獲取私鑰
RSAPrivateKey aPrivate = (RSAPrivateKey) keyPair.getPrivate();
//jwt令牌的內容
Map<String,String> body = new HashMap<>();
body.put("name","itcast");
String bodyString = JSON.toJSONString(body);
//生成jwt令牌
Jwt jwt = JwtHelper.encode(bodyString, new RsaSigner(aPrivate));
//生成jwt令牌編碼
String encoded = jwt.getEncoded();
System.out.println(encoded);
}
驗證JWT令牌
//校驗jwt令牌
@Test
public void testVerify(){
//公鑰
String publickey = "-----BEGIN PUBLIC KEY-----MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnASXh9oSvLRLxk901HANYM6KcYMzX8vFPnH/To2R+SrUVw1O9rEX6m1+rIaMzrEKPm12qPjVq3HMXDbRdUaJEXsB7NgGrAhepYAdJnYMizdltLdGsbfyjITUCOvzZ/QgM1M4INPMD+Ce859xse06jnOkCUzinZmasxrmgNV3Db1GtpyHIiGVUY0lSO1Frr9m5dpemylaT0BV3UwTQWVW9ljm6yR3dBncOdDENumT5tGbaDVyClV0FEB1XdSKd7VjiDCDbUAUbDTG1fm3K9sx7kO1uMGElbXLgMfboJ963HEJcU01km7BmFntqI5liyKheX+HBUCD4zbYNPw236U+7QIDAQAB-----END PUBLIC KEY-----";
//jwt令牌
String jwtString = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiaXRjYXN0In0.lQOqL1s4DpDHROUAibkz6EMf6hcM7HmTPgmg-SlkacVoQAV7y3XQ7LXxiua6SJlN_uNX_EFjzIshEg_kyy972DtymtRMc2NIO5HzIF5I4oQCxNPsJdhu6qQni6sTas3q0JbAarMZSajDX7HhzVSYWPQJCussA4e1r9oFxDcoAo6TEAXOW8gRHzNIygQz1yCj6mdf4UOHI070kRy7f3BdhmrUJdOuDIMoRBYS4WsEOibAU1UCNPaJAXpZC0ihrtdY7SCg1N43fimeFOHrfpLb6OmRF7v7uvGMgrhg9JIYDbJ6nbode5OJkNceRx8QUICre2yKAe0ctlvXO0REf6OpRA";
//校驗jwt令牌
Jwt jwt = JwtHelper.decodeAndVerify(jwtString, new RsaVerifier(publickey));
//拿到jwt令牌中自定義的內容
String claims = jwt.getClaims();
System.out.println(claims);
}