1. 程式人生 > >JWT學習(二):JWT在分散式SSO中的應用例項

JWT學習(二):JWT在分散式SSO中的應用例項


上一篇文章講解了JWT的基本簡介,這一篇文章我就來實戰一下。介紹一下在分散式單點登入中的使用方法:

首先來看一下Token實體類,

public class Token implements Serializable{
	private static final long serialVersionUID = -5391652691006115018L;
	
	/** 認證頭 **/
	private Head head;
	/** 認證資訊有效載荷 **/
	private Playload playload;
	
	/** 第一部分:base64head頭 **/
	private String base64Head;
	/** 第二部分:base64playload荷載 **/
	private String base64PlayLoad;
	/** 第三部分:簽證資訊 **/
	private String signature;
	/** 最終token字串 **/
	private String tokenStr;
	
	/**
	 * Token頭
	 *
	 *
	 */
	public static class Head implements Serializable{
		private static final long serialVersionUID = -6516084948347601103L;
		
		/** token型別 **/
		private String typ = "JWT";
		/** token演算法 預設:HMAC SHA256**/
		private String alg = "HS256";
		
		public String getTyp() {
			return typ;
		}
		public void setTyp(String typ) {
			this.typ = typ;
		}
		public String getAlg() {
			return alg;
		}
		public void setAlg(String alg) {
			this.alg = alg;
		}
	}
	
	/**
	 * Token有效載荷
	 * 
	 *
	 */
	public static class Playload implements Serializable {
		private static final long serialVersionUID = 3981890375700111920L;
		
		/** 該token簽發者 **/
		private String iss;
		/** 該token的所有人,可以存放使用者名稱 **/
		private String sub;
		/** 接收token的一方 **/
		private String aud;
		/** token的過期時間(時間戳),必須要大於簽發時間;大於等於該時間需要重新整理token **/
		private long exp;
		/** token生效的開始時間(時間戳),意味著在這個時間之前驗證token是會失敗的,預設生成token後立即生效 **/
		private long nbf;
		/** token的簽發時間 時間戳**/
		private long iat;
		/** token的唯一身份標識,主要用來作為一次性token,從而回避重放攻擊 **/
		private String jti;
		/** token驗證寬限時間(時間戳) 超過寬限時間需要重新登入,  
		 * 即該token的真正存活時間,寬限時間的加入是為了解決併發token重新整理後新token失效問題
		 * **/
		private long gra;
		/** token型別:  後臺登入使用者,網際網路使用者,第三方機構 **/
		private String typ;
		
		public String getIss() {
			return iss;
		}
		public void setIss(String iss) {
			this.iss = iss;
		}
		public String getSub() {
			return sub;
		}
		public void setSub(String sub) {
			this.sub = sub;
		}
		public String getAud() {
			return aud;
		}
		public void setAud(String aud) {
			this.aud = aud;
		}
		public long getExp() {
			return exp;
		}
		public void setExp(long exp) {
			this.exp = exp;
		}
		public long getNbf() {
			return nbf;
		}
		public void setNbf(long nbf) {
			this.nbf = nbf;
		}
		public long getIat() {
			return iat;
		}
		public void setIat(long iat) {
			this.iat = iat;
		}
		public String getJti() {
			return jti;
		}
		public void setJti(String jti) {
			this.jti = jti;
		}
		public long getGra() {
			return gra;
		}
		public void setGra(long gra) {
			this.gra = gra;
		}
		public String getTyp() {
			return typ;
		}
		public void setTyp(String typ) {
			this.typ = typ;
		}

		
	}

	public Head getHead() {
		return head;
	}
	public void setHead(Head head) {
		this.head = head;
	}
	public Playload getPlayload() {
		return playload;
	}
	public void setPlayload(Playload playload) {
		this.playload = playload;
	}
	public String getSignature() {
		return signature;
	}
	public void setSignature(String signature) {
		this.signature = signature;
	}
	public String getBase64Head() {
		return base64Head;
	}
	public void setBase64Head(String base64Head) {
		this.base64Head = base64Head;
	}
	public String getBase64PlayLoad() {
		return base64PlayLoad;
	}
	public void setBase64PlayLoad(String base64PlayLoad) {
		this.base64PlayLoad = base64PlayLoad;
	}
	public String getTokenStr() {
		return tokenStr;
	}
	public void setTokenStr(String tokenStr) {
		this.tokenStr = tokenStr;
	}
	
}

