1.JWTjava
JWT是json web token縮寫。它將用戶信息加密到token裏,服務器不保存任何用戶信息。服務器經過使用保存的密鑰驗證token的正確性,只要正確即經過驗證。JWT是一種緊湊且自包含的,用於在多方傳遞JSON對象的技術。傳遞的數據可使用數字簽名增長其安全行。可使用HMAC加密算法或RSA公鑰/私鑰加密方式),且數據小表明傳輸速度快。JWT通常用於處理用戶身份驗證或數據信息交換。mysql
2.傳統的利用cookie做爲媒介存在的一些問題程序員
1)cookie不安全很容易被截取和僞造web
2)cookie自身存在跨域問題,之因此可以跨域是由於domain,以及Access-Control-Allow-Origin,根本緣由是子域能夠經過父域的攔截算法
3)保存在cookie中的session id 自身保存服務器內存中,自身存在跨服務器問題,因此須要保存在mysql中或者redies中,頻繁的數據庫操做給效率帶來必定的影響sql
4)上述問題能夠解決,可是做用的是拓展性問題,每每須要作很大的更改。數據庫
5)容易受到CSRF(跨站請求僞造)的攻擊json
3.JWT機制跨域
優勢:全部的數據不在由服務器保存,而是經過相應的私鑰加密之後保存在頭文件中,而服務器只保存祕鑰便可,這樣增長了相應的拓展性,無狀態、可擴展,還能夠防止CSRF(跨站請求僞造)瀏覽器
缺點:當相應的json文件發送之後,咱們便再也不能夠控制
4.HTTP與HTTPS
HTTP 是一種沒有狀態的協議,也就是它並不知道是誰是訪問應用
HTTPS是在創建鏈接的時候會接受服務器傳過來的證書,基於SSL或者TLS,TLS是基於TLS的基礎上發展來的,經過相應的證書完成相應的認證來識別對象
5.Token認證的過程
客戶端使用用戶名、密碼請求登陸,服務端收到請求,去驗證用戶名、密碼,驗證成功後,服務端會簽發一個 Token,再把這個 Token 發送給客戶端,客戶端收到 Token 之後能夠把它存儲起來,好比放在 Cookie 裏或者 Local Storage 裏(cookie保存方式,能夠實現跨域傳遞數據。localStorage是域私有的本地存儲,沒法實現跨域。webstorage可保存的數據容量爲5M。且只能存儲字符串數據),客戶端每次向服務端請求資源的時候須要帶着服務端簽發的 Token,服務端收到請求,而後去驗證客戶端請求裏面帶着的 Token,若是驗證成功,就向客戶端返回請求的數據
備註:
webstorage分爲localStorage和sessionStorage。
localStorage的生命週期是永久的,關閉頁面或瀏覽器以後localStorage中的數據也不會消失。localStorage除非主動刪除數據,不然數據永遠不會消失。
sessionStorage是會話相關的本地存儲單元,生命週期是在僅在當前會話下有效。sessionStorage引入了一個「瀏覽器窗口」的概念,sessionStorage是在同源的窗口中始終存在的數據。只要這個瀏覽器窗口沒有關閉,即便刷新頁面或者進入同源另外一個頁面,數據依然存在。可是sessionStorage在關閉了瀏覽器窗口後就會被銷燬。同時獨立的打開同一個窗口同一個頁面,sessionStorage也是不同的。
6.JWT的數據結構是 : A.B.C
A - header 頭信息
B - payload (有效荷載?)
C - Signature 簽名
header
數據結構: {「alg」: 「加密算法名稱」, 「typ」 : 「JWT」}
alg是加密算法定義內容,如:HMAC SHA256 或 RSA
typ是token類型,這裏固定爲JWT。
payload
在payload數據塊中通常用於記錄實體(一般爲用戶信息)或其餘數據的。主要分爲三個部分,分別是:已註冊信息(registered claims),公開數據(public claims),私有數據(private claims)。
payload中經常使用信息有:iss(發行者),exp(到期時間),sub(主題),aud(受衆)等。前面列舉的都是已註冊信息。公開數據部分通常都會在JWT註冊表中增長定義。避免和已註冊信息衝突。
公開數據和私有數據能夠由程序員任意定義。
注意:即便JWT有簽名加密機制,可是payload內容都是明文記錄,除非記錄的是加密數據,不然不排除泄露隱私數據的可能。不推薦在payload中記錄任何敏感數據。
Signature
簽名信息。這是一個由開發者提供的信息。是服務器驗證的傳遞的數據是否有效安全的標準。在生成JWT最終數據的以前。先使用header中定義的加密算法,將header和payload進行加密,並使用點進行鏈接。如:加密後的head.加密後的payload。再使用相同的加密算法,對加密後的數據和簽名信息進行加密。獲得最終結果。
7.實例
依賴
<dependency> <groupId>com.auth0</groupId> <artifactId>java-jwt</artifactId> <version>3.4.1</version> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency> <dependency> <groupId>net.iharder</groupId> <artifactId>base64</artifactId> <version>2.3.9</version> </dependency>
獲取祕鑰
// 服務器的key。用於作加解密的key數據。 private static final String JWT_SECERT = "test_jwt_secert" ; public static SecretKey generalKey() { try { // 無論哪一種方式最終獲得一個byte[]類型的key就行 byte[] encodedKey = Base64.decode(JWT_SECERT); byte[] encodedKey = JWT_SECERT.getBytes("UTF-8"); SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES"); return key; } catch (Exception e) { e.printStackTrace(); return null; } }
加密
/**
* 簽發JWT,建立token的方法。
* @param id jwt的惟一身份標識,主要用來做爲一次性token,從而回避重放攻擊。
* @param iss jwt簽發者
* @param subject jwt所面向的用戶。payload中記錄的public claims。
* @param ttlMillis 有效期,單位毫秒
* @return token, token是一次性的。是爲一個用戶的有效登陸週期準備的一個token。用戶退出或超時,token失效。
* @throws Exception
*/
public static String createJWT(String id,String iss, String subject, long ttlMillis) {
// 加密算法
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
// 當前時間。
long nowMillis = System.currentTimeMillis();
// 當前時間的日期對象。
Date now = new Date(nowMillis);
SecretKey secretKey = generalKey();
// 建立JWT的構建器。 就是使用指定的信息和加密算法,生成Token的工具。
JwtBuilder builder = Jwts.builder()
.setId(id) // 設置身份標誌。就是一個客戶端的惟一標記。 如:可使用用戶的主鍵,客戶端的IP,服務器生成的隨機數據。
.setIssuer(iss)
.setSubject(subject)
.setIssuedAt(now) // token生成的時間。
.signWith(signatureAlgorithm, secretKey); // 設定密匙和算法
if (ttlMillis >= 0) {
long expMillis = nowMillis + ttlMillis;
Date expDate = new Date(expMillis); // token的失效時間。
builder.setExpiration(expDate);
}
return builder.compact(); // 生成token
}
public class JWTResult { /** * 錯誤編碼。在JWTUtils中定義的常量。 * 200爲正確 */ private int errCode; /** * 是否成功,表明結果的狀態。 */ private boolean success; /** * 驗證過程當中payload中的數據。 */ private Claims claims; public int getErrCode() { return errCode; } public void setErrCode(int errCode) { this.errCode = errCode; } public boolean isSuccess() { return success; } public void setSuccess(boolean success) { this.success = success; } public Claims getClaims() { return claims; } public void setClaims(Claims claims) { this.claims = claims; } } public class JWTSubject { private String username; public JWTSubject() { super(); } public JWTSubject(String username) { super(); this.username = username; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } }
解析與驗證
/**
* 驗證JWT
* @param jwtStr
* @return
*/
public static JWTResult validateJWT(String jwtStr) {
JWTResult checkResult = new JWTResult();
Claims claims = null;
try {
claims = parseJWT(jwtStr);
checkResult.setSuccess(true);
checkResult.setClaims(claims);
} catch (ExpiredJwtException e) { // token超時
checkResult.setErrCode(JWT_ERRCODE_EXPIRE);
checkResult.setSuccess(false);
} catch (SignatureException e) { // 校驗失敗
checkResult.setErrCode(JWT_ERRCODE_FAIL);
checkResult.setSuccess(false);
} catch (Exception e) {
checkResult.setErrCode(JWT_ERRCODE_FAIL);
checkResult.setSuccess(false);
}
return checkResult;
}
/**
*
* 解析JWT字符串
* @param jwt 就是服務器爲客戶端生成的簽名數據,就是token。
* @return
* @throws Exception
*/
public static Claims parseJWT(String jwt) throws Exception {
SecretKey secretKey = generalKey();
return Jwts.parser()
.setSigningKey(secretKey)
.parseClaimsJws(jwt)
.getBody(); // getBody獲取的就是token中記錄的payload數據。就是payload中保存的全部的claims。
}
test
登陸驗證的類
public class JWTUsers { private static final Map<String, String> USERS = new HashMap<>(16); static{ for(int i = 0; i < 10; i++){ USERS.put("admin"+i, "password"+1); } } // 是否可登陸 public static boolean isLogin(String username, String password){ if(null == username || username.trim().length() == 0){ return false; } String obj = USERS.get(username); if(null == obj || !obj.equals(password)){ return false; } return true; } }
返回的對象(後續通過json處理的對象)
public class JWTResponseData { private Integer code;// 返回碼,相似HTTP響應碼。如:200成功,500服務器錯誤,404資源不存在等。 private Object data;// 業務數據 private String msg;// 返回描述 private String token;// 身份標識, JWT生成的令牌。 public Integer getCode() { return code; } public void setCode(Integer code) { this.code = code; } public Object getData() { return data; } public void setData(Object data) { this.data = data; } public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; } public String getToken() { return token; } public void setToken(String token) { this.token = token; } }
//產看是否存在token
@RequestMapping("/testAll") @ResponseBody public Object testAll(HttpServletRequest request){ String token = request.getHeader("Authorization"); JWTResult result = JWTUtils.validateJWT(token); JWTResponseData responseData = new JWTResponseData(); if(result.isSuccess()){ responseData.setCode(200); responseData.setData(result.getClaims().getSubject()); // 從新生成token,就是爲了重置token的有效期。 String newToken = JWTUtils.createJWT(result.getClaims().getId(), result.getClaims().getIssuer(), result.getClaims().getSubject(), 1*60*1000); responseData.setToken(newToken); return responseData; }else{ responseData.setCode(500); responseData.setMsg("用戶未登陸"); return responseData; } } //登陸頁面產生token @RequestMapping("/login") @ResponseBody public Object login(String username, String password){ JWTResponseData responseData = null; // 認證用戶信息。本案例中訪問靜態數據。 if(JWTUsers.isLogin(username, password)){ JWTSubject subject = new JWTSubject(username); String jwtToken = JWTUtils.createJWT(UUID.randomUUID().toString(), "test_jwt_secert", JWTUtils.generalSubject(subject), 1*60*1000); responseData = new JWTResponseData(); responseData.setCode(200); responseData.setData(null); responseData.setMsg("登陸成功"); responseData.setToken(jwtToken); }else{ responseData = new JWTResponseData(); responseData.setCode(500); responseData.setData(null); responseData.setMsg("登陸失敗"); responseData.setToken(null); } return responseData; }