Token機制是sso單點登錄的最主要實現機制,最常用的實現機制。傳統身份認證,一般一個應用服務器,在客戶端和服務器關聯的時候,在應用服務器上做了一個HttpSession對象保持着客戶端和服務器上狀態信息存儲。
1、傳統身份認證。
答:HTTP是一種沒有狀態的協議,也就是它並不知道是誰是訪問應用。這里我們把用戶看成是客戶端,客戶端使用用戶名還有密碼通過了身份驗證,不過下回這個客戶端再發送請求時候,還得再驗證一下。
解決的方法就是,當用戶請求登錄的時候,如果沒有問題,我們在服務端生成一條記錄,這個記錄里可以說明一下登錄的用戶是誰,然后把這條記錄的 ID 號發送給客戶端,客戶端收到以后把這個 ID 號存儲在 Cookie 里,下次這個用戶再向服務端發送請求的時候,可以帶着這個 Cookie ,這樣服務端會驗證一個這個 Cookie 里的信息,看看能不能在服務端這里找到對應的記錄,如果可以,說明用戶已經通過了身份驗證,就把用戶請求的數據返回給客戶端。
上面說的就是 Session,我們需要在服務端存儲為登錄的用戶生成的 Session ,這些 Session 可能會存儲在內存,磁盤,或者數據庫里。我們可能需要在服務端定期的去清理過期的 Session 。
這種認證中出現的問題是:
a)、Session:每次認證用戶發起請求時,服務器需要去創建一個記錄來存儲信息。當越來越多的用戶發請求時,內存的開銷也會不斷增加。
b)、可擴展性:在服務端的內存中使用Session存儲登錄信息,伴隨而來的是可擴展性問題。
c)、CORS(跨域資源共享):當我們需要讓數據跨多台移動設備上使用時,跨域資源的共享會是一個讓人頭疼的問題。在使用Ajax抓取另一個域的資源,就可以會出現禁止請求的情況。
d)、CSRF(跨站請求偽造):用戶在訪問銀行網站時,他們很容易受到跨站請求偽造的攻擊,並且能夠被利用其訪問其他的網站。在這些問題中,可擴展性是最突出的。因此我們有必要去尋求一種更有行之有效的方法。
2、Token身份認證。
答:2.1)、使用基於 Token 的身份驗證方法,在服務端不需要存儲用戶的登錄記錄。大概的流程是這樣的:
a)、客戶端使用用戶名、密碼請求登錄。
b)、服務端收到請求,去驗證用戶名、密碼。
c)、驗證成功后,服務端會簽發一個 Token(令牌),再把這個 Token 發送給客戶端。
d)、客戶端收到 Token 以后可以把它存儲起來,比如放在 Cookie 里或者 Local Storage 、Session Storage里。
e)、客戶端每次向服務端請求資源的時候需要帶着服務端簽發的 Token。
f)、服務端收到請求,然后去驗證客戶端請求里面帶着的 Token,如果驗證成功,就向客戶端返回請求的數據。
2.2)、使用Token驗證的優勢:
a)、無狀態、可擴展。
b)、在客戶端存儲的Tokens是無狀態的,並且能夠被擴展。基於這種無狀態和不存儲Session信息,負載負載均衡器能夠將用戶信息從一個服務傳到其他服務器上。
c)、安全性。
d)、請求中發送token而不再是發送cookie能夠防止CSRF(跨站請求偽造)。即使在客戶端使用cookie存儲token,cookie也僅僅是一個存儲機制而不是用於認證。不將信息存儲在Session中,讓我們少了對session操作。
3、JSON Web Token(JWT)機制。
答:Token在Java中具體實現的方案,JWT(Json數據做web網絡層的令牌機制),可以做加密擴展或者簽名擴展。
1)、JSON Web Token(JWT)機制。
a、JWT是一種緊湊(小而少,只要包含了用戶信息即可)且自包含(json數據中包含本次訪問簡單記錄,記錄不敏感數據)的,用於在多方傳遞JSON對象的技術。傳遞的數據可以使用數字簽名增加其安全行。可以使用HMAC加密算法或RSA公鑰/私鑰加密方式。
b、緊湊:數據小,可以通過URL,POST參數,請求頭發送。且數據小代表傳輸速度快。
c、自包含:使用payload數據塊記錄用戶必要且不隱私的數據,可以有效的減少數據庫訪問次數,提高代碼性能。
d、JWT一般用於處理用戶身份驗證或數據信息交換。
e、用戶身份驗證:一旦用戶登錄,每個后續請求都將包含JWT,允許用戶訪問該令牌允許的路由,服務和資源。單點登錄是當今廣泛使用JWT的一項功能,因為它的開銷
很小,並且能夠輕松地跨不同域使用。
f、數據信息交換:JWT是一種非常方便的多方傳遞數據的載體,因為其可以使用數據簽名來保證數據的有效性和安全性。
h、官網: http://jwt.io。
2)、JWT數據結構。JWT的數據結構是 : A.B.C。 由字符點'.'來分隔三部分數據。
2.1)、A:header 頭信息。
a)、數據結構: {"alg": "加密算法名稱", "typ" : "JWT"}。
b)、alg是加密算法定義內容,如:HMAC SHA256 或 RSA。
c)、typ是token類型,這里固定為JWT。
2.2)、B:payload (有效荷載,可以考慮不傳遞任何信息)。
a)、在payload數據塊中一般用於記錄實體(通常為用戶信息)或其他數據的。主要分為三個部分,分別是:已注冊信息(registered claims),公開數據(public claims),私有數據(private claims)。
b)、payload中常用信息有:iss(發行者),exp(到期時間),sub(主題),aud(受眾)等。前面列舉的都是已注冊信息。
c)、公開數據部分一般都會在JWT注冊表中增加定義。避免和已注冊信息沖突。
d)、公開數據和私有數據可以由程序員任意定義。
e)、注意:即使JWT有簽名加密機制,但是payload內容都是明文記錄,除非記錄的是加密數據,否則不排除泄露隱私數據的可能。不推薦在payload中記錄任何敏感數據。
2.3)、C:Signature 簽名。
簽名信息。這是一個由開發者提供的信息。是服務器驗證的傳遞的數據是否有效安全的標准。在生成JWT最終數據的之前。先使用header中定義的加密算法,將header和payload進行加密,並使用點進行連接。如:加密后的head.加密后的payload。再使用相同的加密算法,對加密后的數據和簽名信息進行加密。得到最終結果。
4、JWT(JSON Web Token)執行流程,如下所示:
5、使用一個簡單的項目來驗證一下JWT的實現。pom.xml依賴配置,如下所示:
1 <project xmlns="http://maven.apache.org/POM/4.0.0" 2 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 3 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 4 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 5 <modelVersion>4.0.0</modelVersion> 6 <groupId>com.bie</groupId> 7 <artifactId>sso-jwt</artifactId> 8 <version>1.0</version> 9 <packaging>war</packaging> 10 11 <dependencies> 12 <!-- spring5.0.6要求Jdk1.8以上版本 --> 13 <dependency> 14 <groupId>org.springframework</groupId> 15 <artifactId>spring-context</artifactId> 16 <version>5.0.6.RELEASE</version> 17 </dependency> 18 <dependency> 19 <groupId>org.springframework</groupId> 20 <artifactId>spring-webmvc</artifactId> 21 <version>5.0.6.RELEASE</version> 22 </dependency> 23 <dependency> 24 <groupId>org.springframework</groupId> 25 <artifactId>spring-aspects</artifactId> 26 <version>5.0.6.RELEASE</version> 27 </dependency> 28 <!-- JWT核心依賴 --> 29 <dependency> 30 <groupId>com.auth0</groupId> 31 <artifactId>java-jwt</artifactId> 32 <version>3.3.0</version> 33 </dependency> 34 <!-- java開發JWT的依賴jar包。 --> 35 <dependency> 36 <groupId>io.jsonwebtoken</groupId> 37 <artifactId>jjwt</artifactId> 38 <version>0.9.0</version> 39 </dependency> 40 <!-- 給springmvc提供的響應擴展。@ResponseBody --> 41 <dependency> 42 <groupId>com.fasterxml.jackson.core</groupId> 43 <artifactId>jackson-databind</artifactId> 44 <version>2.9.5</version> 45 </dependency> 46 <!-- jstl --> 47 <dependency> 48 <groupId>javax.servlet</groupId> 49 <artifactId>jstl</artifactId> 50 <version>1.2</version> 51 </dependency> 52 <!-- servlet --> 53 <dependency> 54 <groupId>javax.servlet</groupId> 55 <artifactId>servlet-api</artifactId> 56 <version>2.5</version> 57 <scope>provided</scope> 58 </dependency> 59 <!-- jsp --> 60 <dependency> 61 <groupId>javax.servlet.jsp</groupId> 62 <artifactId>jsp-api</artifactId> 63 <version>2.2</version> 64 <scope>provided</scope> 65 </dependency> 66 </dependencies> 67 68 <build> 69 <pluginManagement> 70 <plugins> 71 <!-- 配置Tomcat插件 --> 72 <plugin> 73 <groupId>org.apache.tomcat.maven</groupId> 74 <artifactId>tomcat7-maven-plugin</artifactId> 75 <version>2.2</version> 76 </plugin> 77 </plugins> 78 </pluginManagement> 79 <plugins> 80 <plugin> 81 <groupId>org.apache.tomcat.maven</groupId> 82 <artifactId>tomcat7-maven-plugin</artifactId> 83 <configuration> 84 <port>80</port> 85 <path>/</path> 86 </configuration> 87 </plugin> 88 </plugins> 89 </build> 90 91 </project>
JWTSubject作為Subject數據使用。也就是payload中保存的public claims,其中不包含任何敏感數據。建議使用實體類型,或者BO、DTO數據對象。
1 package com.bie.sso.commons; 2 3 /** 4 * 作為Subject數據使用。也就是payload中保存的public 5 * claims,其中不包含任何敏感數據。開發中建議使用實體類型,或者BO、DTO數據對象。 6 * 7 * @author biehl 8 * 9 */ 10 public class JWTSubject { 11 12 private String username; 13 14 public JWTSubject() { 15 super(); 16 } 17 18 public JWTSubject(String username) { 19 super(); 20 this.username = username; 21 } 22 23 public String getUsername() { 24 return username; 25 } 26 27 public void setUsername(String username) { 28 this.username = username; 29 } 30 31 }
JWTResult結果對象。可以存放錯誤編碼、正確編碼、claims驗證過程中payload中的數據。是payload里面的數據對象,是JWT里面的對象。
1 package com.bie.sso.commons; 2 3 import io.jsonwebtoken.Claims; 4 5 /** 6 * 結果對象。 7 * 8 * @author biehl 9 * 10 */ 11 public class JWTResult { 12 13 /** 14 * 錯誤編碼。在JWTUtils中定義的常量, 200為正確。 15 */ 16 private int errCode; 17 18 /** 19 * 是否成功,代表結果的狀態。 20 */ 21 private boolean success; 22 23 /** 24 * 驗證過程中payload中的數據。是payload里面的數據對象,是JWT里面的對象。 25 */ 26 private Claims claims; 27 28 public int getErrCode() { 29 return errCode; 30 } 31 32 public void setErrCode(int errCode) { 33 this.errCode = errCode; 34 } 35 36 public boolean isSuccess() { 37 return success; 38 } 39 40 public void setSuccess(boolean success) { 41 this.success = success; 42 } 43 44 public Claims getClaims() { 45 return claims; 46 } 47 48 public void setClaims(Claims claims) { 49 this.claims = claims; 50 } 51 52 }
JWTResponseData發送給客戶端的數據對象。 商業開發中,一般除特殊請求外,大多數的響應數據都是一個統一類型的數據,統一數據有統一的處理方式,便於開發和維護。
1 package com.bie.sso.commons; 2 3 /*** 4 * 發送給客戶端的數據對象。 商業開發中,一般除特殊請求外,大多數的響應數據都是一個統一類型的數據,統一數據有統一的處理方式,便於開發和維護。 5 * 6 * @author biehl 7 * 8 */ 9 public class JWTResponseData { 10 11 private Integer code;// 返回碼,類似HTTP響應碼。如:200成功,500服務器錯誤,404資源不存在等。 12 13 private Object data;// 業務數據 14 15 private String msg;// 返回描述 16 17 private String token;// 身份標識, JWT生成的令牌。 18 19 public Integer getCode() { 20 return code; 21 } 22 23 public void setCode(Integer code) { 24 this.code = code; 25 } 26 27 public Object getData() { 28 return data; 29 } 30 31 public void setData(Object data) { 32 this.data = data; 33 } 34 35 public String getMsg() { 36 return msg; 37 } 38 39 public void setMsg(String msg) { 40 this.msg = msg; 41 } 42 43 public String getToken() { 44 return token; 45 } 46 47 public void setToken(String token) { 48 this.token = token; 49 } 50 51 }
JWTUtils是JWT的工具。
1 package com.bie.sso.commons; 2 3 import java.util.Date; 4 5 import javax.crypto.SecretKey; 6 import javax.crypto.spec.SecretKeySpec; 7 8 import com.fasterxml.jackson.core.JsonProcessingException; 9 import com.fasterxml.jackson.databind.ObjectMapper; 10 11 import io.jsonwebtoken.Claims; 12 import io.jsonwebtoken.ExpiredJwtException; 13 import io.jsonwebtoken.JwtBuilder; 14 import io.jsonwebtoken.Jwts; 15 import io.jsonwebtoken.SignatureAlgorithm; 16 import io.jsonwebtoken.SignatureException; 17 18 /** 19 * JWT工具 20 * 21 * @author biehl 22 * 23 */ 24 public class JWTUtils { 25 26 // 服務器的key。用於做加解密的key數據,如果可以使用客戶端生成的key,當前定義的常量可以不使用。 27 private static final String JWT_SECERT = "test_jwt_secert"; 28 // 做json和java對象之間的相互轉換。 29 private static final ObjectMapper MAPPER = new ObjectMapper(); 30 public static final int JWT_ERRCODE_EXPIRE = 1005;// Token過期 31 public static final int JWT_ERRCODE_FAIL = 1006;// 驗證不通過 32 33 /** 34 * 創建密匙key 35 * 36 * @return 37 */ 38 public static SecretKey generalKey() { 39 try { 40 // byte[] encodedKey = Base64.decode(JWT_SECERT); 41 // 不管哪種方式最終得到一個byte[]類型的key就行 42 byte[] encodedKey = JWT_SECERT.getBytes("UTF-8"); 43 SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES"); 44 return key; 45 } catch (Exception e) { 46 e.printStackTrace(); 47 return null; 48 } 49 } 50 51 /** 52 * 簽發JWT,創建token的方法。 53 * 54 * @param id 55 * jwt的唯一身份標識,主要用來作為一次性token,從而回避重放攻擊。 56 * @param iss 57 * jwt簽發者,誰去生成的token信息。 58 * @param subject 59 * jwt所面向的用戶。payload中記錄的public claims公開信息。當前環境中就是用戶的登錄名。 60 * @param ttlMillis 61 * 有效期,單位毫秒。 62 * @return token token是一次性的。是為一個用戶的有效登錄周期准備的一個token。用戶退出或者超時,token將會失效。 63 * 64 * @throws Exception 65 */ 66 public static String createJWT(String id, String iss, String subject, long ttlMillis) { 67 // 加密算法。 68 SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256; 69 // 當前時間。 70 long nowMillis = System.currentTimeMillis(); 71 // 當前時間的日期對象。 72 Date now = new Date(nowMillis); 73 // 獲取到密匙key。 74 SecretKey secretKey = generalKey(); 75 // 創建JWT的構建器。 就是使用指定的信息和加密算法,生成Token的工具。 76 JwtBuilder builder = Jwts.builder().setId(id) // 設置身份標志。就是一個客戶端的唯一標記。 如:可以使用用戶的主鍵,客戶端的IP,服務器生成的隨機數據。 77 .setIssuer(iss) 78 79 .setSubject(subject) 80 81 .setIssuedAt(now) // token生成的時間。 82 .signWith(signatureAlgorithm, secretKey); // 設定密匙和算法 83 // 如果有效期大於等於0,ttlMillis是多長時間,單位是毫秒。 84 if (ttlMillis >= 0) { 85 long expMillis = nowMillis + ttlMillis; // 當前時間加上有效期就是token的失效時間。 86 Date expDate = new Date(expMillis); // token的失效時間。 87 builder.setExpiration(expDate);// 設置token的失效時間。 88 } 89 return builder.compact(); // 生成token 90 } 91 92 /** 93 * 驗證JWT 94 * 95 * @param jwtStr 96 * @return 97 */ 98 public static JWTResult validateJWT(String jwtStr) { 99 // 創建JWTResult對象實例 100 JWTResult checkResult = new JWTResult(); 101 // 創建Claims對象實例 102 Claims claims = null; 103 try { 104 // 解析JWT字符串 105 claims = parseJWT(jwtStr); 106 // 設定信息 107 checkResult.setSuccess(true); 108 checkResult.setClaims(claims); 109 } catch (ExpiredJwtException e) { // token超時,Token過期 110 checkResult.setErrCode(JWT_ERRCODE_EXPIRE); 111 checkResult.setSuccess(false); 112 } catch (SignatureException e) { // 校驗失敗,驗證不通過 113 checkResult.setErrCode(JWT_ERRCODE_FAIL); 114 checkResult.setSuccess(false); 115 } catch (Exception e) { 116 checkResult.setErrCode(JWT_ERRCODE_FAIL); 117 checkResult.setSuccess(false); 118 } 119 return checkResult; 120 } 121 122 /** 123 * 124 * 解析JWT字符串 125 * 126 * @param jwt 127 * 就是服務器為客戶端生成的簽名數據,就是token。 128 * @return 129 * @throws Exception 130 */ 131 public static Claims parseJWT(String jwt) throws Exception { 132 // 創建密匙key,通過key校驗token 133 SecretKey secretKey = generalKey(); 134 // getBody獲取的就是token中記錄的payload數據。就是payload中保存的所有的claims。 135 return Jwts.parser() 136 137 .setSigningKey(secretKey) // 設置密匙 138 139 .parseClaimsJws(jwt) // 解析的是什么字符串 140 141 .getBody(); // getBody獲取的就是token中記錄的payload數據。就是payload中保存的所有的claims。 142 } 143 144 /** 145 * 生成subject信息 146 * 147 * @param subObj 148 * - 要轉換的對象。 149 * @return java對象->JSON字符串出錯時返回null 150 */ 151 public static String generalSubject(Object subObj) { 152 try { 153 return MAPPER.writeValueAsString(subObj); 154 } catch (JsonProcessingException e) { 155 e.printStackTrace(); 156 return null; 157 } 158 } 159 160 }
JWTController控制層的業務邏輯,如下所示:
1 package com.bie.sso.controller; 2 3 import java.util.HashMap; 4 import java.util.Map; 5 import java.util.UUID; 6 7 import javax.servlet.http.HttpServletRequest; 8 9 import org.springframework.stereotype.Controller; 10 import org.springframework.web.bind.annotation.RequestMapping; 11 import org.springframework.web.bind.annotation.ResponseBody; 12 13 import com.bie.sso.commons.JWTResponseData; 14 import com.bie.sso.commons.JWTResult; 15 import com.bie.sso.commons.JWTSubject; 16 import com.bie.sso.commons.JWTUtils; 17 18 /** 19 * 20 * @author biehl 21 * 22 */ 23 @Controller 24 public class JWTController { 25 26 private static final Map<String, String> USERS = new HashMap<>(16); 27 28 static { 29 // 初始化10個賬號密碼信息 30 for (int i = 0; i < 10; i++) { 31 USERS.put("admin" + i, "password" + 1); 32 } 33 } 34 35 /** 36 * 是否可登錄 37 * 38 * @param username 39 * @param password 40 * @return 41 */ 42 public static boolean isLogin(String username, String password) { 43 if (null == username || username.trim().length() == 0) { 44 return false; 45 } 46 String obj = USERS.get(username); 47 if (null == obj || !obj.equals(password)) { 48 return false; 49 } 50 return true; 51 } 52 53 @RequestMapping("/authorization") 54 @ResponseBody 55 public Object authorization(HttpServletRequest request) { 56 // 獲取到頭部的校驗數據 57 String token = request.getHeader("Authorization"); 58 // 驗證JWT 59 JWTResult result = JWTUtils.validateJWT(token); 60 // 61 JWTResponseData responseData = new JWTResponseData(); 62 // 判斷是否成功 63 if (result.isSuccess()) { 64 // 如果成功設置響應碼200 65 responseData.setCode(200); 66 // 將獲取到的用戶信息進行設置。 67 responseData.setData(result.getClaims().getSubject()); 68 // 重新生成token,就是為了重置token的有效期。 69 String newToken = JWTUtils.createJWT(result.getClaims().getId(), result.getClaims().getIssuer(), 70 result.getClaims().getSubject(), 1 * 60 * 1000); 71 // 設置新的token的有效期。 72 responseData.setToken(newToken); 73 return responseData; 74 } else { 75 // 如果失敗設置響應碼500 76 responseData.setCode(500); 77 responseData.setMsg("用戶未登錄"); 78 return responseData; 79 } 80 } 81 82 @RequestMapping("/login") 83 @ResponseBody 84 public Object login(String username, String password) { 85 JWTResponseData responseData = null; 86 // 認證用戶信息。本案例中訪問靜態數據。 87 if (JWTController.isLogin(username, password)) { 88 // 創建一個對象實例,將賬號傳遞進去創建實例對象。 89 JWTSubject subject = new JWTSubject(username); 90 // 創建簽名信息token 91 String jwtToken = JWTUtils.createJWT(UUID.randomUUID().toString(), "bie-test-jwt", 92 JWTUtils.generalSubject(subject), 1 * 60 * 1000); 93 // 創建相應實例 94 responseData = new JWTResponseData(); 95 responseData.setCode(200); 96 responseData.setData(null); 97 responseData.setMsg("登錄成功"); 98 // 設置相應的token 99 responseData.setToken(jwtToken); 100 } else { 101 responseData = new JWTResponseData(); 102 responseData.setCode(500); 103 responseData.setData(null); 104 responseData.setMsg("登錄失敗"); 105 responseData.setToken(null); 106 } 107 return responseData; 108 } 109 110 }
登錄主界面,可以使用登錄,和驗證是否登錄來測試JWT的使用。登錄主界面,如下所示:
1 <%@ page language="java" contentType="text/html; charset=UTF-8" 2 pageEncoding="UTF-8"%> 3 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> 4 <html> 5 <head> 6 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> 7 <title>Insert title here</title> 8 <script type="text/javascript" src="js/jquery.min.js"></script> 9 <script type="text/javascript"> 10 function login() { 11 var username = $("#username").val(); 12 var password = $("#password").val(); 13 var params = "username=" + username + "&password=" + password; 14 $.ajax({ 15 'url' : '${pageContext.request.contextPath }/login', 16 'data' : params, 17 'success' : function(data) { 18 if (data.code == 200) { 19 var token = data.token; 20 // web storage的查看 - 在瀏覽器的開發者面板中的application中查看。 21 // local storage - 本地存儲的數據。 長期有效的。 22 // session storage - 會話存儲的數據。 一次會話有效。 23 var localStorage = window.localStorage; // 瀏覽器提供的存儲空間。 根據key-value存儲數據。 24 localStorage.token = token; 25 } else { 26 alert(data.msg); 27 } 28 } 29 }); 30 } 31 32 function setHeader(xhr) { // XmlHttpRequest 33 xhr.setRequestHeader("Authorization", window.localStorage.token); 34 } 35 36 function testLocalStorage() { 37 $.ajax({ 38 'url' : '${pageContext.request.contextPath}/authorization', 39 'success' : function(data) { 40 if (data.code == 200) { 41 window.localStorage.token = data.token; 42 alert(data.data); 43 } else { 44 alert(data.msg); 45 } 46 }, 47 'beforeSend' : setHeader 48 }); 49 } 50 </script> 51 </head> 52 <body> 53 <center> 54 <table> 55 <caption>登錄測試</caption> 56 <tr> 57 <td style="text-align: right; padding-right: 5px">登錄名:</td> 58 <td style="text-align: left; padding-left: 5px"><input 59 type="text" name="username" id="username" /></td> 60 </tr> 61 <tr> 62 <td style="text-align: right; padding-right: 5px">密碼:</td> 63 <td style="text-align: left; padding-left: 5px"><input 64 type="text" name="password" id="password" /></td> 65 </tr> 66 <tr> 67 <td style="text-align: right; padding-right: 5px" colspan="2"> 68 <input type="button" value="登錄" onclick="login();" /> 69 </td> 70 </tr> 71 </table> 72 </center> 73 <input type="button" value="驗證是否登錄" 74 onclick="testLocalStorage();" /> 75 </body> 76 </html>
可以借助瀏覽器的幫助理解這些知識點。
注意點1:web storage的查看,在瀏覽器的開發者面板中的application中查看。
a)、local storage:本地存儲的數據,長期有效的。
b)、session storage:會話存儲的數據,一次會話有效。
注意點2:如果對session和cookie不理解的,還是多搜索一下session和cookie的區別,方便自己的理解,然后在搜索一下單點登錄的實現過程,網上已經很多介紹了,這篇主要是幫助自己理解JWT的使用,包含生成token,驗證token等等知識點。
6、基於JWT機制的單點登錄。注意事項,如下所示:
1)、使用JWT實現單點登錄時,需要注意token時效性。token是保存在客戶端的令牌數據,如果永久有效,則有被劫持的可能。token在設計的時候,可以考慮一次性有效或一段時間內有效。如果設置有效時長,則需要考慮是否需要刷新token有效期問題。
2)、使用JWT技術生成的token,客戶端在保存的時候可以考慮cookie或localStorage。cookie保存方式,可以實現跨域傳遞數據。localStorage是域私有的本地存儲,無法實現跨域。
3)、關於webstorage的相關知識點。
a、webstorage可保存的數據容量為5M。且只能存儲字符串數據。
b、webstorage分為localStorage和sessionStorage。
c、localStorage的生命周期是永久的,關閉頁面或瀏覽器之后localStorage中的數據也不會消失。localStorage除非主動刪除數據,否則數據永遠不會消失。
d、sessionStorage是會話相關的本地存儲單元,生命周期是在僅在當前會話下有效。sessionStorage引入了一個“瀏覽器窗口”的概念,sessionStorage是在同源的窗口中始終存在的數據。只要這個瀏覽器窗口沒有關閉,即使刷新頁面或者進入同源另一個頁面,數據依然存在。但是sessionStorage在關閉了瀏覽器窗口后就會被銷毀。同時獨立的打開同一個窗口同一個頁面,sessionStorage也是不一樣的。
7、Restful接口設計,Rest簡述。
答:REST(英文:Representational State Transfer,簡稱REST)描述了一個架構樣式的網絡系統,比如 web 應用程序。它首次出現在 2000 年 Roy Fielding 的博士論文中,他是 HTTP 規范的主要編寫者之一。在目前主流的三種Web服務交互方案中,REST相比於SOAP(Simple Object Access protocol,簡單對象訪問協議)以及XML-RPC更加簡單明了,無論是對URL的處理還是對Payload的編碼,REST都傾向於用更加簡單輕量的方法設計和實現。值得注意的是REST並沒有一個明確的標准,而更像是一種設計的風格。
8、Restful接口設計,Restful簡述。
答:對應的中文是rest式的,Restful web service是一種常見的rest的應用,是遵守了rest風格的web服務,rest式的web服務是一種ROA(The Resource-Oriented Architecture)(面向資源的架構)。
9、Restful接口設計,Restful特性。
1)、普通架構。
每次請求的接口或者地址,都在做描述,例如查詢的時候用了query,新增的時候用了save。如:http://127.0.0.1/user/query/1,這個是GET請求,根據用戶id查詢用戶數據。http://127.0.0.1/user/save,這個是POST請求,新增用戶。
2)、Restful架構。
使用get請求,就是查詢.使用post請求,就是新增的請求,意圖明顯,沒有必要做描述,這就是restful。http://127.0.0.1/user/1,這個是GET請求,根據用戶id查詢用戶數據。http://127.0.0.1/user,這個是POST請求,新增用戶。
3)、Restful操作方式。冪等性:多次訪問,結果資源狀態是否相同。安全:訪問是否會變更服務器資源狀態。
HTTP方法 |
資源操作 |
冪等性 |
是否安全? |
GET |
查詢 |
是 |
是 |
POST |
新增 |
否 |
否 |
PUT |
修改 |
是 |
否 |
DELETE |
刪除 |
是 |
否 |
4)、響應狀態碼。
編碼 |
HTTP方法 |
響應體內容 |
描述 |
200 |
get/put |
資源數據 |
操作成功 |
201 |
post |
源數據 |
創建成功 |
202 |
post/put/delete |
無 |
請求已接受 |
204 |
delete/put |
無 |
請求已處理,無返回數據 |
301 |
get |
link 鏈接 |
資源已被移除 |
303 |
get |
link |
重定向 |
304 |
get |
無 |
資源沒有被修改 |
400 |
get/post/put/delete |
錯誤提示消息 |
參數錯誤(缺少,格式錯誤等) |
401 |
get/post/put/delete |
錯誤提示消息 |
未授權 |
403 |
get/post/put/delete |
錯誤提示消息 |
訪問受限、授權過期 |
404 |
get/post/put/delete |
錯誤提示消息 |
資源、服務未找到 |
405 |
get/post/put/delete |
錯誤提示消息 |
不允許的HTTP方法 |
409 |
get/post/put/delete |
錯誤提示消息 |
資源沖突或資源被鎖定 |
415 |
get/post/put/delete |
錯誤提示消息 |
不支持的數據類型或媒體類型 |
429 |
get/post/put/delete |
錯誤提示消息 |
請求過多被限制 |
500 |
get/post/put/delete |
錯誤提示消息 |
系統錯誤 |
501 |
get/post/put/delete |
錯誤提示消息 |
接口未實現 |
待續......