JWT全稱JSON Web Token,是一個緊湊的,自包含的,安全的資訊交換協議。JWT有很多方面的應用,例如許可權認證,資訊交換等。本文將簡單介紹JWT登入許可權認證的一個例項操作。
JWT組成
JWT由頭部(Header),負載(Payload)和簽名(Signature)三部分組成。其中頭部包含了JWT的宣告資訊,例如簽名所用的演算法等。
{
"alg": "HS256",
"typ": "JWT"
}
負載部分是負責資訊的承載,在通訊過程中,我們將要交換的資訊放置於負載部分。
{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}
簽名部分是JWT安全的保障,在傳輸過程中,頭部和負載部分會經過Base64編碼在網路中明文傳輸,既然是明文,為了保障資訊在傳輸過程中不被篡改。JWT會對編碼之後的頭部和負載進行一個訊息簽名。
Signature = HMACSHA256(header + "." + payload + secret)
經過簽名之後的JWT保證了資料不會被劫持並篡改。其中secret極為重要,即使有人劫持了訊息,在不知道secret的情況下,無法簽名出一個有效的JWT。
JWT的形式
JWT由三部分組成:頭部,負載和簽名。最終的JWT字串可以呈現出這三部分,在JWT中,.
為分割各部分的分隔符,按照順序依次為頭部,負載和簽名。不過這時你已經看不到JSON的形式,頭部和負載會經過Base64編碼,最終得到一個字串。不過Base64並不是加密演算法,它是一種編碼格式,你可以通過一定的工具解碼之後就會得到相應的JSON字串。
JWT實現許可權認證
在網際網路Web應用開發中,最為常見的一項工作就是認證使用者,狀態化HTTP請求和授予資源訪問許可權。
其中認證使用者通過使用者名稱、密碼的登入操作實現,但是HTTP請求是無狀態的,為了標記已經登入成功的使用者,我們可以通過設定SESSION_ID
,COOKIE
來標記認證過的請求。但是它們都需要在服務端額外的儲存這些SESSION_ID
和COOKIE
。在最初的單體應用架構中,儲存可以在服務端中開闢一塊記憶體,以鍵值對的形式儲存SESSION_ID
,COOKIE
與使用者的對映關係。分散式架構中可以使用REDIS
等中介軟體來實現SESSION
共享。
為什要儲存對映關係?通過SESSION_ID
和COOKIE
我們不能夠直接得到使用者資訊嗎?其實並不是不可以,而是不安全,SESSION_ID
和COOKIE
是可以供使用者自由操作的。如果直接明文形式的將使用者資訊寫入其中,那麼這些資訊極有可能會被篡改。所以通常我們會在服務端生成隨機字串,寫入到SESSION_ID
或COOKIE
中,再將隨機字串與使用者之間建立一個對映。這樣,客戶端的使用者並不能隨意篡改這些資訊了。因為並不知道其他使用者的隨機字串是什麼。
JWT也是字串,只不過是編碼之後的字串,而且這個字串是安全的。因為它是被服務端簽名認證的。如果有使用者修改的痕跡,那麼服務端在檢驗時會發現字串被修改。正是這一特性保障了認證的安全性。
在業務中,JWT可以實現雙向校驗,即通訊雙方都可以校驗JWT有無被篡改。實現方式是通過非對稱加密技術。
Java-JWT許可權認證Demo
登陸成功之後,服務端簽發JWT程式碼:
final long expireTime = 1000 * 60 * 60 * 4; //JWT有效期為4小時
final String loginWebToken = JWT.create()
.withIssuer(configurationProperties.getJwtLoginIssuer())
.withClaim("username", vo.getUsername()) // 負載部分
.withClaim("user_id", admin.getId())
.withExpiresAt(new Date(System.currentTimeMillis() + expireTime)) // 設定有效期
.sign(Algorithm.HMAC256(configurationProperties.getJwtSignKey())); // 進行簽名
簽發的JWT可以直接返回給客戶端,有客戶端JS程式碼寫入下次請求的指定位置,也可以由服務端寫入SESSION_ID
或COOKIE
中。
校驗JWT程式碼,攔截未認證的請求:
try
{
final String loginWebToken = request.getHeader("Authorization");
// final DecodedJWT decodeToken = JWT.decode(loginWebToken);
// String username = decodeToken.getClaim("username").asString();
// JWT驗證
JWT.require(Algorithm.HMAC256(configurationProperties.getJwtSignKey()))
.withIssuer(configurationProperties.getJwtLoginIssuer())
.build().verify(loginWebToken);
return true;
}catch (Exception ex)
{
Response re = new Response()
.setMsg("許可權受限,請登陸")
.setData(null)
.setSuccess(false);
response.reset();
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json;charset=UTF-8");
response.getWriter().print(JSON.toJSONString(re));
return false;
}
業務中使用JWT實現HTTP狀態化,服務不同認證使用者:
@GetMapping("/user/info")
public Response getUserInfo(HttpServletRequest req)
{
final String loginWebToken = request.getHeader("Authorization");
final DecodedJWT jwt = JWT.decode(loginWebToken);
log.info("歡迎{}使用系統", jwt.getClaim("username");
return userService.getUserInfo(jwt.getClaim("user_id"));
}