可以看到實體類裡面包含了head payload signature這三部分。

然後使用者登入成功之後建立token的程式碼如下:

public static Token createToken(String secret,String tokenId,TokenType tokenType,String userName) {
		try {
			Token token = new Token();
			//建立頭
			Token.Head head = new Token.Head();
			head.setAlg("HS256");
			head.setTyp("JWT");
			
			//建立載荷
			//簽發時間
			long iat = System.currentTimeMillis();
			//過期時間 20 分鐘後過期
			long exp = iat + AuthConstants.TOKEN_EXP_TIME ;
			//最後存活時間
			long gra = iat + AuthConstants.TOKEN_GRA_TIME ;
			Token.Playload playload = new Token.Playload();
			playload.setAud("CLIENT"); //接收token的一方
			playload.setIat(iat); //簽發時間
			playload.setExp(exp);
			playload.setGra(gra);
			playload.setIss("AUTH_CENTER");//簽發者
			playload.setJti(tokenId); //token唯一身份標識
			playload.setNbf(iat);//生效時間,立即生效
			playload.setSub(userName); //token的所屬者,可以存放使用者名稱
			playload.setTyp(tokenType.toString().toUpperCase());
			
			//建立token
			String base64Head = Base64Util.encodeStr(JSONUtil.toJson(head) );
			String base64Playload = Base64Util.encodeStr(JSONUtil.toJson(playload) );
			String signature = HmacUtil.encryptHMACSHA256(base64Head+"."+base64Playload, secret); //token簽名
			String tokenStr = base64Head+"."+base64Playload+"."+signature; //token字串
			
			//組裝token物件
			token.setHead(head);
			token.setPlayload(playload);
			token.setBase64Head(base64Head);
			token.setBase64PlayLoad(base64Playload);
			token.setSignature(signature);
			token.setTokenStr(tokenStr);
			return token;
		} catch (Exception e) {
			e.printStackTrace();
			logger.error("生成token失敗:{}",e.getMessage());
		}
		return null;
	}

建立成功之後,要把token放到響應頭中,setHeader方法name引數要用Authorization,value值要使用

"Bearer "+token。

然後使用者訪問需要許可權的介面都需要在請求頭加上token,因為使用了Spring Cloud微服務架構,因此請求

會統一通過API閘道器訪問,所以需要在閘道器處驗證token的合法性,使用下面這個parseToken的方法:

/**
	 * 驗證並解析token
	 * @param token token字串
	 * @param secret token簽名的鹽(金鑰)
	 * @return 成功返回token ,失敗返回ull
	 */
	public static Token parseToken(String token,String secret) {
		try {
			//判斷token是否是合法格式的token串
			if(StringUtils.isEmpty(token)) {
				throw new AuthException("token串為空!");
			}
			if(StringUtils.isEmpty(secret)) {
				throw new AuthException("解析token時,token金鑰為空!");
			}
			String[] tokens = token.split("\\.");
			if(tokens==null || tokens.length!=3) {
				throw new AuthException("非法格式的token串:"+token);
			}
			
			//token分解
			String base64Head = tokens[0].trim(); //token頭
			String base64Playload = tokens[1].trim(); //token載荷
			String signature = tokens[2].trim(); //token簽名
			//驗證簽名是否為合法的
			String signData = base64Head+"."+base64Playload;
			String signaturedStr = HmacUtil.encryptHMACSHA256(signData, secret.trim()); //token簽名
			if(!signature.equals(signaturedStr)) {
				throw new AuthException("非法的token:解析token時,token驗籤失敗!");
			}
			
			Token.Head head = JSONUtil.toBean(Base64Util.decodeStr(base64Head), Token.Head.class);
			Token.Playload playLoad = JSONUtil.toBean(Base64Util.decodeStr(base64Playload), Token.Playload.class);
			Token rs = new Token();
			rs.setHead(head);
			rs.setPlayload(playLoad);
			rs.setSignature(signature);
			return rs;
		} catch (Exception e) {
			e.printStackTrace();
			logger.error("解析token失敗:{}",e.getMessage());
			return null;
		}
	}

使用JWT進行身份驗證的基本方法的例項就到這裡,沒有接觸過JWT的同學可以先看一下JWT