使用JWT實現Token認證
為什麼使用JWT?
json Web Token(縮寫JWT)是目前最流行的跨域認證解決方案。
session登入的認證方案是看,使用者從客戶端傳遞使用者名稱和密碼登入資訊,服務端認證後將資訊儲存在session中,將session_id放入cookie中,以後訪問其他頁面,瀏覽器都會帶著cookie,服務端會自動從cookie中獲取session_id,在從session中獲取認證資訊。
JWT的解決方案是,將認證資訊返回個客戶端,儲存在客戶端,下次訪問其他頁面,需要從客戶端傳遞認證資訊回伺服器端。
JWT應用場景:
下列場景中使用JSON Web Token是很有用的:
- Authorization
- Information Exchange(資訊交換) : 對於安全的在各方之間傳輸資訊而言,JSON Web Tokens無疑是一種很好的方式。因為JWTs可以被簽名,例如,用公鑰/私鑰對,你可以確定傳送人就是它們所說的那個人。另外,由於簽名是使用頭和有效負載計算的,您還可以驗證內容沒有被篡改。
優點:
1.簡潔(Compact): 可以通過URL
,POST
引數或者在HTTP header
2.自包含(Self-contained):負載中包含了所有使用者所需要的資訊,避免了多次查詢資料庫
3.因為Token
是以JSON
加密的形式儲存在客戶端的,所以JWT
是跨語言的,原則上任何web形式都支援。
4.不需要在服務端儲存會話資訊,特別適用於分散式微服務。
JSON Web Token的結構是什麼樣的
JSON Web Token由三部分組成,它們之間用圓點(.)連線。這三部分分別是:
- Header
- Payload
- Signature
因此,一個典型的JWT看起來是這個樣子的:
xxxxx.yyyyy.zzzzz
接下來,具體看一下每一部分:
Header
header典型的由兩部分組成:token的型別(“JWT”)和演算法名稱(比如:HMAC SHA256或者RSA等等)。
例如:
然後,用Base64對這個JSON編碼就得到JWT的第一部分
Payload
JWT的第二部分是payload,它包含宣告(要求)。宣告是關於實體(通常是使用者)和其他資料的宣告。宣告有三種類型: registered, public 和 private。
- Registered claims : 這裡有一組預定義的宣告,它們不是強制的,但是推薦。比如:iss (issuer), exp (expiration time), sub (subject), aud (audience)等。
- Public claims : 可以隨意定義。
- Private claims : 用於在同意使用它們的各方之間共享資訊,並且不是註冊的或公開的宣告。
下面是一個例子:
對payload進行Base64編碼就得到JWT的第二部分
注意,不要在JWT的payload或header中放置敏感資訊,除非它們是加密的。
Signature
為了得到簽名部分,你必須有編碼過的header、編碼過的payload、一個祕鑰,簽名演算法是header中指定的那個,然對它們簽名即可。
例如:
HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)
簽名是用於驗證訊息在傳遞過程中有沒有被更改,並且,對於使用私鑰簽名的token,它還可以驗證JWT的傳送方是否為它所稱的傳送方。
看一張官網的圖就明白了:
JSON Web Tokens是如何工作的
- 應用(或者客戶端)想授權伺服器請求授權。例如,如果用授權碼流程的話,就是/oauth/authorize
- 當授權被許可以後,授權伺服器返回一個access token給應用
- 應用使用access token訪問受保護的資源(比如:API)
maven依賴:
<dependency> <groupId>com.auth0</groupId> <artifactId>java-jwt</artifactId> <version>3.2.0</version> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.7.0</version> </dependency>
JWT工具類:
用於生成Token,和Token驗證
public class JwtUtils { /** * 簽發JWT * * @param id * @param subject 可以是JSON資料 儘可能少 * @param ttlMillis * @return String * */ public static String createJWT(String id, String subject, long ttlMillis) { SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256; long nowMillis = System.currentTimeMillis(); Date now = new Date(nowMillis); SecretKey secretKey = generalKey(); JwtBuilder builder = Jwts.builder().setId(id).setSubject(subject) // 主題 .setIssuer("user") // 簽發者 .setIssuedAt(now) // 簽發時間 .signWith(signatureAlgorithm, secretKey); // 簽名演算法以及密匙 if (ttlMillis >= 0) { long expMillis = nowMillis + ttlMillis; Date expDate = new Date(expMillis); builder.setExpiration(expDate); // 過期時間 } return builder.compact(); } /** * 驗證JWT * * @param jwtStr * @return */ public static CheckResult validateJWT(String jwtStr) { CheckResult checkResult = new CheckResult(); Claims claims = null; try { claims = parseJWT(jwtStr); checkResult.setSuccess(true); checkResult.setClaims(claims); } catch (ExpiredJwtException e) { checkResult.setErrCode(SystemConstant.JWT_ERRCODE_EXPIRE); checkResult.setSuccess(false); } catch (SignatureException e) { checkResult.setErrCode(SystemConstant.JWT_ERRCODE_FAIL); checkResult.setSuccess(false); } catch (Exception e) { checkResult.setErrCode(SystemConstant.JWT_ERRCODE_FAIL); checkResult.setSuccess(false); } return checkResult; } public static SecretKey generalKey() { byte[] encodedKey = Base64.decode(SystemConstant.JWT_SECERT); SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES"); return key; } /** * * 解析JWT字串 * * @param jwt * @return * @throws Exception */ public static Claims parseJWT(String jwt) throws Exception { SecretKey secretKey = generalKey(); return Jwts.parser().setSigningKey(secretKey).parseClaimsJws(jwt).getBody(); } }
如何使用?
程式碼例項:
public class LoginController { @Autowired UserRepository userRepository; @RequestMapping(value="login",method = RequestMethod.POST) public ReturnVo login(String username, String password,HttpServletResponse response) { User user = userRepository.findByUsername(username); if(user!=null){ if(user.getPassword().equals(password)){ //把token返回給客戶端-->客戶端儲存至cookie-->客戶端每次請求附帶cookie引數 String JWT = JwtUtils.createJWT("1", username, SystemConstant.JWT_TTL); return ReturnVo.ok(JWT); }else{ return ReturnVo.error(); } }else{ return ReturnVo.error(); } }
@RequestMapping(value="description",method = RequestMethod.POST) public ReturnVo description(String username) { User user = userRepository.findByUsername(username); return ReturnVo.ok(user.getDescription()); } }