JWT實現SSO


1.JWT

JWT是json web token縮寫。它將用戶信息加密到token里,服務器不保存任何用戶信息。服務器通過使用保存的密鑰驗證token的正確性,只要正確即通過驗證。JWT是一種緊湊且自包含的,用於在多方傳遞JSON對象的技術。傳遞的數據可以使用數字簽名增加其安全行。可以使用HMAC加密算法或RSA公鑰/私鑰加密方式),且數據小代表傳輸速度快。JWT一般用於處理用戶身份驗證或數據信息交換。

 

2.傳統的利用cookie作為媒介存在的一些問題

1)cookie不安全很容易被截取和偽造

2)cookie自身存在跨域問題,之所以能夠跨域是因為domain,以及Access-Control-Allow-Origin,根本原因是子域可以通過父域的攔截

3)保存在cookie中的session  id 自身保存服務器內存中,自身存在跨服務器問題,所以需要保存在mysql中或者redies中,頻繁的數據庫操作給效率帶來一定的影響

4)上述問題可以解決,但是作用的是拓展性問題,往往需要做很大的更改。

5)容易受到CSRF(跨站請求偽造)的攻擊

 

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; }

 

 
        

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM